diff options
author | Sven Gothel <[email protected]> | 2014-06-19 17:03:28 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-06-19 17:03:28 +0200 |
commit | d9a584844a60542519d813b5dc1a62428f14a0ae (patch) | |
tree | 942c10a5ebcd0aab65e9d6facb59778468f39d3b /LibOVR/Src |
Add OculusSDK 0.3.2 Linux Source Code w/o Samples, docs or binaries (libs or tools)
Diffstat (limited to 'LibOVR/Src')
116 files changed, 48619 insertions, 0 deletions
diff --git a/LibOVR/Src/CAPI/CAPI_DistortionRenderer.cpp b/LibOVR/Src/CAPI/CAPI_DistortionRenderer.cpp new file mode 100644 index 0000000..613d8fb --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_DistortionRenderer.cpp @@ -0,0 +1,73 @@ +/************************************************************************************ + +Filename : CAPI_DistortionRenderer.cpp +Content : Combines all of the rendering state associated with the HMD +Created : February 2, 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_DistortionRenderer.h" + +#if defined (OVR_OS_WIN32) + +// TBD: Move to separate config file that handles back-ends. +#define OVR_D3D_VERSION 11 +#include "D3D1X/CAPI_D3D1X_DistortionRenderer.h" +#undef OVR_D3D_VERSION + +#define OVR_D3D_VERSION 10 +#include "D3D1X/CAPI_D3D1X_DistortionRenderer.h" +#undef OVR_D3D_VERSION + +#define OVR_D3D_VERSION 9 +#include "D3D1X/CAPI_D3D9_DistortionRenderer.h" +#undef OVR_D3D_VERSION + +#endif + +#include "GL/CAPI_GL_DistortionRenderer.h" + +namespace OVR { namespace CAPI { + +//------------------------------------------------------------------------------------- +// ***** DistortionRenderer + +// TBD: Move to separate config file that handles back-ends. + +DistortionRenderer::CreateFunc DistortionRenderer::APICreateRegistry[ovrRenderAPI_Count] = +{ + 0, // None + &GL::DistortionRenderer::Create, + 0, // Android_GLES +#if defined (OVR_OS_WIN32) + &D3D9::DistortionRenderer::Create, + &D3D10::DistortionRenderer::Create, + &D3D11::DistortionRenderer::Create +#else + 0, + 0, + 0 +#endif +}; + + +}} // namespace OVR::CAPI + diff --git a/LibOVR/Src/CAPI/CAPI_DistortionRenderer.h b/LibOVR/Src/CAPI/CAPI_DistortionRenderer.h new file mode 100644 index 0000000..a256bc6 --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_DistortionRenderer.h @@ -0,0 +1,118 @@ +/************************************************************************************ + +Filename : CAPI_DistortionRenderer.h +Content : Abstract interface for platform-specific rendering of distortion +Created : February 2, 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. + +************************************************************************************/ + +#ifndef OVR_CAPI_DistortionRenderer_h +#define OVR_CAPI_DistortionRenderer_h + +#include "CAPI_HMDRenderState.h" +#include "CAPI_FrameTimeManager.h" + + +namespace OVR { namespace CAPI { + +//------------------------------------------------------------------------------------- +// ***** CAPI::DistortionRenderer + +// DistortionRenderer implements rendering of distortion and other overlay elements +// in platform-independent way. +// Platform-specific renderer back ends for CAPI are derived from this class. + +class DistortionRenderer : public RefCountBase<DistortionRenderer> +{ + // Quiet assignment compiler warning. + void operator = (const DistortionRenderer&) { } +public: + + DistortionRenderer(ovrRenderAPIType api, ovrHmd hmd, + FrameTimeManager& timeManager, + const HMDRenderState& renderState) + : RenderAPI(api), HMD(hmd), TimeManager(timeManager), RState(renderState) + { } + virtual ~DistortionRenderer() + { } + + + // Configures the Renderer based on externally passed API settings. Must be + // called before use. + // Under D3D, apiConfig includes D3D Device pointer, back buffer and other + // needed structures. + virtual bool Initialize(const ovrRenderAPIConfig* apiConfig, + unsigned distortionCaps) = 0; + + // Submits one eye texture for rendering. This is in the separate method to + // allow "submit as you render" scenarios on horizontal screens where one + // eye can be scanned out before the other. + virtual void SubmitEye(int eyeId, ovrTexture* eyeTexture) = 0; + + // Finish the frame, optionally swapping buffers. + // Many implementations may actually apply the distortion here. + virtual void EndFrame(bool swapBuffers, unsigned char* latencyTesterDrawColor, + unsigned char* latencyTester2DrawColor) = 0; + + // Stores the current graphics pipeline state so it can be restored later. + void SaveGraphicsState() { if (!(RState.EnabledHmdCaps & ovrHmdCap_NoRestore)) GfxState->Save(); } + + // Restores the saved graphics pipeline state. + void RestoreGraphicsState() { if (!(RState.EnabledHmdCaps & ovrHmdCap_NoRestore)) GfxState->Restore(); } + + // *** Creation Factory logic + + ovrRenderAPIType GetRenderAPI() const { return RenderAPI; } + + // Creation function for this interface, registered for API. + typedef DistortionRenderer* (*CreateFunc)(ovrHmd hmd, + FrameTimeManager &timeManager, + const HMDRenderState& renderState); + + static CreateFunc APICreateRegistry[ovrRenderAPI_Count]; + +protected: + + class GraphicsState : public RefCountBase<GraphicsState> + { + public: + GraphicsState() : IsValid(false) {} + virtual ~GraphicsState() {} + virtual void Save() = 0; + virtual void Restore() = 0; + + protected: + bool IsValid; + }; + + const ovrRenderAPIType RenderAPI; + const ovrHmd HMD; + FrameTimeManager& TimeManager; + const HMDRenderState& RState; + Ptr<GraphicsState> GfxState; +}; + +}} // namespace OVR::CAPI + + +#endif // OVR_CAPI_DistortionRenderer_h + + diff --git a/LibOVR/Src/CAPI/CAPI_FrameTimeManager.cpp b/LibOVR/Src/CAPI/CAPI_FrameTimeManager.cpp new file mode 100644 index 0000000..3e776c3 --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_FrameTimeManager.cpp @@ -0,0 +1,675 @@ +/************************************************************************************ + +Filename : CAPI_FrameTimeManager.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.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_FrameTimeManager.h" + + +namespace OVR { namespace CAPI { + + +//------------------------------------------------------------------------------------- +// ***** FrameLatencyTracker + + +FrameLatencyTracker::FrameLatencyTracker() +{ + Reset(); +} + +void FrameLatencyTracker::Reset() +{ + TrackerEnabled = true; + WaitMode = SampleWait_Zeroes; + FrameIndex = 0; + MatchCount = 0; + RenderLatencySeconds = 0.0; + TimewarpLatencySeconds = 0.0; + + FrameDeltas.Clear(); +} + + +unsigned char FrameLatencyTracker::GetNextDrawColor() +{ + if (!TrackerEnabled || (WaitMode == SampleWait_Zeroes) || + (FrameIndex >= FramesTracked)) + { + return (unsigned char)Util::FrameTimeRecord::ReadbackIndexToColor(0); + } + + OVR_ASSERT(FrameIndex < FramesTracked); + return (unsigned char)Util::FrameTimeRecord::ReadbackIndexToColor(FrameIndex+1); +} + + +void FrameLatencyTracker::SaveDrawColor(unsigned char drawColor, double endFrameTime, + double renderIMUTime, double timewarpIMUTime ) +{ + if (!TrackerEnabled || (WaitMode == SampleWait_Zeroes)) + return; + + if (FrameIndex < FramesTracked) + { + OVR_ASSERT(Util::FrameTimeRecord::ReadbackIndexToColor(FrameIndex+1) == drawColor); + OVR_UNUSED(drawColor); + + // saves {color, endFrame time} + FrameEndTimes[FrameIndex].ReadbackIndex = FrameIndex + 1; + FrameEndTimes[FrameIndex].TimeSeconds = endFrameTime; + FrameEndTimes[FrameIndex].RenderIMUTimeSeconds = renderIMUTime; + FrameEndTimes[FrameIndex].TimewarpIMUTimeSeconds= timewarpIMUTime; + FrameEndTimes[FrameIndex].MatchedRecord = false; + FrameIndex++; + } + else + { + // If the request was outstanding for too long, switch to zero mode to restart. + if (endFrameTime > (FrameEndTimes[FrameIndex-1].TimeSeconds + 0.15)) + { + if (MatchCount == 0) + { + // If nothing was matched, we have no latency reading. + RenderLatencySeconds = 0.0; + TimewarpLatencySeconds = 0.0; + } + + WaitMode = SampleWait_Zeroes; + MatchCount = 0; + FrameIndex = 0; + } + } +} + + +void FrameLatencyTracker::MatchRecord(const Util::FrameTimeRecordSet &r) +{ + if (!TrackerEnabled) + return; + + if (WaitMode == SampleWait_Zeroes) + { + // Do we have all zeros? + if (r.IsAllZeroes()) + { + OVR_ASSERT(FrameIndex == 0); + WaitMode = SampleWait_Match; + MatchCount = 0; + } + return; + } + + // We are in Match Mode. Wait until all colors are matched or timeout, + // at which point we go back to zeros. + + for (int i = 0; i < FrameIndex; i++) + { + int recordIndex = 0; + int consecutiveMatch = 0; + + OVR_ASSERT(FrameEndTimes[i].ReadbackIndex != 0); + + if (r.FindReadbackIndex(&recordIndex, FrameEndTimes[i].ReadbackIndex)) + { + // Advance forward to see that we have several more matches. + int ri = recordIndex + 1; + int j = i + 1; + + consecutiveMatch++; + + for (; (j < FrameIndex) && (ri < Util::FrameTimeRecordSet::RecordCount); j++, ri++) + { + if (r[ri].ReadbackIndex != FrameEndTimes[j].ReadbackIndex) + break; + consecutiveMatch++; + } + + // Match at least 2 items in the row, to avoid accidentally matching color. + if (consecutiveMatch > 1) + { + // Record latency values for all but last samples. Keep last 2 samples + // for the future to simplify matching. + for (int q = 0; q < consecutiveMatch; q++) + { + const Util::FrameTimeRecord &scanoutFrame = r[recordIndex+q]; + FrameTimeRecordEx &renderFrame = FrameEndTimes[i+q]; + + if (!renderFrame.MatchedRecord) + { + double deltaSeconds = scanoutFrame.TimeSeconds - renderFrame.TimeSeconds; + if (deltaSeconds > 0.0) + { + FrameDeltas.AddTimeDelta(deltaSeconds); + LatencyRecordTime = scanoutFrame.TimeSeconds; + RenderLatencySeconds = scanoutFrame.TimeSeconds - renderFrame.RenderIMUTimeSeconds; + TimewarpLatencySeconds = (renderFrame.TimewarpIMUTimeSeconds == 0.0) ? 0.0 : + (scanoutFrame.TimeSeconds - renderFrame.TimewarpIMUTimeSeconds); + } + + renderFrame.MatchedRecord = true; + MatchCount++; + } + } + + // Exit for. + break; + } + } + } // for ( i => FrameIndex ) + + + // If we matched all frames, start over. + if (MatchCount == FramesTracked) + { + WaitMode = SampleWait_Zeroes; + MatchCount = 0; + FrameIndex = 0; + } +} + + +void FrameLatencyTracker::GetLatencyTimings(float latencies[3]) +{ + if (ovr_GetTimeInSeconds() > (LatencyRecordTime + 2.0)) + { + latencies[0] = 0.0f; + latencies[1] = 0.0f; + latencies[2] = 0.0f; + } + else + { + latencies[0] = (float)RenderLatencySeconds; + latencies[1] = (float)TimewarpLatencySeconds; + latencies[2] = (float)FrameDeltas.GetMedianTimeDelta(); + } +} + + +//------------------------------------------------------------------------------------- + +FrameTimeManager::FrameTimeManager(bool vsyncEnabled) + : VsyncEnabled(vsyncEnabled), DynamicPrediction(true), SdkRender(false), + FrameTiming() +{ + RenderIMUTimeSeconds = 0.0; + TimewarpIMUTimeSeconds = 0.0; + + // HACK: SyncToScanoutDelay observed close to 1 frame in video cards. + // Overwritten by dynamic latency measurement on DK2. + VSyncToScanoutDelay = 0.013f; + NoVSyncToScanoutDelay = 0.004f; +} + +void FrameTimeManager::Init(HmdRenderInfo& renderInfo) +{ + // Set up prediction distances. + // With-Vsync timings. + RenderInfo = renderInfo; + + ScreenSwitchingDelay = RenderInfo.Shutter.PixelSettleTime * 0.5f + + RenderInfo.Shutter.PixelPersistence * 0.5f; +} + +void FrameTimeManager::ResetFrameTiming(unsigned frameIndex, + bool dynamicPrediction, + bool sdkRender) +{ + DynamicPrediction = dynamicPrediction; + SdkRender = sdkRender; + + FrameTimeDeltas.Clear(); + DistortionRenderTimes.Clear(); + ScreenLatencyTracker.Reset(); + + FrameTiming.FrameIndex = frameIndex; + FrameTiming.NextFrameTime = 0.0; + FrameTiming.ThisFrameTime = 0.0; + FrameTiming.Inputs.FrameDelta = calcFrameDelta(); + FrameTiming.Inputs.ScreenDelay = calcScreenDelay(); + FrameTiming.Inputs.TimewarpWaitDelta = 0.0f; + + LocklessTiming.SetState(FrameTiming); +} + + +double FrameTimeManager::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.GetMedianTimeDelta(); + if (frameDelta > (RenderInfo.Shutter.VsyncToNextVsync + 0.001)) + frameDelta = RenderInfo.Shutter.VsyncToNextVsync; + } + else + { + frameDelta = RenderInfo.Shutter.VsyncToNextVsync; + } + + return frameDelta; +} + + +double FrameTimeManager::calcScreenDelay() const +{ + double screenDelay = ScreenSwitchingDelay; + double measuredVSyncToScanout; + + // Use real-time DK2 latency tester HW for prediction if its is working. + // Do sanity check under 60 ms + if (!VsyncEnabled) + { + screenDelay += NoVSyncToScanoutDelay; + } + else if ( DynamicPrediction && + (ScreenLatencyTracker.FrameDeltas.GetCount() > 3) && + (measuredVSyncToScanout = ScreenLatencyTracker.FrameDeltas.GetMedianTimeDelta(), + (measuredVSyncToScanout > 0.0001) && (measuredVSyncToScanout < 0.06)) ) + { + screenDelay += measuredVSyncToScanout; + } + else + { + screenDelay += VSyncToScanoutDelay; + } + + return screenDelay; +} + + +double FrameTimeManager::calcTimewarpWaitDelta() const +{ + // If timewarp timing hasn't been calculated, we should wait. + if (!VsyncEnabled) + return 0.0; + + if (SdkRender) + { + if (NeedDistortionTimeMeasurement()) + return 0.0; + return -(DistortionRenderTimes.GetMedianTimeDelta() + 0.002); + } + + // Just a hard-coded "high" value for game-drawn code. + // TBD: Just return 0 and let users calculate this themselves? + return -0.003; +} + + + +void FrameTimeManager::Timing::InitTimingFromInputs(const FrameTimeManager::TimingInputs& inputs, + HmdShutterTypeEnum shutterType, + double thisFrameTime, unsigned int frameIndex) +{ + // ThisFrameTime comes from the end of last frame, unless it it changed. + double nextFrameBase; + double frameDelta = inputs.FrameDelta; + + FrameIndex = frameIndex; + + ThisFrameTime = thisFrameTime; + NextFrameTime = ThisFrameTime + frameDelta; + nextFrameBase = NextFrameTime + inputs.ScreenDelay; + MidpointTime = nextFrameBase + frameDelta * 0.5; + TimewarpPointTime = (inputs.TimewarpWaitDelta == 0.0) ? + 0.0 : (NextFrameTime + inputs.TimewarpWaitDelta); + + // Calculate absolute points in time when eye rendering or corresponding time-warp + // screen edges will become visible. + // This only matters with VSync. + switch(shutterType) + { + case HmdShutter_RollingTopToBottom: + EyeRenderTimes[0] = MidpointTime; + EyeRenderTimes[1] = MidpointTime; + TimeWarpStartEndTimes[0][0] = nextFrameBase; + TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta; + TimeWarpStartEndTimes[1][0] = nextFrameBase; + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; + break; + case HmdShutter_RollingLeftToRight: + EyeRenderTimes[0] = nextFrameBase + frameDelta * 0.25; + EyeRenderTimes[1] = nextFrameBase + frameDelta * 0.75; + + /* + // TBD: MA: It is probably better if mesh sets it up per-eye. + // Would apply if screen is 0 -> 1 for each eye mesh + TimeWarpStartEndTimes[0][0] = nextFrameBase; + TimeWarpStartEndTimes[0][1] = MidpointTime; + TimeWarpStartEndTimes[1][0] = MidpointTime; + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; + */ + + // Mesh is set up to vary from Edge of scree 0 -> 1 across both eyes + TimeWarpStartEndTimes[0][0] = nextFrameBase; + TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta; + TimeWarpStartEndTimes[1][0] = nextFrameBase; + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; + + break; + case HmdShutter_RollingRightToLeft: + + EyeRenderTimes[0] = nextFrameBase + frameDelta * 0.75; + EyeRenderTimes[1] = nextFrameBase + frameDelta * 0.25; + + // This is *Correct* with Tom's distortion mesh organization. + TimeWarpStartEndTimes[0][0] = nextFrameBase ; + TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta; + TimeWarpStartEndTimes[1][0] = nextFrameBase ; + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; + break; + case HmdShutter_Global: + // TBD + EyeRenderTimes[0] = MidpointTime; + EyeRenderTimes[1] = MidpointTime; + TimeWarpStartEndTimes[0][0] = MidpointTime; + TimeWarpStartEndTimes[0][1] = MidpointTime; + TimeWarpStartEndTimes[1][0] = MidpointTime; + TimeWarpStartEndTimes[1][1] = MidpointTime; + break; + default: + break; + } +} + + +double FrameTimeManager::BeginFrame(unsigned frameIndex) +{ + RenderIMUTimeSeconds = 0.0; + TimewarpIMUTimeSeconds = 0.0; + + // ThisFrameTime comes from the end of last frame, unless it it changed. + double thisFrameTime = (FrameTiming.NextFrameTime != 0.0) ? + FrameTiming.NextFrameTime : ovr_GetTimeInSeconds(); + + // We are starting to process a new frame... + FrameTiming.InitTimingFromInputs(FrameTiming.Inputs, RenderInfo.Shutter.Type, + thisFrameTime, frameIndex); + + return FrameTiming.ThisFrameTime; +} + + +void FrameTimeManager::EndFrame() +{ + // Record timing since last frame; must be called after Present & sync. + FrameTiming.NextFrameTime = ovr_GetTimeInSeconds(); + if (FrameTiming.ThisFrameTime > 0.0) + { + FrameTimeDeltas.AddTimeDelta(FrameTiming.NextFrameTime - FrameTiming.ThisFrameTime); + FrameTiming.Inputs.FrameDelta = calcFrameDelta(); + } + + // Write to Lock-less + LocklessTiming.SetState(FrameTiming); +} + + + +// Thread-safe function to query timing for a future frame + +FrameTimeManager::Timing FrameTimeManager::GetFrameTiming(unsigned frameIndex) +{ + Timing frameTiming = LocklessTiming.GetState(); + + if (frameTiming.ThisFrameTime != 0.0) + { + // If timing hasn't been initialized, starting based on "now" is the best guess. + frameTiming.InitTimingFromInputs(frameTiming.Inputs, RenderInfo.Shutter.Type, + ovr_GetTimeInSeconds(), frameIndex); + } + + else if (frameIndex > frameTiming.FrameIndex) + { + unsigned frameDelta = frameIndex - frameTiming.FrameIndex; + double thisFrameTime = frameTiming.NextFrameTime + + double(frameDelta-1) * frameTiming.Inputs.FrameDelta; + // Don't run away too far into the future beyond rendering. + OVR_ASSERT(frameDelta < 6); + + frameTiming.InitTimingFromInputs(frameTiming.Inputs, RenderInfo.Shutter.Type, + thisFrameTime, frameIndex); + } + + return frameTiming; +} + + +double FrameTimeManager::GetEyePredictionTime(ovrEyeType eye) +{ + if (VsyncEnabled) + { + return FrameTiming.EyeRenderTimes[eye]; + } + + // No VSync: Best guess for the near future + return ovr_GetTimeInSeconds() + ScreenSwitchingDelay + NoVSyncToScanoutDelay; +} + +Transformf FrameTimeManager::GetEyePredictionPose(ovrHmd hmd, ovrEyeType eye) +{ + double eyeRenderTime = GetEyePredictionTime(eye); + ovrSensorState eyeState = ovrHmd_GetSensorState(hmd, eyeRenderTime); + +// EyeRenderPoses[eye] = eyeState.Predicted.Pose; + + // Record view pose sampling time for Latency reporting. + if (RenderIMUTimeSeconds == 0.0) + RenderIMUTimeSeconds = eyeState.Recorded.TimeInSeconds; + + return eyeState.Predicted.Pose; +} + + +void FrameTimeManager::GetTimewarpPredictions(ovrEyeType eye, double timewarpStartEnd[2]) +{ + if (VsyncEnabled) + { + timewarpStartEnd[0] = FrameTiming.TimeWarpStartEndTimes[eye][0]; + timewarpStartEnd[1] = FrameTiming.TimeWarpStartEndTimes[eye][1]; + return; + } + + // Free-running, so this will be displayed immediately. + // Unfortunately we have no idea which bit of the screen is actually going to be displayed. + // TODO: guess which bit of the screen is being displayed! + // (e.g. use DONOTWAIT on present and see when the return isn't WASSTILLWAITING?) + + // We have no idea where scan-out is currently, so we can't usefully warp the screen spatially. + timewarpStartEnd[0] = ovr_GetTimeInSeconds() + ScreenSwitchingDelay + NoVSyncToScanoutDelay; + timewarpStartEnd[1] = timewarpStartEnd[0]; +} + + +void FrameTimeManager::GetTimewarpMatrices(ovrHmd hmd, ovrEyeType eyeId, + ovrPosef renderPose, ovrMatrix4f twmOut[2]) +{ + if (!hmd) + { + return; + } + + double timewarpStartEnd[2] = { 0.0, 0.0 }; + GetTimewarpPredictions(eyeId, timewarpStartEnd); + + ovrSensorState startState = ovrHmd_GetSensorState(hmd, timewarpStartEnd[0]); + ovrSensorState endState = ovrHmd_GetSensorState(hmd, timewarpStartEnd[1]); + + if (TimewarpIMUTimeSeconds == 0.0) + TimewarpIMUTimeSeconds = startState.Recorded.TimeInSeconds; + + Quatf quatFromStart = startState.Predicted.Pose.Orientation; + Quatf quatFromEnd = endState.Predicted.Pose.Orientation; + Quatf quatFromEye = renderPose.Orientation; //EyeRenderPoses[eyeId].Orientation; + quatFromEye.Invert(); + + Quatf timewarpStartQuat = quatFromEye * quatFromStart; + Quatf timewarpEndQuat = quatFromEye * quatFromEnd; + + Matrix4f timewarpStart(timewarpStartQuat); + Matrix4f timewarpEnd(timewarpEndQuat); + + + // The real-world orientations have: X=right, Y=up, Z=backwards. + // The vectors inside the mesh are in NDC to keep the shader simple: X=right, Y=down, Z=forwards. + // So we need to perform a similarity transform on this delta matrix. + // The verbose code would look like this: + /* + Matrix4f matBasisChange; + matBasisChange.SetIdentity(); + matBasisChange.M[0][0] = 1.0f; + matBasisChange.M[1][1] = -1.0f; + matBasisChange.M[2][2] = -1.0f; + Matrix4f matBasisChangeInv = matBasisChange.Inverted(); + matRenderFromNow = matBasisChangeInv * matRenderFromNow * matBasisChange; + */ + // ...but of course all the above is a constant transform and much more easily done. + // We flip the signs of the Y&Z row, then flip the signs of the Y&Z column, + // and of course most of the flips cancel: + // +++ +-- +-- + // +++ -> flip Y&Z columns -> +-- -> flip Y&Z rows -> -++ + // +++ +-- -++ + timewarpStart.M[0][1] = -timewarpStart.M[0][1]; + timewarpStart.M[0][2] = -timewarpStart.M[0][2]; + timewarpStart.M[1][0] = -timewarpStart.M[1][0]; + timewarpStart.M[2][0] = -timewarpStart.M[2][0]; + + timewarpEnd .M[0][1] = -timewarpEnd .M[0][1]; + timewarpEnd .M[0][2] = -timewarpEnd .M[0][2]; + timewarpEnd .M[1][0] = -timewarpEnd .M[1][0]; + timewarpEnd .M[2][0] = -timewarpEnd .M[2][0]; + + twmOut[0] = timewarpStart; + twmOut[1] = timewarpEnd; +} + + +// Used by renderer to determine if it should time distortion rendering. +bool FrameTimeManager::NeedDistortionTimeMeasurement() const +{ + if (!VsyncEnabled) + return false; + return DistortionRenderTimes.GetCount() < 10; +} + + +void FrameTimeManager::AddDistortionTimeMeasurement(double distortionTimeSeconds) +{ + DistortionRenderTimes.AddTimeDelta(distortionTimeSeconds); + + // If timewarp timing changes based on this sample, update it. + double newTimewarpWaitDelta = calcTimewarpWaitDelta(); + if (newTimewarpWaitDelta != FrameTiming.Inputs.TimewarpWaitDelta) + { + FrameTiming.Inputs.TimewarpWaitDelta = newTimewarpWaitDelta; + LocklessTiming.SetState(FrameTiming); + } +} + + +void FrameTimeManager::UpdateFrameLatencyTrackingAfterEndFrame( + unsigned char frameLatencyTestColor, + const Util::FrameTimeRecordSet& rs) +{ + // FrameTiming.NextFrameTime in this context (after EndFrame) is the end frame time. + ScreenLatencyTracker.SaveDrawColor(frameLatencyTestColor, + FrameTiming.NextFrameTime, + RenderIMUTimeSeconds, + TimewarpIMUTimeSeconds); + + ScreenLatencyTracker.MatchRecord(rs); + + // If screen delay changed, update timing. + double newScreenDelay = calcScreenDelay(); + if (newScreenDelay != FrameTiming.Inputs.ScreenDelay) + { + FrameTiming.Inputs.ScreenDelay = newScreenDelay; + LocklessTiming.SetState(FrameTiming); + } +} + + +//----------------------------------------------------------------------------------- +// ***** TimeDeltaCollector + +void TimeDeltaCollector::AddTimeDelta(double timeSeconds) +{ + // avoid adding invalid timing values + if(timeSeconds < 0.0f) + return; + + if (Count == Capacity) + { + for(int i=0; i< Count-1; i++) + TimeBufferSeconds[i] = TimeBufferSeconds[i+1]; + Count--; + } + TimeBufferSeconds[Count++] = timeSeconds; +} + +double TimeDeltaCollector::GetMedianTimeDelta() const +{ + double SortedList[Capacity]; + bool used[Capacity]; + + memset(used, 0, sizeof(used)); + SortedList[0] = 0.0; // In case Count was 0... + + // Probably the slowest way to find median... + for (int i=0; i<Count; i++) + { + double smallestDelta = 1000000.0; + int index = 0; + + for (int j = 0; j < Count; j++) + { + if (!used[j]) + { + if (TimeBufferSeconds[j] < smallestDelta) + { + smallestDelta = TimeBufferSeconds[j]; + index = j; + } + } + } + + // Mark as used + used[index] = true; + SortedList[i] = smallestDelta; + } + + return SortedList[Count/2]; +} + + +}} // namespace OVR::CAPI + diff --git a/LibOVR/Src/CAPI/CAPI_FrameTimeManager.h b/LibOVR/Src/CAPI/CAPI_FrameTimeManager.h new file mode 100644 index 0000000..4693697 --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_FrameTimeManager.h @@ -0,0 +1,264 @@ +/************************************************************************************ + +Filename : CAPI_FrameTimeManager.h +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.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. + +************************************************************************************/ + +#ifndef OVR_CAPI_FrameTimeManager_h +#define OVR_CAPI_FrameTimeManager_h + +#include "../OVR_CAPI.h" +#include "../Kernel/OVR_Timer.h" +#include "../Kernel/OVR_Math.h" +#include "../Util/Util_Render_Stereo.h" +#include "../Util/Util_LatencyTest2.h" + +namespace OVR { namespace CAPI { + +//------------------------------------------------------------------------------------- + +// Helper class to collect median times between frames, so that we know +// how long to wait. +struct TimeDeltaCollector +{ + TimeDeltaCollector() : Count(0) { } + + void AddTimeDelta(double timeSeconds); + void Clear() { Count = 0; } + + double GetMedianTimeDelta() const; + + double GetCount() const { return Count; } + + enum { Capacity = 12 }; +private: + int Count; + double TimeBufferSeconds[Capacity]; +}; + + +//------------------------------------------------------------------------------------- +// ***** FrameLatencyTracker + +// FrameLatencyTracker tracks frame Present to display Scan-out timing, as reported by +// the DK2 internal latency tester pixel read-back. The computed value is used in +// FrameTimeManager for prediction. View Render and TimeWarp to scan-out latencies are +// also reported for debugging. +// +// The class operates by generating color values from GetNextDrawColor() that must +// be rendered on the back end and then looking for matching values in FrameTimeRecordSet +// structure as reported by HW. + +class FrameLatencyTracker +{ +public: + + enum { FramesTracked = Util::LT2_IncrementCount-1 }; + + FrameLatencyTracker(); + + // DrawColor == 0 is special in that it doesn't need saving of timestamp + unsigned char GetNextDrawColor(); + + void SaveDrawColor(unsigned char drawColor, double endFrameTime, + double renderIMUTime, double timewarpIMUTime ); + + void MatchRecord(const Util::FrameTimeRecordSet &r); + + void GetLatencyTimings(float latencies[3]); + + void Reset(); + +public: + + struct FrameTimeRecordEx : public Util::FrameTimeRecord + { + bool MatchedRecord; + double RenderIMUTimeSeconds; + double TimewarpIMUTimeSeconds; + }; + + // True if rendering read-back is enabled. + bool TrackerEnabled; + + enum SampleWaitType { + SampleWait_Zeroes, // We are waiting for a record with all zeros. + SampleWait_Match // We are issuing & matching colors. + }; + + SampleWaitType WaitMode; + int MatchCount; + // Records of frame timings that we are trying to measure. + FrameTimeRecordEx FrameEndTimes[FramesTracked]; + int FrameIndex; + // Median filter for (ScanoutTimeSeconds - PostPresent frame time) + TimeDeltaCollector FrameDeltas; + // Latency reporting results + double RenderLatencySeconds; + double TimewarpLatencySeconds; + double LatencyRecordTime; +}; + + + +//------------------------------------------------------------------------------------- +// ***** FrameTimeManager + +// FrameTimeManager keeps track of rendered frame timing and handles predictions for +// orientations and time-warp. + +class FrameTimeManager +{ +public: + FrameTimeManager(bool vsyncEnabled = true); + + // Data that affects frame timing computation. + struct TimingInputs + { + // Hard-coded value or dynamic as reported by FrameTimeDeltas.GetMedianTimeDelta(). + double FrameDelta; + // Screen delay from present to scan-out, as potentially reported by ScreenLatencyTracker. + double ScreenDelay; + // Negative value of how many seconds before EndFrame we start timewarp. 0.0 if not used. + double TimewarpWaitDelta; + + TimingInputs() + : FrameDelta(0), ScreenDelay(0), TimewarpWaitDelta(0) + { } + }; + + // Timing values for a specific frame. + struct Timing + { + TimingInputs Inputs; + + // Index of a frame that started at ThisFrameTime. + unsigned int FrameIndex; + // Predicted absolute times for when this frame will show up on screen. + // Generally, all values will be >= NextFrameTime, since that's the time we expect next + // vsync to succeed. + double ThisFrameTime; + double TimewarpPointTime; + double NextFrameTime; + double MidpointTime; + double EyeRenderTimes[2]; + double TimeWarpStartEndTimes[2][2]; + + Timing() + { + memset(this, 0, sizeof(Timing)); + } + + void InitTimingFromInputs(const TimingInputs& inputs, HmdShutterTypeEnum shutterType, + double thisFrameTime, unsigned int frameIndex); + }; + + + // Called on startup to provided data on HMD timing. + void Init(HmdRenderInfo& renderInfo); + + // Called with each new ConfigureRendering. + void ResetFrameTiming(unsigned frameIndex, + bool dynamicPrediction, bool sdkRender); + + void SetVsync(bool enabled) { VsyncEnabled = enabled; } + + // BeginFrame returns time of the call + // TBD: Should this be a predicted time value instead ? + double BeginFrame(unsigned frameIndex); + void EndFrame(); + + // Thread-safe function to query timing for a future frame + Timing GetFrameTiming(unsigned frameIndex); + + double GetEyePredictionTime(ovrEyeType eye); + Transformf GetEyePredictionPose(ovrHmd hmd, ovrEyeType eye); + + void GetTimewarpPredictions(ovrEyeType eye, double timewarpStartEnd[2]); + void GetTimewarpMatrices(ovrHmd hmd, ovrEyeType eye, ovrPosef renderPose, ovrMatrix4f twmOut[2]); + + // Used by renderer to determine if it should time distortion rendering. + bool NeedDistortionTimeMeasurement() const; + void AddDistortionTimeMeasurement(double distortionTimeSeconds); + + + // DK2 Lateny test interface + + // Get next draw color for DK2 latency tester + unsigned char GetFrameLatencyTestDrawColor() + { return ScreenLatencyTracker.GetNextDrawColor(); } + + // Must be called after EndFrame() to update latency tester timings. + // Must pass color reported by NextFrameColor for this frame. + void UpdateFrameLatencyTrackingAfterEndFrame(unsigned char frameLatencyTestColor, + const Util::FrameTimeRecordSet& rs); + + void GetLatencyTimings(float latencies[3]) + { return ScreenLatencyTracker.GetLatencyTimings(latencies); } + + + const Timing& GetFrameTiming() const { return FrameTiming; } + +private: + + double calcFrameDelta() const; + double calcScreenDelay() const; + double calcTimewarpWaitDelta() const; + + + HmdRenderInfo RenderInfo; + // Timings are collected through a median filter, to avoid outliers. + TimeDeltaCollector FrameTimeDeltas; + TimeDeltaCollector DistortionRenderTimes; + FrameLatencyTracker ScreenLatencyTracker; + + // Timing changes if we have no Vsync (all prediction is reduced to fixed interval). + bool VsyncEnabled; + // Set if we are rendering via the SDK, so DistortionRenderTimes is valid. + bool DynamicPrediction; + // Set if SDk is doing teh rendering. + bool SdkRender; + + // Total frame delay due to VsyncToFirstScanline, persistence and settle time. + // Computed from RenderInfor.Shutter. + double VSyncToScanoutDelay; + double NoVSyncToScanoutDelay; + double ScreenSwitchingDelay; + + // Current (or last) frame timing info. Used as a source for LocklessTiming. + Timing FrameTiming; + // TBD: Don't we need NextFrame here as well? + LocklessUpdater<Timing> LocklessTiming; + + + // IMU Read timings + double RenderIMUTimeSeconds; + double TimewarpIMUTimeSeconds; +}; + + +}} // namespace OVR::CAPI + +#endif // OVR_CAPI_FrameTimeManager_h + + diff --git a/LibOVR/Src/CAPI/CAPI_GlobalState.cpp b/LibOVR/Src/CAPI/CAPI_GlobalState.cpp new file mode 100644 index 0000000..2ed1794 --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_GlobalState.cpp @@ -0,0 +1,142 @@ +/************************************************************************************ + +Filename : CAPI_GlobalState.cpp +Content : Maintains global state of the CAPI +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_GlobalState.h" + +namespace OVR { namespace CAPI { + + +//------------------------------------------------------------------------------------- +// Open Questions / Notes + +// 2. Detect HMDs. +// Challenge: If we do everything through polling, it would imply we want all the devices +// initialized. However, there may be multiple rifts, extra sensors, etc, +// which shouldn't be allocated. +// + +// How do you reset orientation Quaternion? +// Can you change IPD? + + + +//------------------------------------------------------------------------------------- +// ***** OVRGlobalState + +// Global instance +GlobalState* GlobalState::pInstance = 0; + + +GlobalState::GlobalState() +{ + pManager = *DeviceManager::Create(); + // Handle the DeviceManager's messages + pManager->AddMessageHandler( this ); + EnumerateDevices(); + + // PhoneSensors::Init(); +} + +GlobalState::~GlobalState() +{ + RemoveHandlerFromDevices(); + OVR_ASSERT(HMDs.IsEmpty()); +} + +int GlobalState::EnumerateDevices() +{ + // Need to use separate lock for device enumeration, as pManager->GetHandlerLock() + // would produce deadlocks here. + Lock::Locker lock(&EnumerationLock); + + EnumeratedDevices.Clear(); + + DeviceEnumerator<HMDDevice> e = pManager->EnumerateDevices<HMDDevice>(); + while(e.IsAvailable()) + { + EnumeratedDevices.PushBack(DeviceHandle(e)); + e.Next(); + } + + return (int)EnumeratedDevices.GetSize(); +} + + +HMDDevice* GlobalState::CreateDevice(int index) +{ + Lock::Locker lock(&EnumerationLock); + + if (index >= (int)EnumeratedDevices.GetSize()) + return 0; + return EnumeratedDevices[index].CreateDeviceTyped<HMDDevice>(); +} + + +void GlobalState::AddHMD(HMDState* hmd) +{ + Lock::Locker lock(pManager->GetHandlerLock()); + HMDs.PushBack(hmd); +} +void GlobalState::RemoveHMD(HMDState* hmd) +{ + Lock::Locker lock(pManager->GetHandlerLock()); + hmd->RemoveNode(); +} + +void GlobalState::NotifyHMDs_AddDevice(DeviceType deviceType) +{ + Lock::Locker lock(pManager->GetHandlerLock()); + for(HMDState* hmd = HMDs.GetFirst(); !HMDs.IsNull(hmd); hmd = hmd->pNext) + hmd->NotifyAddDevice(deviceType); +} + +void GlobalState::OnMessage(const Message& msg) +{ + if (msg.Type == Message_DeviceAdded || msg.Type == Message_DeviceRemoved) + { + if (msg.pDevice == pManager) + { + const MessageDeviceStatus& statusMsg = + static_cast<const MessageDeviceStatus&>(msg); + + if (msg.Type == Message_DeviceAdded) + { + //LogText("OnMessage DeviceAdded.\n"); + + // We may have added a sensor/other device; notify any HMDs that might + // need it to check for it later. + NotifyHMDs_AddDevice(statusMsg.Handle.GetType()); + } + else + { + //LogText("OnMessage DeviceRemoved.\n"); + } + } + } +} + + +}} // namespace OVR::CAPI diff --git a/LibOVR/Src/CAPI/CAPI_GlobalState.h b/LibOVR/Src/CAPI/CAPI_GlobalState.h new file mode 100644 index 0000000..54ab8cc --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_GlobalState.h @@ -0,0 +1,84 @@ +/************************************************************************************ + +Filename : CAPI_GlobalState.h +Content : Maintains global state of the CAPI +Created : January 24, 2013 +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. + +************************************************************************************/ + +#ifndef OVR_CAPI_GlobalState_h +#define OVR_CAPI_GlobalState_h + +#include "../OVR_CAPI.h" +#include "../OVR_Device.h" +#include "../Kernel/OVR_Timer.h" +#include "../Kernel/OVR_Math.h" + +#include "CAPI_HMDState.h" + +namespace OVR { namespace CAPI { + +//------------------------------------------------------------------------------------- +// ***** OVRGlobalState + +// Global DeviceManager state - singleton instance of this is created +// by ovr_Initialize(). +class GlobalState : public MessageHandler, public NewOverrideBase +{ +public: + GlobalState(); + ~GlobalState(); + + static GlobalState *pInstance; + + int EnumerateDevices(); + HMDDevice* CreateDevice(int index); + + // MessageHandler implementation + void OnMessage(const Message& msg); + + // Helpers used to keep track of HMDs and notify them of sensor changes. + void AddHMD(HMDState* hmd); + void RemoveHMD(HMDState* hmd); + void NotifyHMDs_AddDevice(DeviceType deviceType); + + const char* GetLastError() + { + return 0; + } + + DeviceManager* GetManager() { return pManager; } + +protected: + + Ptr<DeviceManager> pManager; + Lock EnumerationLock; + Array<DeviceHandle> EnumeratedDevices; + + // Currently created hmds; protected by Manager lock. + List<HMDState> HMDs; +}; + +}} // namespace OVR::CAPI + +#endif + + diff --git a/LibOVR/Src/CAPI/CAPI_HMDRenderState.cpp b/LibOVR/Src/CAPI/CAPI_HMDRenderState.cpp new file mode 100644 index 0000000..00bdea2 --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_HMDRenderState.cpp @@ -0,0 +1,143 @@ +/************************************************************************************ + +Filename : OVR_CAPI_HMDRenderState.cpp +Content : Combines all of the rendering state associated with the HMD +Created : February 2, 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_HMDRenderState.h" + +namespace OVR { namespace CAPI { + + +//------------------------------------------------------------------------------------- +// ***** HMDRenderState + + +HMDRenderState::HMDRenderState(ovrHmd hmd, Profile* userProfile, const OVR::HMDInfo& hmdInfo) + : HMD(hmd), HMDInfo(hmdInfo) +{ + RenderInfo = GenerateHmdRenderInfoFromHmdInfo( HMDInfo, userProfile ); + + Distortion[0] = CalculateDistortionRenderDesc(StereoEye_Left, RenderInfo, 0); + Distortion[1] = CalculateDistortionRenderDesc(StereoEye_Right, RenderInfo, 0); + + ClearColor[0] = ClearColor[1] = ClearColor[2] = ClearColor[3] =0.0f; + + EnabledHmdCaps = 0; +} + +HMDRenderState::~HMDRenderState() +{ +} + +ovrHmdDesc HMDRenderState::GetDesc() +{ + ovrHmdDesc d; + memset(&d, 0, sizeof(d)); + + d.Type = ovrHmd_Other; + + d.ProductName = HMDInfo.ProductName; + d.Manufacturer = HMDInfo.Manufacturer; + d.Resolution.w = HMDInfo.ResolutionInPixels.w; + d.Resolution.h = HMDInfo.ResolutionInPixels.h; + d.WindowsPos.x = HMDInfo.DesktopX; + d.WindowsPos.y = HMDInfo.DesktopY; + d.DisplayDeviceName = HMDInfo.DisplayDeviceName; + d.DisplayId = HMDInfo.DisplayId; + + d.HmdCaps = ovrHmdCap_Present | ovrHmdCap_NoVSync; + d.SensorCaps = ovrSensorCap_YawCorrection | ovrSensorCap_Orientation; + d.DistortionCaps = ovrDistortionCap_Chromatic | ovrDistortionCap_TimeWarp | ovrDistortionCap_Vignette; + + if (strstr(HMDInfo.ProductName, "DK1")) + { + d.Type = ovrHmd_DK1; + } + else if (strstr(HMDInfo.ProductName, "DK2")) + { + d.Type = ovrHmd_DK2; + d.HmdCaps |= ovrHmdCap_LowPersistence | + ovrHmdCap_LatencyTest | ovrHmdCap_DynamicPrediction; + d.SensorCaps |= ovrSensorCap_Position; + } + + DistortionRenderDesc& leftDistortion = Distortion[0]; + DistortionRenderDesc& rightDistortion = Distortion[1]; + + // The suggested FOV (assuming eye rotation) + d.DefaultEyeFov[0] = CalculateFovFromHmdInfo(StereoEye_Left, leftDistortion, RenderInfo, OVR_DEFAULT_EXTRA_EYE_ROTATION); + d.DefaultEyeFov[1] = CalculateFovFromHmdInfo(StereoEye_Right, rightDistortion, RenderInfo, OVR_DEFAULT_EXTRA_EYE_ROTATION); + + // FOV extended across the entire screen + d.MaxEyeFov[0] = GetPhysicalScreenFov(StereoEye_Left, leftDistortion); + d.MaxEyeFov[1] = GetPhysicalScreenFov(StereoEye_Right, rightDistortion); + + if (HMDInfo.Shutter.Type == HmdShutter_RollingRightToLeft) + { + d.EyeRenderOrder[0] = ovrEye_Right; + d.EyeRenderOrder[1] = ovrEye_Left; + } + else + { + d.EyeRenderOrder[0] = ovrEye_Left; + d.EyeRenderOrder[1] = ovrEye_Right; + } + + return d; +} + + +ovrSizei HMDRenderState::GetFOVTextureSize(int eye, ovrFovPort fov, float pixelsPerDisplayPixel) +{ + OVR_ASSERT((unsigned)eye < 2); + StereoEye seye = (eye == ovrEye_Left) ? StereoEye_Left : StereoEye_Right; + return CalculateIdealPixelSize(seye, Distortion[eye], fov, pixelsPerDisplayPixel); +} + +ovrEyeRenderDesc HMDRenderState::calcRenderDesc(ovrEyeType eyeType, const ovrFovPort& fov) +{ + HmdRenderInfo& hmdri = RenderInfo; + StereoEye eye = (eyeType == ovrEye_Left) ? StereoEye_Left : StereoEye_Right; + ovrEyeRenderDesc e0; + + e0.Eye = eyeType; + e0.Fov = fov; + e0.ViewAdjust = CalculateEyeVirtualCameraOffset(hmdri, eye, false); + e0.DistortedViewport = GetFramebufferViewport(eye, hmdri); + e0.PixelsPerTanAngleAtCenter = Distortion[0].PixelsPerTanAngleAtCenter; + + return e0; +} + + +void HMDRenderState::setupRenderDesc( ovrEyeRenderDesc eyeRenderDescOut[2], + const ovrFovPort eyeFovIn[2] ) +{ + eyeRenderDescOut[0] = EyeRenderDesc[0] = calcRenderDesc(ovrEye_Left, eyeFovIn[0]); + eyeRenderDescOut[1] = EyeRenderDesc[1] = calcRenderDesc(ovrEye_Right, eyeFovIn[1]); +} + + +}} // namespace OVR::CAPI + diff --git a/LibOVR/Src/CAPI/CAPI_HMDRenderState.h b/LibOVR/Src/CAPI/CAPI_HMDRenderState.h new file mode 100644 index 0000000..a4e8d21 --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_HMDRenderState.h @@ -0,0 +1,93 @@ +/************************************************************************************ + +Filename : CAPI_HMDRenderState.h +Content : Combines all of the rendering state associated with the HMD +Created : February 2, 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. + +************************************************************************************/ + +#ifndef OVR_CAPI_HMDRenderState_h +#define OVR_CAPI_HMDRenderState_h + +#include "../OVR_CAPI.h" +#include "../Kernel/OVR_Math.h" +#include "../Util/Util_Render_Stereo.h" + + +namespace OVR { namespace CAPI { + +using namespace OVR::Util::Render; + +//------------------------------------------------------------------------------------- +// ***** HMDRenderState + +// Combines all of the rendering setup information about one HMD. + +class HMDRenderState : public NewOverrideBase +{ + // Quiet assignment compiler warning. + void operator = (const HMDRenderState&) { } +public: + + HMDRenderState(ovrHmd hmd, Profile* userProfile, const OVR::HMDInfo& hmdInfo); + virtual ~HMDRenderState(); + + + // *** Rendering Setup + + // Delegated access APIs + ovrHmdDesc GetDesc(); + ovrSizei GetFOVTextureSize(int eye, ovrFovPort fov, float pixelsPerDisplayPixel); + + ovrEyeRenderDesc calcRenderDesc(ovrEyeType eyeType, const ovrFovPort& fov); + + void setupRenderDesc(ovrEyeRenderDesc eyeRenderDescOut[2], + const ovrFovPort eyeFovIn[2]); +public: + + // HMDInfo shouldn't change, as its string pointers are passed out. + ovrHmd HMD; + const OVR::HMDInfo& HMDInfo; + + //const char* pLastError; + + HmdRenderInfo RenderInfo; + DistortionRenderDesc Distortion[2]; + ovrEyeRenderDesc EyeRenderDesc[2]; + + // Clear color used for distortion + float ClearColor[4]; + + // Pose at which last time the eye was rendered, as submitted by EndEyeRender. + ovrPosef EyeRenderPoses[2]; + + // Capabilities passed to Configure. + unsigned EnabledHmdCaps; + unsigned DistortionCaps; +}; + + +}} // namespace OVR::CAPI + + +#endif // OVR_CAPI_HMDState_h + + 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 + diff --git a/LibOVR/Src/CAPI/CAPI_HMDState.h b/LibOVR/Src/CAPI/CAPI_HMDState.h new file mode 100644 index 0000000..0a1466f --- /dev/null +++ b/LibOVR/Src/CAPI/CAPI_HMDState.h @@ -0,0 +1,347 @@ +/************************************************************************************ + +Filename : CAPI_HMDState.h +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. + +************************************************************************************/ + +#ifndef OVR_CAPI_HMDState_h +#define OVR_CAPI_HMDState_h + +#include "../Kernel/OVR_Math.h" +#include "../Kernel/OVR_List.h" +#include "../Kernel/OVR_Log.h" +#include "../OVR_CAPI.h" +#include "../OVR_SensorFusion.h" +#include "../Util/Util_LatencyTest.h" +#include "../Util/Util_LatencyTest2.h" + +#include "CAPI_FrameTimeManager.h" +#include "CAPI_HMDRenderState.h" +#include "CAPI_DistortionRenderer.h" + +// Define OVR_CAPI_VISIONSUPPORT to compile in vision support +#ifdef OVR_CAPI_VISIONSUPPORT + #define OVR_CAPI_VISION_CODE(c) c + #include "../Vision/Vision_PoseTracker.h" +#else + #define OVR_CAPI_VISION_CODE(c) +#endif + + +struct ovrHmdStruct { }; + +namespace OVR { namespace CAPI { + +using namespace OVR::Util::Render; + + +//------------------------------------------------------------------------------------- +// ***** ThreadChecker + +// This helper class is used to verify that the API is used according to supported +// thread safety constraints (is not re-entrant for this and related functions). +class ThreadChecker +{ +public: + +#ifndef OVR_BUILD_DEBUG + + // In release build, thread checks are disabled. + ThreadChecker() { } + void Begin(const char* functionName) { OVR_UNUSED1(functionName); } + void End() { } + + // Add thread-re-entrancy check for function scope + struct Scope + { + Scope(ThreadChecker*, const char *) { } + ~Scope() { } + }; + + +#else // OVR_BUILD_DEBUG + ThreadChecker() : pFunctionName(0), FirstThread(0) + { } + + void Begin(const char* functionName) + { + if (!pFunctionName) + { + pFunctionName = functionName; + FirstThread = GetCurrentThreadId(); + } + else + { + // pFunctionName may be not null here if function is called internally on the same thread. + OVR_ASSERT_LOG((FirstThread == GetCurrentThreadId()), + ("%s (threadId=%p) called at the same times as %s (threadId=%p)\n", + functionName, GetCurrentThreadId(), pFunctionName, FirstThread) ); + } + } + void End() + { + pFunctionName = 0; + FirstThread = 0; + } + + // Add thread-re-entrancy check for function scope. + struct Scope + { + Scope(ThreadChecker* threadChecker, const char *functionName) : pChecker(threadChecker) + { pChecker->Begin(functionName); } + ~Scope() + { pChecker->End(); } + private: + ThreadChecker* pChecker; + }; + +private: + // If not 0, contains the name of the function that first entered the scope. + const char * pFunctionName; + ThreadId FirstThread; + +#endif // OVR_BUILD_DEBUG +}; + + +//------------------------------------------------------------------------------------- +// ***** HMDState + +// Describes a single HMD. +class HMDState : public ListNode<HMDState>, + public ovrHmdStruct, public NewOverrideBase +{ +public: + + HMDState(HMDDevice* device); + HMDState(ovrHmdType hmdType); + virtual ~HMDState(); + + + // *** Sensor Setup + + bool StartSensor(unsigned supportedCaps, unsigned requiredCaps); + void StopSensor(); + void ResetSensor(); + ovrSensorState PredictedSensorState(double absTime); + bool GetSensorDesc(ovrSensorDesc* descOut); + + // Changes HMD Caps. + // Capability bits that are not directly or logically tied to one system (such as sensor) + // are grouped here. ovrHmdCap_VSync, for example, affects rendering and timing. + void SetEnabledHmdCaps(unsigned caps); + + + bool ProcessLatencyTest(unsigned char rgbColorOut[3]); + void ProcessLatencyTest2(unsigned char rgbColorOut[3], double startTime); + + + // *** Rendering Setup + + bool ConfigureRendering(ovrEyeRenderDesc eyeRenderDescOut[2], + const ovrFovPort eyeFovIn[2], + const ovrRenderAPIConfig* apiConfig, + unsigned distortionCaps); + + ovrPosef BeginEyeRender(ovrEyeType eye); + void EndEyeRender(ovrEyeType eye, ovrPosef renderPose, ovrTexture* eyeTexture); + + + const char* GetLastError() + { + const char* p = pLastError; + pLastError = 0; + return p; + } + + void NotifyAddDevice(DeviceType deviceType) + { + if (deviceType == Device_Sensor) + AddSensorCount++; + else if (deviceType == Device_LatencyTester) + { + AddLatencyTestCount++; + AddLatencyTestDisplayCount++; + } + } + + bool checkCreateSensor(); + + void applyProfileToSensorFusion(); + + // INlines so that they can be easily compiled out. + // Does debug ASSERT checks for functions that require BeginFrame. + // Also verifies that we are on the right thread. + void checkBeginFrameScope(const char* functionName) + { + OVR_UNUSED1(functionName); // for Release build. + OVR_ASSERT_LOG(BeginFrameCalled == true, + ("%s called outside ovrHmd_BeginFrame.", functionName)); + OVR_ASSERT_LOG(BeginFrameThreadId == OVR::GetCurrentThreadId(), + ("%s called on a different thread then ovrHmd_BeginFrame.", functionName)); + } + + void checkRenderingConfigured(const char* functionName) + { + OVR_UNUSED1(functionName); // for Release build. + OVR_ASSERT_LOG(RenderingConfigured == true, + ("%s called without ovrHmd_ConfigureRendering.", functionName)); + } + + void checkBeginFrameTimingScope(const char* functionName) + { + OVR_UNUSED1(functionName); // for Release build. + OVR_ASSERT_LOG(BeginFrameTimingCalled == true, + ("%s called outside ovrHmd_BeginFrameTiming.", functionName)); + } + + + HMDState* getThis() { return this; } + + void updateLowPersistenceMode(bool lowPersistence) const; + void updateLatencyTestForHmd(bool latencyTesting); + + void updateDK2FeaturesTiedToSensor(bool sensorCreatedJustNow); + + // Get properties by name. + float getFloatValue(const char* propertyName, float defaultVal); + bool setFloatValue(const char* propertyName, float value); + unsigned getFloatArray(const char* propertyName, float values[], unsigned arraySize); + bool setFloatArray(const char* propertyName, float values[], unsigned arraySize); + const char* getString(const char* propertyName, const char* defaultVal); +public: + + // Wrapper to support 'const' + struct HMDInfoWrapper + { + HMDInfoWrapper(ovrHmdType hmdType) + { + HmdTypeEnum t = HmdType_None; + if (hmdType == ovrHmd_DK1) + t = HmdType_DK1; + else if (hmdType == ovrHmd_CrystalCoveProto) + t = HmdType_CrystalCoveProto; + else if (hmdType == ovrHmd_DK2) + t = HmdType_DK2; + h = CreateDebugHMDInfo(t); + } + HMDInfoWrapper(HMDDevice* device) { if (device) device->GetDeviceInfo(&h); } + OVR::HMDInfo h; + }; + + // Note: pHMD can be null if we are representing a virtualized debug HMD. + Ptr<HMDDevice> pHMD; + + // HMDInfo shouldn't change, as its string pointers are passed out. + const HMDInfoWrapper HMDInfoW; + const OVR::HMDInfo& HMDInfo; + + const char* pLastError; + + // Caps enabled for the HMD. + unsigned EnabledHmdCaps; + // These are the flags actually applied to the Sensor device, + // used to track whether SetDisplayReport calls are necessary. + unsigned HmdCapsAppliedToSensor; + + + // *** Sensor + + // Lock used to support thread-safe lifetime access to sensor. + Lock DevicesLock; + + // Atomic integer used as a flag that we should check the sensor device. + AtomicInt<int> AddSensorCount; + + // All of Sensor variables may be modified/used with DevicesLock, with exception that + // the {SensorStarted, SensorCreated} can be read outside the lock to see + // if device creation check is necessary. + // Whether we called StartSensor() and requested sensor caps. + volatile bool SensorStarted; + volatile bool SensorCreated; + // pSensor may still be null or non-running after start if it wasn't yet available + Ptr<SensorDevice> pSensor; // Head + unsigned SensorCaps; + + // SensorFusion state may be accessible without a lock. + SensorFusion SFusion; + + + // Vision pose tracker is currently new-allocated + OVR_CAPI_VISION_CODE( + Vision::PoseTracker* pPoseTracker; + ) + + // Latency tester + Ptr<LatencyTestDevice> pLatencyTester; + Util::LatencyTest LatencyUtil; + AtomicInt<int> AddLatencyTestCount; + + bool LatencyTestActive; + unsigned char LatencyTestDrawColor[3]; + + // Using latency tester as debug display + Ptr<LatencyTestDevice> pLatencyTesterDisplay; + AtomicInt<int> AddLatencyTestDisplayCount; + Util::LatencyTest2 LatencyUtil2; + + bool LatencyTest2Active; + unsigned char LatencyTest2DrawColor[3]; + //bool ReadbackColor; + + // Rendering part + FrameTimeManager TimeManager; + HMDRenderState RenderState; + Ptr<DistortionRenderer> pRenderer; + + // Last timing value reported by BeginFrame. + double LastFrameTimeSeconds; + // Last timing value reported by GetFrameTime. These are separate since the intended + // use is from different threads. TBD: Move to FrameTimeManager? Make atomic? + double LastGetFrameTimeSeconds; + + // Last cached value returned by ovrHmd_GetString/ovrHmd_GetStringArray. + char LastGetStringValue[256]; + + + // Debug flag set after ovrHmd_ConfigureRendering succeeds. + bool RenderingConfigured; + // Set after BeginFrame succeeds, and its corresponding thread id for debug checks. + bool BeginFrameCalled; + ThreadId BeginFrameThreadId; + // Graphics functions are not re-entrant from other threads. + ThreadChecker RenderAPIThreadChecker; + // + bool BeginFrameTimingCalled; + + // Flags set when we've called BeginEyeRender on a given eye. + bool EyeRenderActive[2]; +}; + + +}} // namespace OVR::CAPI + + +#endif // OVR_CAPI_HMDState_h + + diff --git a/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionRenderer.cpp b/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionRenderer.cpp new file mode 100644 index 0000000..21b6509 --- /dev/null +++ b/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionRenderer.cpp @@ -0,0 +1,784 @@ +/************************************************************************************ + +Filename : CAPI_GL_DistortionRenderer.h +Content : Distortion renderer header for GL +Created : November 11, 2013 +Authors : David Borel, Lee Cooper + +Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. + +Use of this software is subject to the terms of the Oculus Inc license +agreement provided at the time of installation or download, or which +otherwise accompanies this software in either electronic or hard copy form. + +************************************************************************************/ + +#include "CAPI_GL_DistortionRenderer.h" + +#include "CAPI_GL_DistortionShaders.h" + +#include "../../OVR_CAPI_GL.h" + +namespace OVR { namespace CAPI { namespace GL { + +// Distortion pixel shader lookup. +// Bit 0: Chroma Correction +// Bit 1: Timewarp + +enum { + DistortionVertexShaderBitMask = 3, + DistortionVertexShaderCount = DistortionVertexShaderBitMask + 1, + DistortionPixelShaderBitMask = 1, + DistortionPixelShaderCount = DistortionPixelShaderBitMask + 1 +}; + +struct ShaderInfo +{ + const char* ShaderData; + size_t ShaderSize; + const ShaderBase::Uniform* ReflectionData; + size_t ReflectionSize; +}; + +// Do add a new distortion shader use these macros (with or w/o reflection) +#define SI_NOREFL(shader) { shader, sizeof(shader), NULL, 0 } +#define SI_REFL__(shader) { shader, sizeof(shader), shader ## _refl, sizeof( shader ## _refl )/sizeof(*(shader ## _refl)) } + + +static ShaderInfo DistortionVertexShaderLookup[DistortionVertexShaderCount] = +{ + SI_REFL__(Distortion_vs), + SI_REFL__(DistortionChroma_vs), + SI_REFL__(DistortionTimewarp_vs), + SI_REFL__(DistortionTimewarpChroma_vs) +}; + +static ShaderInfo DistortionPixelShaderLookup[DistortionPixelShaderCount] = +{ + SI_NOREFL(Distortion_fs), + SI_NOREFL(DistortionChroma_fs) +}; + +void DistortionShaderBitIndexCheck() +{ + OVR_COMPILER_ASSERT(ovrDistortionCap_Chromatic == 1); + OVR_COMPILER_ASSERT(ovrDistortionCap_TimeWarp == 2); +} + + + +struct DistortionVertex +{ + Vector2f Pos; + Vector2f TexR; + Vector2f TexG; + Vector2f TexB; + Color Col; +}; + + +// Vertex type; same format is used for all shapes for simplicity. +// Shapes are built by adding vertices to Model. +struct LatencyVertex +{ + Vector3f Pos; + LatencyVertex (const Vector3f& p) : Pos(p) {} +}; + + +//---------------------------------------------------------------------------- +// ***** GL::DistortionRenderer + +DistortionRenderer::DistortionRenderer(ovrHmd hmd, FrameTimeManager& timeManager, + const HMDRenderState& renderState) + : CAPI::DistortionRenderer(ovrRenderAPI_OpenGL, hmd, timeManager, renderState) + , LatencyVAO(0) +{ + DistortionMeshVAOs[0] = 0; + DistortionMeshVAOs[1] = 0; +} + +DistortionRenderer::~DistortionRenderer() +{ + destroy(); +} + +// static +CAPI::DistortionRenderer* DistortionRenderer::Create(ovrHmd hmd, + FrameTimeManager& timeManager, + const HMDRenderState& renderState) +{ +#if !defined(OVR_OS_MAC) + InitGLExtensions(); +#endif + return new DistortionRenderer(hmd, timeManager, renderState); +} + + +bool DistortionRenderer::Initialize(const ovrRenderAPIConfig* apiConfig, + unsigned distortionCaps) +{ + GfxState = *new GraphicsState(); + + const ovrGLConfig* config = (const ovrGLConfig*)apiConfig; + + if (!config) + { + // Cleanup + pEyeTextures[0].Clear(); + pEyeTextures[1].Clear(); + memset(&RParams, 0, sizeof(RParams)); + return true; + } + + RParams.Multisample = config->OGL.Header.Multisample; + RParams.RTSize = config->OGL.Header.RTSize; +#if defined(OVR_OS_WIN32) + RParams.Window = (config->OGL.Window) ? config->OGL.Window : GetActiveWindow(); +#elif defined(OVR_OS_LINUX) + RParams.Disp = (config->OGL.Disp) ? config->OGL.Disp : XOpenDisplay(NULL); + RParams.Win = config->OGL.Win; + if (!RParams.Win) + { + int unused; + XGetInputFocus(RParams.Disp, &RParams.Win, &unused); + } +#endif + + DistortionCaps = distortionCaps; + + //DistortionWarper.SetVsync((hmdCaps & ovrHmdCap_NoVSync) ? false : true); + + pEyeTextures[0] = *new Texture(&RParams, 0, 0); + pEyeTextures[1] = *new Texture(&RParams, 0, 0); + + initBuffersAndShaders(); + + return true; +} + + +void DistortionRenderer::SubmitEye(int eyeId, ovrTexture* eyeTexture) +{ + // Doesn't do a lot in here?? + const ovrGLTexture* tex = (const ovrGLTexture*)eyeTexture; + + // Write in values + eachEye[eyeId].texture = tex->OGL.TexId; + + if (tex) + { + // Its only at this point we discover what the viewport of the texture is. + // because presumably we allow users to realtime adjust the resolution. + eachEye[eyeId].TextureSize = tex->OGL.Header.TextureSize; + eachEye[eyeId].RenderViewport = tex->OGL.Header.RenderViewport; + + const ovrEyeRenderDesc& erd = RState.EyeRenderDesc[eyeId]; + + ovrHmd_GetRenderScaleAndOffset( erd.Fov, + eachEye[eyeId].TextureSize, eachEye[eyeId].RenderViewport, + eachEye[eyeId].UVScaleOffset ); + + pEyeTextures[eyeId]->UpdatePlaceholderTexture(tex->OGL.TexId, + tex->OGL.Header.TextureSize); + } +} + +void DistortionRenderer::EndFrame(bool swapBuffers, + unsigned char* latencyTesterDrawColor, unsigned char* latencyTester2DrawColor) +{ + if (!TimeManager.NeedDistortionTimeMeasurement()) + { + if (RState.DistortionCaps & ovrDistortionCap_TimeWarp) + { + // Wait for timewarp distortion if it is time and Gpu idle + FlushGpuAndWaitTillTime(TimeManager.GetFrameTiming().TimewarpPointTime); + } + + renderDistortion(pEyeTextures[0], pEyeTextures[1]); + } + else + { + // If needed, measure distortion time so that TimeManager can better estimate + // latency-reducing time-warp wait timing. + WaitUntilGpuIdle(); + double distortionStartTime = ovr_GetTimeInSeconds(); + + renderDistortion(pEyeTextures[0], pEyeTextures[1]); + + WaitUntilGpuIdle(); + TimeManager.AddDistortionTimeMeasurement(ovr_GetTimeInSeconds() - distortionStartTime); + } + + if(latencyTesterDrawColor) + { + renderLatencyQuad(latencyTesterDrawColor); + } + else if(latencyTester2DrawColor) + { + renderLatencyPixel(latencyTester2DrawColor); + } + + if (swapBuffers) + { + bool useVsync = ((RState.EnabledHmdCaps & ovrHmdCap_NoVSync) == 0); + int swapInterval = (useVsync) ? 1 : 0; +#if defined(OVR_OS_WIN32) + if (wglGetSwapIntervalEXT() != swapInterval) + wglSwapIntervalEXT(swapInterval); + + HDC dc = GetDC(RParams.Window); + BOOL success = SwapBuffers(dc); + ReleaseDC(RParams.Window, dc); + OVR_ASSERT(success); + OVR_UNUSED(success); +#elif defined(OVR_OS_MAC) + CGLContextObj context = CGLGetCurrentContext(); + GLint currentSwapInterval = 0; + CGLGetParameter(context, kCGLCPSwapInterval, ¤tSwapInterval); + if (currentSwapInterval != swapInterval) + CGLSetParameter(context, kCGLCPSwapInterval, &swapInterval); + + CGLFlushDrawable(context); +#elif defined(OVR_OS_LINUX) + static const char* extensions = glXQueryExtensionsString(RParams.Disp, 0); + static bool supportsVSync = (extensions != NULL && strstr(extensions, "GLX_EXT_swap_control")); + if (supportsVSync) + { + GLuint currentSwapInterval = 0; + glXQueryDrawable(RParams.Disp, RParams.Win, GLX_SWAP_INTERVAL_EXT, ¤tSwapInterval); + if (currentSwapInterval != swapInterval) + glXSwapIntervalEXT(RParams.Disp, RParams.Win, swapInterval); + } + + glXSwapBuffers(RParams.Disp, RParams.Win); +#endif + } +} + +void DistortionRenderer::WaitUntilGpuIdle() +{ + glFlush(); + glFinish(); +} + +double DistortionRenderer::FlushGpuAndWaitTillTime(double absTime) +{ + double initialTime = ovr_GetTimeInSeconds(); + if (initialTime >= absTime) + return 0.0; + + glFlush(); + glFinish(); + + double newTime = initialTime; + volatile int i; + + while (newTime < absTime) + { + for (int j = 0; j < 50; j++) + i = 0; + + newTime = ovr_GetTimeInSeconds(); + } + + // How long we waited + return newTime - initialTime; +} + + +DistortionRenderer::GraphicsState::GraphicsState() +{ + const char* glVersionString = (const char*)glGetString(GL_VERSION); + OVR_DEBUG_LOG(("GL_VERSION STRING: %s", (const char*)glVersionString)); + char prefix[64]; + bool foundVersion = false; + + for (int i = 10; i < 30; ++i) + { + int major = i / 10; + int minor = i % 10; + OVR_sprintf(prefix, 64, "%d.%d", major, minor); + if (strstr(glVersionString, prefix) == glVersionString) + { + GlMajorVersion = major; + GlMinorVersion = minor; + foundVersion = true; + break; + } + } + + if (!foundVersion) + { + glGetIntegerv(GL_MAJOR_VERSION, &GlMajorVersion); + glGetIntegerv(GL_MAJOR_VERSION, &GlMinorVersion); + } + + OVR_ASSERT(GlMajorVersion >= 2); + + if (GlMajorVersion >= 3) + { + SupportsVao = true; + } + else + { + const char* extensions = (const char*)glGetString(GL_EXTENSIONS); + SupportsVao = (strstr("GL_ARB_vertex_array_object", extensions) != NULL); + } +} + + +void DistortionRenderer::GraphicsState::ApplyBool(GLenum Name, GLint Value) +{ + if (Value != 0) + glEnable(Name); + else + glDisable(Name); +} + + +void DistortionRenderer::GraphicsState::Save() +{ + glGetIntegerv(GL_VIEWPORT, Viewport); + glGetFloatv(GL_COLOR_CLEAR_VALUE, ClearColor); + glGetIntegerv(GL_DEPTH_TEST, &DepthTest); + glGetIntegerv(GL_CULL_FACE, &CullFace); + glGetIntegerv(GL_CURRENT_PROGRAM, &Program); + glGetIntegerv(GL_ACTIVE_TEXTURE, &ActiveTexture); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &TextureBinding); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &VertexArray); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &FrameBufferBinding); + glGetIntegerv(GL_BLEND, &Blend); + glGetIntegerv(GL_COLOR_WRITEMASK, ColorWritemask); + glGetIntegerv(GL_DITHER, &Dither); + glGetIntegerv(GL_RASTERIZER_DISCARD, &RasterizerDiscard); + if (GlMajorVersion >= 3 && GlMajorVersion >= 2) + glGetIntegerv(GL_SAMPLE_MASK, &SampleMask); + glGetIntegerv(GL_SCISSOR_TEST, &ScissorTest); + + IsValid = true; +} + + +void DistortionRenderer::GraphicsState::Restore() +{ + // Don't allow restore-before-save. + if (!IsValid) + return; + + glViewport(Viewport[0], Viewport[1], Viewport[2], Viewport[3]); + glClearColor(ClearColor[0], ClearColor[1], ClearColor[2], ClearColor[3]); + + ApplyBool(GL_DEPTH_TEST, DepthTest); + ApplyBool(GL_CULL_FACE, CullFace); + + glUseProgram(Program); + glActiveTexture(ActiveTexture); + glBindTexture(GL_TEXTURE_2D, TextureBinding); + if (SupportsVao) + glBindVertexArray(VertexArray); + glBindFramebuffer(GL_FRAMEBUFFER, FrameBufferBinding); + + ApplyBool(GL_BLEND, Blend); + + glColorMask((GLboolean)ColorWritemask[0], (GLboolean)ColorWritemask[1], (GLboolean)ColorWritemask[2], (GLboolean)ColorWritemask[3]); + ApplyBool(GL_DITHER, Dither); + ApplyBool(GL_RASTERIZER_DISCARD, RasterizerDiscard); + if (GlMajorVersion >= 3 && GlMajorVersion >= 2) + ApplyBool(GL_SAMPLE_MASK, SampleMask); + ApplyBool(GL_SCISSOR_TEST, ScissorTest); +} + + +void DistortionRenderer::initBuffersAndShaders() +{ + for ( int eyeNum = 0; eyeNum < 2; eyeNum++ ) + { + // Allocate & generate distortion mesh vertices. + ovrDistortionMesh meshData; + +// double startT = ovr_GetTimeInSeconds(); + + if (!ovrHmd_CreateDistortionMesh( HMD, + RState.EyeRenderDesc[eyeNum].Eye, + RState.EyeRenderDesc[eyeNum].Fov, + RState.DistortionCaps, + &meshData) ) + { + OVR_ASSERT(false); + continue; + } + + // Now parse the vertex data and create a render ready vertex buffer from it + DistortionVertex * pVBVerts = (DistortionVertex*)OVR_ALLOC ( sizeof(DistortionVertex) * meshData.VertexCount ); + DistortionVertex * pCurVBVert = pVBVerts; + ovrDistortionVertex* pCurOvrVert = meshData.pVertexData; + + for ( unsigned vertNum = 0; vertNum < meshData.VertexCount; vertNum++ ) + { + pCurVBVert->Pos.x = pCurOvrVert->Pos.x; + pCurVBVert->Pos.y = pCurOvrVert->Pos.y; + pCurVBVert->TexR = (*(Vector2f*)&pCurOvrVert->TexR); + pCurVBVert->TexG = (*(Vector2f*)&pCurOvrVert->TexG); + pCurVBVert->TexB = (*(Vector2f*)&pCurOvrVert->TexB); + // Convert [0.0f,1.0f] to [0,255] + pCurVBVert->Col.R = (OVR::UByte)( pCurOvrVert->VignetteFactor * 255.99f ); + pCurVBVert->Col.G = pCurVBVert->Col.R; + pCurVBVert->Col.B = pCurVBVert->Col.R; + pCurVBVert->Col.A = (OVR::UByte)( pCurOvrVert->TimeWarpFactor * 255.99f );; + pCurOvrVert++; + pCurVBVert++; + } + + DistortionMeshVBs[eyeNum] = *new Buffer(&RParams); + DistortionMeshVBs[eyeNum]->Data ( Buffer_Vertex | Buffer_ReadOnly, pVBVerts, sizeof(DistortionVertex) * meshData.VertexCount ); + DistortionMeshIBs[eyeNum] = *new Buffer(&RParams); + DistortionMeshIBs[eyeNum]->Data ( Buffer_Index | Buffer_ReadOnly, meshData.pIndexData, ( sizeof(SInt16) * meshData.IndexCount ) ); + + OVR_FREE ( pVBVerts ); + ovrHmd_DestroyDistortionMesh( &meshData ); + } + + initShaders(); +} + +void DistortionRenderer::renderDistortion(Texture* leftEyeTexture, Texture* rightEyeTexture) +{ + GraphicsState* glState = (GraphicsState*)GfxState.GetPtr(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + setViewport( Recti(0,0, RParams.RTSize.w, RParams.RTSize.h) ); + + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + glDisable(GL_DITHER); + glDisable(GL_RASTERIZER_DISCARD); + if (glState->GlMajorVersion >= 3 && glState->GlMajorVersion >= 2) + glDisable(GL_SAMPLE_MASK); + glDisable(GL_SCISSOR_TEST); + + glClearColor( + RState.ClearColor[0], + RState.ClearColor[1], + RState.ClearColor[2], + RState.ClearColor[3] ); + + glClear(GL_COLOR_BUFFER_BIT); + + for (int eyeNum = 0; eyeNum < 2; eyeNum++) + { + ShaderFill distortionShaderFill(DistortionShader); + distortionShaderFill.SetTexture(0, eyeNum == 0 ? leftEyeTexture : rightEyeTexture); + + DistortionShader->SetUniform2f("EyeToSourceUVScale", eachEye[eyeNum].UVScaleOffset[0].x, eachEye[eyeNum].UVScaleOffset[0].y); + DistortionShader->SetUniform2f("EyeToSourceUVOffset", eachEye[eyeNum].UVScaleOffset[1].x, eachEye[eyeNum].UVScaleOffset[1].y); + + if (DistortionCaps & ovrDistortionCap_TimeWarp) + { + ovrMatrix4f timeWarpMatrices[2]; + ovrHmd_GetEyeTimewarpMatrices(HMD, (ovrEyeType)eyeNum, + RState.EyeRenderPoses[eyeNum], timeWarpMatrices); + + // Feed identity like matrices in until we get proper timewarp calculation going on + DistortionShader->SetUniform4x4f("EyeRotationStart", Matrix4f(timeWarpMatrices[0]).Transposed()); + DistortionShader->SetUniform4x4f("EyeRotationEnd", Matrix4f(timeWarpMatrices[1]).Transposed()); + + renderPrimitives(&distortionShaderFill, DistortionMeshVBs[eyeNum], DistortionMeshIBs[eyeNum], + 0, (int)DistortionMeshIBs[eyeNum]->GetSize()/2, Prim_Triangles, &DistortionMeshVAOs[eyeNum], true); + } + else + { + renderPrimitives(&distortionShaderFill, DistortionMeshVBs[eyeNum], DistortionMeshIBs[eyeNum], + 0, (int)DistortionMeshIBs[eyeNum]->GetSize()/2, Prim_Triangles, &DistortionMeshVAOs[eyeNum], true); + } + } +} + +void DistortionRenderer::createDrawQuad() +{ + const int numQuadVerts = 4; + LatencyTesterQuadVB = *new Buffer(&RParams); + if(!LatencyTesterQuadVB) + { + return; + } + + LatencyTesterQuadVB->Data(Buffer_Vertex, NULL, numQuadVerts * sizeof(LatencyVertex)); + LatencyVertex* vertices = (LatencyVertex*)LatencyTesterQuadVB->Map(0, numQuadVerts * sizeof(LatencyVertex), Map_Discard); + if(!vertices) + { + OVR_ASSERT(false); // failed to lock vertex buffer + return; + } + + const float left = -1.0f; + const float top = -1.0f; + const float right = 1.0f; + const float bottom = 1.0f; + + vertices[0] = LatencyVertex(Vector3f(left, top, 0.0f)); + vertices[1] = LatencyVertex(Vector3f(left, bottom, 0.0f)); + vertices[2] = LatencyVertex(Vector3f(right, top, 0.0f)); + vertices[3] = LatencyVertex(Vector3f(right, bottom, 0.0f)); + + LatencyTesterQuadVB->Unmap(vertices); +} + +void DistortionRenderer::renderLatencyQuad(unsigned char* latencyTesterDrawColor) +{ + const int numQuadVerts = 4; + + if(!LatencyTesterQuadVB) + { + createDrawQuad(); + } + + ShaderFill quadFill(SimpleQuadShader); + //quadFill.SetInputLayout(SimpleQuadVertexIL); + + setViewport(Recti(0,0, RParams.RTSize.w, RParams.RTSize.h)); + + SimpleQuadShader->SetUniform2f("Scale", 0.2f, 0.2f); + SimpleQuadShader->SetUniform4f("Color", (float)latencyTesterDrawColor[0] / 255.99f, + (float)latencyTesterDrawColor[0] / 255.99f, + (float)latencyTesterDrawColor[0] / 255.99f, + 1.0f); + + for(int eyeNum = 0; eyeNum < 2; eyeNum++) + { + SimpleQuadShader->SetUniform2f("PositionOffset", eyeNum == 0 ? -0.4f : 0.4f, 0.0f); + renderPrimitives(&quadFill, LatencyTesterQuadVB, NULL, 0, numQuadVerts, Prim_TriangleStrip, &LatencyVAO, false); + } +} + +void DistortionRenderer::renderLatencyPixel(unsigned char* latencyTesterPixelColor) +{ + const int numQuadVerts = 4; + + if(!LatencyTesterQuadVB) + { + createDrawQuad(); + } + + ShaderFill quadFill(SimpleQuadShader); + + setViewport(Recti(0,0, RParams.RTSize.w, RParams.RTSize.h)); + + SimpleQuadShader->SetUniform4f("Color", (float)latencyTesterPixelColor[0] / 255.99f, + (float)latencyTesterPixelColor[0] / 255.99f, + (float)latencyTesterPixelColor[0] / 255.99f, + 1.0f); + + Vector2f scale(2.0f / RParams.RTSize.w, 2.0f / RParams.RTSize.h); + SimpleQuadShader->SetUniform2f("Scale", scale.x, scale.y); + SimpleQuadShader->SetUniform2f("PositionOffset", 1.0f, 1.0f); + renderPrimitives(&quadFill, LatencyTesterQuadVB, NULL, 0, numQuadVerts, Prim_TriangleStrip, &LatencyVAO, false); +} + +void DistortionRenderer::renderPrimitives( + const ShaderFill* fill, + Buffer* vertices, Buffer* indices, + int offset, int count, + PrimitiveType rprim, GLuint* vao, bool isDistortionMesh) +{ + GraphicsState* glState = (GraphicsState*)GfxState.GetPtr(); + + GLenum prim; + switch (rprim) + { + case Prim_Triangles: + prim = GL_TRIANGLES; + break; + case Prim_Lines: + prim = GL_LINES; + break; + case Prim_TriangleStrip: + prim = GL_TRIANGLE_STRIP; + break; + default: + OVR_ASSERT(false); + return; + } + + fill->Set(); + + GLuint prog = fill->GetShaders()->Prog; + + if (vao != NULL) + { + if (*vao != 0) + { + glBindVertexArray(*vao); + + if (isDistortionMesh) + glDrawElements(prim, count, GL_UNSIGNED_SHORT, NULL); + else + glDrawArrays(prim, 0, count); + } + else + { + if (glState->SupportsVao) + { + glGenVertexArrays(1, vao); + glBindVertexArray(*vao); + } + + int attributeCount = (isDistortionMesh) ? 5 : 1; + int* locs = new int[attributeCount]; + + glBindBuffer(GL_ARRAY_BUFFER, ((Buffer*)vertices)->GLBuffer); + + if (isDistortionMesh) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ((Buffer*)indices)->GLBuffer); + + locs[0] = glGetAttribLocation(prog, "Position"); + locs[1] = glGetAttribLocation(prog, "Color"); + locs[2] = glGetAttribLocation(prog, "TexCoord0"); + locs[3] = glGetAttribLocation(prog, "TexCoord1"); + locs[4] = glGetAttribLocation(prog, "TexCoord2"); + + glVertexAttribPointer(locs[0], 2, GL_FLOAT, false, sizeof(DistortionVertex), reinterpret_cast<char*>(offset)+offsetof(DistortionVertex, Pos)); + glVertexAttribPointer(locs[1], 4, GL_UNSIGNED_BYTE, true, sizeof(DistortionVertex), reinterpret_cast<char*>(offset)+offsetof(DistortionVertex, Col)); + glVertexAttribPointer(locs[2], 2, GL_FLOAT, false, sizeof(DistortionVertex), reinterpret_cast<char*>(offset)+offsetof(DistortionVertex, TexR)); + glVertexAttribPointer(locs[3], 2, GL_FLOAT, false, sizeof(DistortionVertex), reinterpret_cast<char*>(offset)+offsetof(DistortionVertex, TexG)); + glVertexAttribPointer(locs[4], 2, GL_FLOAT, false, sizeof(DistortionVertex), reinterpret_cast<char*>(offset)+offsetof(DistortionVertex, TexB)); + } + else + { + locs[0] = glGetAttribLocation(prog, "Position"); + + glVertexAttribPointer(locs[0], 3, GL_FLOAT, false, sizeof(LatencyVertex), reinterpret_cast<char*>(offset)+offsetof(LatencyVertex, Pos)); + } + + for (int i = 0; i < attributeCount; ++i) + glEnableVertexAttribArray(locs[i]); + + if (isDistortionMesh) + glDrawElements(prim, count, GL_UNSIGNED_SHORT, NULL); + else + glDrawArrays(prim, 0, count); + + + if (!glState->SupportsVao) + { + for (int i = 0; i < attributeCount; ++i) + glDisableVertexAttribArray(locs[i]); + } + + delete[] locs; + } + } +} + +void DistortionRenderer::setViewport(const Recti& vp) +{ + glViewport(vp.x, vp.y, vp.w, vp.h); +} + + +void DistortionRenderer::initShaders() +{ + GraphicsState* glState = (GraphicsState*)GfxState.GetPtr(); + + const char* shaderPrefix = + (glState->GlMajorVersion < 3 || (glState->GlMajorVersion == 3 && glState->GlMinorVersion < 2)) ? + glsl2Prefix : glsl3Prefix; + + { + ShaderInfo vsInfo = DistortionVertexShaderLookup[DistortionVertexShaderBitMask & DistortionCaps]; + + size_t vsSize = strlen(shaderPrefix)+vsInfo.ShaderSize; + char* vsSource = new char[vsSize]; + OVR_strcpy(vsSource, vsSize, shaderPrefix); + OVR_strcat(vsSource, vsSize, vsInfo.ShaderData); + + Ptr<GL::VertexShader> vs = *new GL::VertexShader( + &RParams, + (void*)vsSource, vsSize, + vsInfo.ReflectionData, vsInfo.ReflectionSize); + + DistortionShader = *new ShaderSet; + DistortionShader->SetShader(vs); + + delete[](vsSource); + + ShaderInfo psInfo = DistortionPixelShaderLookup[DistortionPixelShaderBitMask & DistortionCaps]; + + size_t psSize = strlen(shaderPrefix)+psInfo.ShaderSize; + char* psSource = new char[psSize]; + OVR_strcpy(psSource, psSize, shaderPrefix); + OVR_strcat(psSource, psSize, psInfo.ShaderData); + + Ptr<GL::FragmentShader> ps = *new GL::FragmentShader( + &RParams, + (void*)psSource, psSize, + psInfo.ReflectionData, psInfo.ReflectionSize); + + DistortionShader->SetShader(ps); + + delete[](psSource); + } + { + size_t vsSize = strlen(shaderPrefix)+sizeof(SimpleQuad_vs); + char* vsSource = new char[vsSize]; + OVR_strcpy(vsSource, vsSize, shaderPrefix); + OVR_strcat(vsSource, vsSize, SimpleQuad_vs); + + Ptr<GL::VertexShader> vs = *new GL::VertexShader( + &RParams, + (void*)vsSource, vsSize, + SimpleQuad_vs_refl, sizeof(SimpleQuad_vs_refl) / sizeof(SimpleQuad_vs_refl[0])); + + SimpleQuadShader = *new ShaderSet; + SimpleQuadShader->SetShader(vs); + + delete[](vsSource); + + size_t psSize = strlen(shaderPrefix)+sizeof(SimpleQuad_fs); + char* psSource = new char[psSize]; + OVR_strcpy(psSource, psSize, shaderPrefix); + OVR_strcat(psSource, psSize, SimpleQuad_fs); + + Ptr<GL::FragmentShader> ps = *new GL::FragmentShader( + &RParams, + (void*)psSource, psSize, + SimpleQuad_fs_refl, sizeof(SimpleQuad_fs_refl) / sizeof(SimpleQuad_fs_refl[0])); + + SimpleQuadShader->SetShader(ps); + + delete[](psSource); + } +} + + +void DistortionRenderer::destroy() +{ + GraphicsState* glState = (GraphicsState*)GfxState.GetPtr(); + + for(int eyeNum = 0; eyeNum < 2; eyeNum++) + { + if (glState->SupportsVao) + glDeleteVertexArrays(1, &DistortionMeshVAOs[eyeNum]); + + DistortionMeshVAOs[eyeNum] = 0; + + DistortionMeshVBs[eyeNum].Clear(); + DistortionMeshIBs[eyeNum].Clear(); + } + + if (DistortionShader) + { + DistortionShader->UnsetShader(Shader_Vertex); + DistortionShader->UnsetShader(Shader_Pixel); + DistortionShader.Clear(); + } + + LatencyTesterQuadVB.Clear(); + LatencyVAO = 0; +} + +}}} // OVR::CAPI::GL diff --git a/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionRenderer.h b/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionRenderer.h new file mode 100644 index 0000000..60f1a9f --- /dev/null +++ b/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionRenderer.h @@ -0,0 +1,178 @@ +/************************************************************************************ + +Filename : CAPI_GL_DistortionRenderer.h +Content : Distortion renderer header for GL +Created : November 11, 2013 +Authors : David Borel, Lee Cooper + +Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. + +Use of this software is subject to the terms of the Oculus Inc license +agreement provided at the time of installation or download, or which +otherwise accompanies this software in either electronic or hard copy form. + +************************************************************************************/ + +#ifndef OVR_CAPI_GL_DistortionRenderer_h +#define OVR_CAPI_GL_DistortionRenderer_h + +#include "../CAPI_DistortionRenderer.h" + +#include "../../Kernel/OVR_Log.h" +#include "CAPI_GL_Util.h" + +namespace OVR { namespace CAPI { namespace GL { + +// ***** GL::DistortionRenderer + +// Implementation of DistortionRenderer for GL. + +class DistortionRenderer : public CAPI::DistortionRenderer +{ +public: + DistortionRenderer(ovrHmd hmd, + FrameTimeManager& timeManager, + const HMDRenderState& renderState); + ~DistortionRenderer(); + + + // Creation function for the device. + static CAPI::DistortionRenderer* Create(ovrHmd hmd, + FrameTimeManager& timeManager, + const HMDRenderState& renderState); + + + // ***** Public DistortionRenderer interface + + virtual bool Initialize(const ovrRenderAPIConfig* apiConfig, + unsigned distortionCaps); + + virtual void SubmitEye(int eyeId, ovrTexture* eyeTexture); + + virtual void EndFrame(bool swapBuffers, unsigned char* latencyTesterDrawColor, unsigned char* latencyTester2DrawColor); + + void WaitUntilGpuIdle(); + + // Similar to ovr_WaitTillTime but it also flushes GPU. + // Note, it exits when time expires, even if GPU is not in idle state yet. + double FlushGpuAndWaitTillTime(double absTime); + +protected: + + + class GraphicsState : public CAPI::DistortionRenderer::GraphicsState + { + public: + GraphicsState(); + virtual void Save(); + virtual void Restore(); + + protected: + void ApplyBool(GLenum Name, GLint Value); + + public: + GLint GlMajorVersion; + GLint GlMinorVersion; + bool SupportsVao; + + GLint Viewport[4]; + GLfloat ClearColor[4]; + GLint DepthTest; + GLint CullFace; + GLint Program; + GLint ActiveTexture; + GLint TextureBinding; + GLint VertexArray; + GLint FrameBufferBinding; + + GLint Blend; + GLint ColorWritemask[4]; + GLint Dither; + GLint Fog; + GLint Lighting; + GLint RasterizerDiscard; + GLint RenderMode; + GLint SampleMask; + GLint ScissorTest; + GLfloat ZoomX; + GLfloat ZoomY; + }; + + // TBD: Should we be using oe from RState instead? + unsigned DistortionCaps; + + struct FOR_EACH_EYE + { + FOR_EACH_EYE() : TextureSize(0), RenderViewport(Sizei(0)) { } + +#if 0 + IDirect3DVertexBuffer9 * dxVerts; + IDirect3DIndexBuffer9 * dxIndices; +#endif + int numVerts; + int numIndices; + + GLuint texture; + + ovrVector2f UVScaleOffset[2]; + Sizei TextureSize; + Recti RenderViewport; + } eachEye[2]; + + // GL context and utility variables. + RenderParams RParams; + + // Helpers + void initBuffersAndShaders(); + void initShaders(); + void initFullscreenQuad(); + void destroy(); + + void setViewport(const Recti& vp); + + void renderDistortion(Texture* leftEyeTexture, Texture* rightEyeTexture); + + void renderPrimitives(const ShaderFill* fill, Buffer* vertices, Buffer* indices, + int offset, int count, + PrimitiveType rprim, GLuint* vao, bool isDistortionMesh); + + void createDrawQuad(); + void renderLatencyQuad(unsigned char* latencyTesterDrawColor); + void renderLatencyPixel(unsigned char* latencyTesterPixelColor); + + Ptr<Texture> pEyeTextures[2]; + + Ptr<Buffer> DistortionMeshVBs[2]; // one per-eye + Ptr<Buffer> DistortionMeshIBs[2]; // one per-eye + GLuint DistortionMeshVAOs[2]; // one per-eye + + Ptr<ShaderSet> DistortionShader; + + struct StandardUniformData + { + Matrix4f Proj; + Matrix4f View; + } StdUniforms; + + GLuint LatencyVAO; + Ptr<Buffer> LatencyTesterQuadVB; + Ptr<ShaderSet> SimpleQuadShader; + + Ptr<Texture> CurRenderTarget; + Array<Ptr<Texture> > DepthBuffers; + GLuint CurrentFbo; + + GLint SavedViewport[4]; + GLfloat SavedClearColor[4]; + GLint SavedDepthTest; + GLint SavedCullFace; + GLint SavedProgram; + GLint SavedActiveTexture; + GLint SavedBoundTexture; + GLint SavedVertexArray; + GLint SavedBoundFrameBuffer; +}; + +}}} // OVR::CAPI::GL + +#endif // OVR_CAPI_GL_DistortionRenderer_h
\ No newline at end of file diff --git a/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionShaders.h b/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionShaders.h new file mode 100644 index 0000000..03fd174 --- /dev/null +++ b/LibOVR/Src/CAPI/GL/CAPI_GL_DistortionShaders.h @@ -0,0 +1,326 @@ +/************************************************************************************ + + Filename : CAPI_GL_Shaders.h + Content : Distortion shader header for GL + Created : November 11, 2013 + Authors : David Borel, Volga Aksoy + + Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. + + Use of this software is subject to the terms of the Oculus Inc license + agreement provided at the time of installation or download, or which + otherwise accompanies this software in either electronic or hard copy form. + + ************************************************************************************/ + + +#ifndef OVR_CAPI_GL_Shaders_h +#define OVR_CAPI_GL_Shaders_h + + +#include "CAPI_GL_Util.h" + +namespace OVR { namespace CAPI { namespace GL { + + static const char glsl2Prefix[] = + "#version 110\n" + "#extension GL_ARB_shader_texture_lod : enable\n" + "#define _FRAGCOLOR_DECLARATION\n" + "#define _VS_IN attribute\n" + "#define _VS_OUT varying\n" + "#define _FS_IN varying\n" + "#define _TEXTURELOD texture2DLod\n" + "#define _FRAGCOLOR gl_FragColor\n"; + + static const char glsl3Prefix[] = + "#version 150\n" + "#define _FRAGCOLOR_DECLARATION out vec4 FragColor;\n" + "#define _VS_IN in\n" + "#define _VS_OUT out\n" + "#define _FS_IN in\n" + "#define _TEXTURELOD textureLod\n" + "#define _FRAGCOLOR FragColor\n"; + + static const char SimpleQuad_vs[] = + "uniform vec2 PositionOffset;\n" + "uniform vec2 Scale;\n" + + "_VS_IN vec3 Position;\n" + + "void main()\n" + "{\n" + " gl_Position = vec4(Position.xy * Scale + PositionOffset, 0.5, 1.0);\n" + "}\n"; + + const OVR::CAPI::GL::ShaderBase::Uniform SimpleQuad_vs_refl[] = + { + { "PositionOffset", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 0, 8 }, + { "Scale", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 8, 8 }, + }; + + static const char SimpleQuad_fs[] = + "uniform vec4 Color;\n" + + "_FRAGCOLOR_DECLARATION\n" + + "void main()\n" + "{\n" + " _FRAGCOLOR = Color;\n" + "}\n"; + + const OVR::CAPI::GL::ShaderBase::Uniform SimpleQuad_fs_refl[] = + { + { "Color", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 0, 16 }, + }; + + + static const char Distortion_vs[] = + "uniform vec2 EyeToSourceUVScale;\n" + "uniform vec2 EyeToSourceUVOffset;\n" + + "_VS_IN vec2 Position;\n" + "_VS_IN vec4 Color;\n" + "_VS_IN vec2 TexCoord0;\n" + + "_VS_OUT vec4 oColor;\n" + "_VS_OUT vec2 oTexCoord0;\n" + + "void main()\n" + "{\n" + " gl_Position.x = Position.x;\n" + " gl_Position.y = Position.y;\n" + " gl_Position.z = 0.5;\n" + " gl_Position.w = 1.0;\n" + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + " oTexCoord0 = TexCoord0 * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " oTexCoord0.y = 1.0 - oTexCoord0.y;\n" + " oColor = Color;\n" // Used for vignette fade. + "}\n"; + + const OVR::CAPI::GL::ShaderBase::Uniform Distortion_vs_refl[] = + { + { "EyeToSourceUVScale", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 0, 8 }, + { "EyeToSourceUVOffset", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 8, 8 }, + }; + + static const char Distortion_fs[] = + "uniform sampler2D Texture0;\n" + + "_FS_IN vec4 oColor;\n" + "_FS_IN vec2 oTexCoord0;\n" + + "_FRAGCOLOR_DECLARATION\n" + + "void main()\n" + "{\n" + " _FRAGCOLOR = _TEXTURELOD(Texture0, oTexCoord0, 0.0);\n" + " _FRAGCOLOR.a = 1.0;\n" + "}\n"; + + + static const char DistortionTimewarp_vs[] = + "uniform vec2 EyeToSourceUVScale;\n" + "uniform vec2 EyeToSourceUVOffset;\n" + "uniform mat4 EyeRotationStart;\n" + "uniform mat4 EyeRotationEnd;\n" + + "_VS_IN vec2 Position;\n" + "_VS_IN vec4 Color;\n" + "_VS_IN vec2 TexCoord0;\n" + + "_FS_IN vec4 oColor;\n" + "_FS_IN vec2 oTexCoord0;\n" + + "void main()\n" + "{\n" + " gl_Position.x = Position.x;\n" + " gl_Position.y = Position.y;\n" + " gl_Position.z = 0.0;\n" + " gl_Position.w = 1.0;\n" + + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // These are now "real world" vectors in direction (x,y,1) relative to the eye of the HMD. + " vec3 TanEyeAngle = vec3 ( TexCoord0.x, TexCoord0.y, 1.0 );\n" + + // Accurate time warp lerp vs. faster +#if 1 + // Apply the two 3x3 timewarp rotations to these vectors. + " vec3 TransformedStart = (EyeRotationStart * vec4(TanEyeAngle, 0)).xyz;\n" + " vec3 TransformedEnd = (EyeRotationEnd * vec4(TanEyeAngle, 0)).xyz;\n" + // And blend between them. + " vec3 Transformed = mix ( TransformedStart, TransformedEnd, Color.a );\n" +#else + " mat4 EyeRotation = mix ( EyeRotationStart, EyeRotationEnd, Color.a );\n" + " vec3 Transformed = EyeRotation * TanEyeAngle;\n" +#endif + + // Project them back onto the Z=1 plane of the rendered images. + " float RecipZ = 1.0 / Transformed.z;\n" + " vec2 Flattened = vec2 ( Transformed.x * RecipZ, Transformed.y * RecipZ );\n" + + // These are now still in TanEyeAngle space. + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + " vec2 SrcCoord = Flattened * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " oTexCoord0 = SrcCoord;\n" + " oTexCoord0.y = 1.0-oTexCoord0.y;\n" + " oColor = vec4(Color.r, Color.r, Color.r, Color.r);\n" // Used for vignette fade. + "}\n"; + + + const OVR::CAPI::GL::ShaderBase::Uniform DistortionTimewarp_vs_refl[] = + { + { "EyeToSourceUVScale", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 0, 8 }, + { "EyeToSourceUVOffset", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 8, 8 }, + }; + + static const char DistortionChroma_vs[] = + "uniform vec2 EyeToSourceUVScale;\n" + "uniform vec2 EyeToSourceUVOffset;\n" + + "_VS_IN vec2 Position;\n" + "_VS_IN vec4 Color;\n" + "_VS_IN vec2 TexCoord0;\n" + "_VS_IN vec2 TexCoord1;\n" + "_VS_IN vec2 TexCoord2;\n" + + "_VS_OUT vec4 oColor;\n" + "_VS_OUT vec2 oTexCoord0;\n" + "_VS_OUT vec2 oTexCoord1;\n" + "_VS_OUT vec2 oTexCoord2;\n" + + "void main()\n" + "{\n" + " gl_Position.x = Position.x;\n" + " gl_Position.y = Position.y;\n" + " gl_Position.z = 0.5;\n" + " gl_Position.w = 1.0;\n" + + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + " oTexCoord0 = TexCoord0 * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " oTexCoord0.y = 1.0-oTexCoord0.y;\n" + " oTexCoord1 = TexCoord1 * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " oTexCoord1.y = 1.0-oTexCoord1.y;\n" + " oTexCoord2 = TexCoord2 * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " oTexCoord2.y = 1.0-oTexCoord2.y;\n" + + " oColor = Color;\n" // Used for vignette fade. + "}\n"; + + const OVR::CAPI::GL::ShaderBase::Uniform DistortionChroma_vs_refl[] = + { + { "EyeToSourceUVScale", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 0, 8 }, + { "EyeToSourceUVOffset", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 8, 8 }, + }; + + static const char DistortionChroma_fs[] = + "uniform sampler2D Texture0;\n" + + "_FS_IN vec4 oColor;\n" + "_FS_IN vec2 oTexCoord0;\n" + "_FS_IN vec2 oTexCoord1;\n" + "_FS_IN vec2 oTexCoord2;\n" + + "_FRAGCOLOR_DECLARATION\n" + + "void main()\n" + "{\n" + " float ResultR = _TEXTURELOD(Texture0, oTexCoord0, 0.0).r;\n" + " float ResultG = _TEXTURELOD(Texture0, oTexCoord1, 0.0).g;\n" + " float ResultB = _TEXTURELOD(Texture0, oTexCoord2, 0.0).b;\n" + + " _FRAGCOLOR = vec4(ResultR * oColor.r, ResultG * oColor.g, ResultB * oColor.b, 1.0);\n" + "}\n"; + + + static const char DistortionTimewarpChroma_vs[] = + "uniform vec2 EyeToSourceUVScale;\n" + "uniform vec2 EyeToSourceUVOffset;\n" + "uniform mat4 EyeRotationStart;\n" + "uniform mat4 EyeRotationEnd;\n" + + "_VS_IN vec2 Position;\n" + "_VS_IN vec4 Color;\n" + "_VS_IN vec2 TexCoord0;\n" + "_VS_IN vec2 TexCoord1;\n" + "_VS_IN vec2 TexCoord2;\n" + + "_VS_OUT vec4 oColor;\n" + "_VS_OUT vec2 oTexCoord0;\n" + "_VS_OUT vec2 oTexCoord1;\n" + "_VS_OUT vec2 oTexCoord2;\n" + + "void main()\n" + "{\n" + " gl_Position.x = Position.x;\n" + " gl_Position.y = Position.y;\n" + " gl_Position.z = 0.0;\n" + " gl_Position.w = 1.0;\n" + + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // These are now "real world" vectors in direction (x,y,1) relative to the eye of the HMD. + " vec3 TanEyeAngleR = vec3 ( TexCoord0.x, TexCoord0.y, 1.0 );\n" + " vec3 TanEyeAngleG = vec3 ( TexCoord1.x, TexCoord1.y, 1.0 );\n" + " vec3 TanEyeAngleB = vec3 ( TexCoord2.x, TexCoord2.y, 1.0 );\n" + + // Accurate time warp lerp vs. faster +#if 1 + // Apply the two 3x3 timewarp rotations to these vectors. + " vec3 TransformedRStart = (EyeRotationStart * vec4(TanEyeAngleR, 0)).xyz;\n" + " vec3 TransformedGStart = (EyeRotationStart * vec4(TanEyeAngleG, 0)).xyz;\n" + " vec3 TransformedBStart = (EyeRotationStart * vec4(TanEyeAngleB, 0)).xyz;\n" + " vec3 TransformedREnd = (EyeRotationEnd * vec4(TanEyeAngleR, 0)).xyz;\n" + " vec3 TransformedGEnd = (EyeRotationEnd * vec4(TanEyeAngleG, 0)).xyz;\n" + " vec3 TransformedBEnd = (EyeRotationEnd * vec4(TanEyeAngleB, 0)).xyz;\n" + + // And blend between them. + " vec3 TransformedR = mix ( TransformedRStart, TransformedREnd, Color.a );\n" + " vec3 TransformedG = mix ( TransformedGStart, TransformedGEnd, Color.a );\n" + " vec3 TransformedB = mix ( TransformedBStart, TransformedBEnd, Color.a );\n" +#else + " mat3 EyeRotation;\n" + " EyeRotation[0] = mix ( EyeRotationStart[0], EyeRotationEnd[0], Color.a ).xyz;\n" + " EyeRotation[1] = mix ( EyeRotationStart[1], EyeRotationEnd[1], Color.a ).xyz;\n" + " EyeRotation[2] = mix ( EyeRotationStart[2], EyeRotationEnd[2], Color.a ).xyz;\n" + " vec3 TransformedR = EyeRotation * TanEyeAngleR;\n" + " vec3 TransformedG = EyeRotation * TanEyeAngleG;\n" + " vec3 TransformedB = EyeRotation * TanEyeAngleB;\n" +#endif + + // Project them back onto the Z=1 plane of the rendered images. + " float RecipZR = 1.0 / TransformedR.z;\n" + " float RecipZG = 1.0 / TransformedG.z;\n" + " float RecipZB = 1.0 / TransformedB.z;\n" + " vec2 FlattenedR = vec2 ( TransformedR.x * RecipZR, TransformedR.y * RecipZR );\n" + " vec2 FlattenedG = vec2 ( TransformedG.x * RecipZG, TransformedG.y * RecipZG );\n" + " vec2 FlattenedB = vec2 ( TransformedB.x * RecipZB, TransformedB.y * RecipZB );\n" + + // These are now still in TanEyeAngle space. + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + " vec2 SrcCoordR = FlattenedR * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " vec2 SrcCoordG = FlattenedG * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + " vec2 SrcCoordB = FlattenedB * EyeToSourceUVScale + EyeToSourceUVOffset;\n" + + " oTexCoord0 = SrcCoordR;\n" + " oTexCoord0.y = 1.0-oTexCoord0.y;\n" + " oTexCoord1 = SrcCoordG;\n" + " oTexCoord1.y = 1.0-oTexCoord1.y;\n" + " oTexCoord2 = SrcCoordB;\n" + " oTexCoord2.y = 1.0-oTexCoord2.y;\n" + + " oColor = vec4(Color.r, Color.r, Color.r, Color.r);\n" // Used for vignette fade. + "}\n"; + + + const OVR::CAPI::GL::ShaderBase::Uniform DistortionTimewarpChroma_vs_refl[] = + { + { "EyeToSourceUVScale", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 0, 8 }, + { "EyeToSourceUVOffset", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 8, 8 }, + { "EyeRotationStart", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 16, 64 }, + { "EyeRotationEnd", OVR::CAPI::GL::ShaderBase::VARTYPE_FLOAT, 80, 64 }, + }; + +}}} // OVR::CAPI::GL + +#endif // OVR_CAPI_GL_Shaders_h diff --git a/LibOVR/Src/CAPI/GL/CAPI_GL_Util.cpp b/LibOVR/Src/CAPI/GL/CAPI_GL_Util.cpp new file mode 100644 index 0000000..910e28c --- /dev/null +++ b/LibOVR/Src/CAPI/GL/CAPI_GL_Util.cpp @@ -0,0 +1,530 @@ +/************************************************************************************ + +Filename : Render_GL_Device.cpp +Content : RenderDevice implementation for OpenGL +Created : September 10, 2012 +Authors : David Borel, Andrew Reisse + +Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +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_GL_Util.h" +#include "../../Kernel/OVR_Log.h" +#include <string.h> + +namespace OVR { namespace CAPI { namespace GL { + + + +// GL Hooks for non-Mac. +#if !defined(OVR_OS_MAC) + +#if defined(OVR_OS_WIN32) + +PFNWGLGETPROCADDRESS wglGetProcAddress; + +PFNGLENABLEPROC glEnable; +PFNGLDISABLEPROC glDisable; +PFNGLGETFLOATVPROC glGetFloatv; +PFNGLGETINTEGERVPROC glGetIntegerv; +PFNGLGETSTRINGPROC glGetString; +PFNGLCOLORMASKPROC glColorMask; +PFNGLCLEARPROC glClear; +PFNGLCLEARCOLORPROC glClearColor; +PFNGLCLEARDEPTHPROC glClearDepth; +PFNGLVIEWPORTPROC glViewport; +PFNGLDRAWELEMENTSPROC glDrawElements; +PFNGLTEXPARAMETERIPROC glTexParameteri; +PFNGLFLUSHPROC glFlush; +PFNGLFINISHPROC glFinish; +PFNGLDRAWARRAYSPROC glDrawArrays; +PFNGLGENTEXTURESPROC glGenTextures; +PFNGLDELETETEXTURESPROC glDeleteTextures; +PFNGLBINDTEXTUREPROC glBindTexture; + +PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT; +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + +#elif defined(OVR_OS_LINUX) + +PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; + +#endif + +PFNGLDELETESHADERPROC glDeleteShader; +PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer; +PFNGLACTIVETEXTUREPROC glActiveTexture; +PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; +PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; +PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; +PFNGLBINDBUFFERPROC glBindBuffer; +PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv; +PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; +PFNGLDELETEBUFFERSPROC glDeleteBuffers; +PFNGLBUFFERDATAPROC glBufferData; +PFNGLGENBUFFERSPROC glGenBuffers; +PFNGLMAPBUFFERPROC glMapBuffer; +PFNGLUNMAPBUFFERPROC glUnmapBuffer; +PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; +PFNGLGETSHADERIVPROC glGetShaderiv; +PFNGLCOMPILESHADERPROC glCompileShader; +PFNGLSHADERSOURCEPROC glShaderSource; +PFNGLCREATESHADERPROC glCreateShader; +PFNGLCREATEPROGRAMPROC glCreateProgram; +PFNGLATTACHSHADERPROC glAttachShader; +PFNGLDETACHSHADERPROC glDetachShader; +PFNGLDELETEPROGRAMPROC glDeleteProgram; +PFNGLUNIFORM1IPROC glUniform1i; +PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; +PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform; +PFNGLUSEPROGRAMPROC glUseProgram; +PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; +PFNGLGETPROGRAMIVPROC glGetProgramiv; +PFNGLLINKPROGRAMPROC glLinkProgram; +PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation; +PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; +PFNGLUNIFORM4FVPROC glUniform4fv; +PFNGLUNIFORM3FVPROC glUniform3fv; +PFNGLUNIFORM2FVPROC glUniform2fv; +PFNGLUNIFORM1FVPROC glUniform1fv; +PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; +PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; +PFNGLBINDVERTEXARRAYPROC glBindVertexArray; + + +#if defined(OVR_OS_WIN32) + +void* GetFunction(const char* functionName) +{ + return wglGetProcAddress(functionName); +} + +#else + +void (*GetFunction(const char *functionName))( void ) +{ + return glXGetProcAddress((GLubyte*)functionName); +} + +#endif + +void InitGLExtensions() +{ + if (glGenVertexArrays) + return; + +#if defined(OVR_OS_WIN32) + HINSTANCE hInst = LoadLibrary(L"Opengl32.dll"); + if (!hInst) + return; + + glGetFloatv = (PFNGLGETFLOATVPROC) GetProcAddress(hInst, "glGetFloatv"); + glGetIntegerv = (PFNGLGETINTEGERVPROC) GetProcAddress(hInst, "glGetIntegerv"); + glGetString = (PFNGLGETSTRINGPROC) GetProcAddress(hInst, "glGetString"); + glEnable = (PFNGLENABLEPROC) GetProcAddress(hInst, "glEnable"); + glDisable = (PFNGLDISABLEPROC) GetProcAddress(hInst, "glDisable"); + glColorMask = (PFNGLCOLORMASKPROC) GetProcAddress(hInst, "glColorMask"); + glClear = (PFNGLCLEARPROC) GetProcAddress(hInst, "glClear" ); + glClearColor = (PFNGLCLEARCOLORPROC) GetProcAddress(hInst, "glClearColor"); + glClearDepth = (PFNGLCLEARDEPTHPROC) GetProcAddress(hInst, "glClearDepth"); + glViewport = (PFNGLVIEWPORTPROC) GetProcAddress(hInst, "glViewport"); + glFlush = (PFNGLFLUSHPROC) GetProcAddress(hInst, "glFlush"); + glFinish = (PFNGLFINISHPROC) GetProcAddress(hInst, "glFinish"); + glDrawArrays = (PFNGLDRAWARRAYSPROC) GetProcAddress(hInst, "glDrawArrays"); + glDrawElements = (PFNGLDRAWELEMENTSPROC) GetProcAddress(hInst, "glDrawElements"); + glGenTextures = (PFNGLGENTEXTURESPROC) GetProcAddress(hInst,"glGenTextures"); + glDeleteTextures = (PFNGLDELETETEXTURESPROC) GetProcAddress(hInst,"glDeleteTextures"); + glBindTexture = (PFNGLBINDTEXTUREPROC) GetProcAddress(hInst,"glBindTexture"); + glTexParameteri = (PFNGLTEXPARAMETERIPROC) GetProcAddress(hInst, "glTexParameteri"); + + wglGetProcAddress = (PFNWGLGETPROCADDRESS) GetProcAddress(hInst, "wglGetProcAddress"); + + wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC) GetFunction("wglGetSwapIntervalEXT"); + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) GetFunction("wglSwapIntervalEXT"); +#elif defined(OVR_OS_LINUX) + glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) GetFunction("glXSwapIntervalEXT"); +#endif + + glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) GetFunction("glBindFramebufferEXT"); + glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) GetFunction("glGenVertexArrays"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) GetFunction("glDeleteVertexArrays"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC) GetFunction("glBindVertexArray"); + glGenBuffers = (PFNGLGENBUFFERSPROC) GetFunction("glGenBuffers"); + glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) GetFunction("glDeleteBuffers"); + glBindBuffer = (PFNGLBINDBUFFERPROC) GetFunction("glBindBuffer"); + glBufferData = (PFNGLBUFFERDATAPROC) GetFunction("glBufferData"); + glMapBuffer = (PFNGLMAPBUFFERPROC) GetFunction("glMapBuffer"); + glUnmapBuffer = (PFNGLUNMAPBUFFERPROC) GetFunction("glUnmapBuffer"); + glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC) GetFunction("glDisableVertexAttribArray"); + glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC) GetFunction("glVertexAttribPointer"); + glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC) GetFunction("glEnableVertexAttribArray"); + glActiveTexture = (PFNGLACTIVETEXTUREPROC) GetFunction("glActiveTexture"); + glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC) GetFunction("glUniformMatrix3fv"); + glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC) GetFunction("glUniformMatrix4fv"); + glUniform1i = (PFNGLUNIFORM1IPROC) GetFunction("glUniform1i"); + glUniform1fv = (PFNGLUNIFORM1FVPROC) GetFunction("glUniform1fv"); + glUniform2fv = (PFNGLUNIFORM2FVPROC) GetFunction("glUniform2fv"); + glUniform3fv = (PFNGLUNIFORM3FVPROC) GetFunction("glUniform3fv"); + glUniform2fv = (PFNGLUNIFORM2FVPROC) GetFunction("glUniform2fv"); + glUniform4fv = (PFNGLUNIFORM4FVPROC) GetFunction("glUniform4fv"); + glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) GetFunction("glGetUniformLocation"); + glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC) GetFunction("glGetActiveUniform"); + glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) GetFunction("glGetShaderInfoLog"); + glGetShaderiv = (PFNGLGETSHADERIVPROC) GetFunction("glGetShaderiv"); + glCompileShader = (PFNGLCOMPILESHADERPROC) GetFunction("glCompileShader"); + glShaderSource = (PFNGLSHADERSOURCEPROC) GetFunction("glShaderSource"); + glCreateShader = (PFNGLCREATESHADERPROC) GetFunction("glCreateShader"); + glDeleteShader = (PFNGLDELETESHADERPROC) GetFunction("glDeleteShader"); + glCreateProgram = (PFNGLCREATEPROGRAMPROC) GetFunction("glCreateProgram"); + glDeleteProgram = (PFNGLDELETEPROGRAMPROC) GetFunction("glDeleteProgram"); + glUseProgram = (PFNGLUSEPROGRAMPROC) GetFunction("glUseProgram"); + glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) GetFunction("glGetProgramInfoLog"); + glGetProgramiv = (PFNGLGETPROGRAMIVPROC) GetFunction("glGetProgramiv"); + glLinkProgram = (PFNGLLINKPROGRAMPROC) GetFunction("glLinkProgram"); + glAttachShader = (PFNGLATTACHSHADERPROC) GetFunction("glAttachShader"); + glDetachShader = (PFNGLDETACHSHADERPROC) GetFunction("glDetachShader"); + glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC) GetFunction("glBindAttribLocation"); + glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC) GetFunction("glGetAttribLocation"); +} + +#endif + +Buffer::Buffer(RenderParams* rp) : pParams(rp), Size(0), Use(0), GLBuffer(0) +{ +} + +Buffer::~Buffer() +{ + if (GLBuffer) + glDeleteBuffers(1, &GLBuffer); +} + +bool Buffer::Data(int use, const void* buffer, size_t size) +{ + Size = size; + + switch (use & Buffer_TypeMask) + { + case Buffer_Index: Use = GL_ELEMENT_ARRAY_BUFFER; break; + default: Use = GL_ARRAY_BUFFER; break; + } + + if (!GLBuffer) + glGenBuffers(1, &GLBuffer); + + int mode = GL_DYNAMIC_DRAW; + if (use & Buffer_ReadOnly) + mode = GL_STATIC_DRAW; + + glBindBuffer(Use, GLBuffer); + glBufferData(Use, size, buffer, mode); + return 1; +} + +void* Buffer::Map(size_t, size_t, int) +{ + int mode = GL_WRITE_ONLY; + //if (flags & Map_Unsynchronized) + // mode |= GL_MAP_UNSYNCHRONIZED; + + glBindBuffer(Use, GLBuffer); + void* v = glMapBuffer(Use, mode); + return v; +} + +bool Buffer::Unmap(void*) +{ + glBindBuffer(Use, GLBuffer); + int r = glUnmapBuffer(Use); + return r != 0; +} + +ShaderSet::ShaderSet() +{ + Prog = glCreateProgram(); +} +ShaderSet::~ShaderSet() +{ + glDeleteProgram(Prog); +} + +GLint ShaderSet::GetGLShader(Shader* s) +{ + switch (s->Stage) + { + case Shader_Vertex: { + ShaderImpl<Shader_Vertex, GL_VERTEX_SHADER>* gls = (ShaderImpl<Shader_Vertex, GL_VERTEX_SHADER>*)s; + return gls->GLShader; + } break; + case Shader_Fragment: { + ShaderImpl<Shader_Fragment, GL_FRAGMENT_SHADER>* gls = (ShaderImpl<Shader_Fragment, GL_FRAGMENT_SHADER>*)s; + return gls->GLShader; + } break; + default: break; + } + + return -1; +} + +void ShaderSet::SetShader(Shader *s) +{ + Shaders[s->Stage] = s; + GLint GLShader = GetGLShader(s); + glAttachShader(Prog, GLShader); + if (Shaders[Shader_Vertex] && Shaders[Shader_Fragment]) + Link(); +} + +void ShaderSet::UnsetShader(int stage) +{ + if (Shaders[stage] == NULL) + return; + + GLint GLShader = GetGLShader(Shaders[stage]); + glDetachShader(Prog, GLShader); + + Shaders[stage] = NULL; +} + +bool ShaderSet::SetUniform(const char* name, int n, const float* v) +{ + for (unsigned int i = 0; i < UniformInfo.GetSize(); i++) + if (!strcmp(UniformInfo[i].Name.ToCStr(), name)) + { + OVR_ASSERT(UniformInfo[i].Location >= 0); + glUseProgram(Prog); + switch (UniformInfo[i].Type) + { + case 1: glUniform1fv(UniformInfo[i].Location, n, v); break; + case 2: glUniform2fv(UniformInfo[i].Location, n/2, v); break; + case 3: glUniform3fv(UniformInfo[i].Location, n/3, v); break; + case 4: glUniform4fv(UniformInfo[i].Location, n/4, v); break; + case 12: glUniformMatrix3fv(UniformInfo[i].Location, 1, 1, v); break; + case 16: glUniformMatrix4fv(UniformInfo[i].Location, 1, 1, v); break; + default: OVR_ASSERT(0); + } + return 1; + } + + OVR_DEBUG_LOG(("Warning: uniform %s not present in selected shader", name)); + return 0; +} + +bool ShaderSet::Link() +{ + glLinkProgram(Prog); + GLint r; + glGetProgramiv(Prog, GL_LINK_STATUS, &r); + if (!r) + { + GLchar msg[1024]; + glGetProgramInfoLog(Prog, sizeof(msg), 0, msg); + OVR_DEBUG_LOG(("Linking shaders failed: %s\n", msg)); + if (!r) + return 0; + } + glUseProgram(Prog); + + UniformInfo.Clear(); + LightingVer = 0; + UsesLighting = 0; + + GLint uniformCount = 0; + glGetProgramiv(Prog, GL_ACTIVE_UNIFORMS, &uniformCount); + OVR_ASSERT(uniformCount >= 0); + + for(GLuint i = 0; i < (GLuint)uniformCount; i++) + { + GLsizei namelen; + GLint size = 0; + GLenum type; + GLchar name[32]; + glGetActiveUniform(Prog, i, sizeof(name), &namelen, &size, &type, name); + + if (size) + { + int l = glGetUniformLocation(Prog, name); + char *np = name; + while (*np) + { + if (*np == '[') + *np = 0; + np++; + } + Uniform u; + u.Name = name; + u.Location = l; + u.Size = size; + switch (type) + { + case GL_FLOAT: u.Type = 1; break; + case GL_FLOAT_VEC2: u.Type = 2; break; + case GL_FLOAT_VEC3: u.Type = 3; break; + case GL_FLOAT_VEC4: u.Type = 4; break; + case GL_FLOAT_MAT3: u.Type = 12; break; + case GL_FLOAT_MAT4: u.Type = 16; break; + default: + continue; + } + UniformInfo.PushBack(u); + if (!strcmp(name, "LightCount")) + UsesLighting = 1; + } + else + break; + } + + ProjLoc = glGetUniformLocation(Prog, "Proj"); + ViewLoc = glGetUniformLocation(Prog, "View"); + for (int i = 0; i < 8; i++) + { + char texv[32]; + OVR_sprintf(texv, 10, "Texture%d", i); + TexLoc[i] = glGetUniformLocation(Prog, texv); + if (TexLoc[i] < 0) + break; + + glUniform1i(TexLoc[i], i); + } + if (UsesLighting) + OVR_ASSERT(ProjLoc >= 0 && ViewLoc >= 0); + return 1; +} + +bool ShaderBase::SetUniform(const char* name, int n, const float* v) +{ + for(unsigned i = 0; i < UniformReflSize; i++) + { + if (!strcmp(UniformRefl[i].Name, name)) + { + memcpy(UniformData + UniformRefl[i].Offset, v, n * sizeof(float)); + return 1; + } + } + return 0; +} + +bool ShaderBase::SetUniformBool(const char* name, int n, const bool* v) +{ + OVR_UNUSED(n); + for(unsigned i = 0; i < UniformReflSize; i++) + { + if (!strcmp(UniformRefl[i].Name, name)) + { + memcpy(UniformData + UniformRefl[i].Offset, v, UniformRefl[i].Size); + return 1; + } + } + return 0; +} + +void ShaderBase::InitUniforms(const Uniform* refl, size_t reflSize) +{ + if(!refl) + { + UniformRefl = NULL; + UniformReflSize = 0; + + UniformsSize = 0; + if (UniformData) + { + OVR_FREE(UniformData); + UniformData = 0; + } + return; // no reflection data + } + + UniformRefl = refl; + UniformReflSize = reflSize; + + UniformsSize = UniformRefl[UniformReflSize-1].Offset + UniformRefl[UniformReflSize-1].Size; + UniformData = (unsigned char*)OVR_ALLOC(UniformsSize); +} + +Texture::Texture(RenderParams* rp, int w, int h) : IsUserAllocated(true), pParams(rp), TexId(0), Width(w), Height(h) +{ + if (w && h) + glGenTextures(1, &TexId); +} + +Texture::~Texture() +{ + if (TexId && !IsUserAllocated) + glDeleteTextures(1, &TexId); +} + +void Texture::Set(int slot, ShaderStage) const +{ + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, TexId); +} + +void Texture::SetSampleMode(int sm) +{ + glBindTexture(GL_TEXTURE_2D, TexId); + switch (sm & Sample_FilterMask) + { + case Sample_Linear: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1); + break; + + case Sample_Anisotropic: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8); + break; + + case Sample_Nearest: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1); + break; + } + + switch (sm & Sample_AddressMask) + { + case Sample_Repeat: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + break; + + case Sample_Clamp: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + break; + + case Sample_ClampBorder: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + break; + } +} + +void Texture::UpdatePlaceholderTexture(GLuint texId, const Sizei& textureSize) +{ + if (!IsUserAllocated && TexId && texId != TexId) + glDeleteTextures(1, &TexId); + + TexId = texId; + Width = textureSize.w; + Height = textureSize.h; + + IsUserAllocated = true; +} + +}}} diff --git a/LibOVR/Src/CAPI/GL/CAPI_GL_Util.h b/LibOVR/Src/CAPI/GL/CAPI_GL_Util.h new file mode 100644 index 0000000..a410f17 --- /dev/null +++ b/LibOVR/Src/CAPI/GL/CAPI_GL_Util.h @@ -0,0 +1,537 @@ +/************************************************************************************ + +Filename : CAPI_GL_Util.h +Content : Utility header for OpenGL +Created : March 27, 2014 +Authors : Andrew Reisse, David Borel + +Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +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. + +************************************************************************************/ + +#ifndef INC_OVR_CAPI_GL_Util_h +#define INC_OVR_CAPI_GL_Util_h + +#include "../../OVR_CAPI.h" +#include "../../Kernel/OVR_Array.h" +#include "../../Kernel/OVR_Math.h" +#include "../../Kernel/OVR_RefCount.h" +#include "../../Kernel/OVR_String.h" +#include "../../Kernel/OVR_Types.h" +#include "../../Kernel/OVR_Log.h" + +#if defined(OVR_OS_WIN32) +#include <Windows.h> +#endif + +#if defined(OVR_OS_MAC) +#include <OpenGL/gl3.h> +#include <OpenGL/gl3ext.h> +#else +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif +#include <GL/gl.h> +#include <GL/glext.h> +#if defined(OVR_OS_WIN32) +#include <GL/wglext.h> +#elif defined(OVR_OS_LINUX) +#include <GL/glx.h> +#endif +#endif + +namespace OVR { namespace CAPI { namespace GL { + +// GL extension Hooks for Non-Mac. +#if !defined(OVR_OS_MAC) + +// Let Windows apps build without linking GL. +#if defined(OVR_OS_WIN32) + +typedef void (__stdcall *PFNGLENABLEPROC) (GLenum); +typedef void (__stdcall *PFNGLDISABLEPROC) (GLenum); +typedef void (__stdcall *PFNGLGETFLOATVPROC) (GLenum, GLfloat*); +typedef const GLubyte * (__stdcall *PFNGLGETSTRINGPROC) (GLenum); +typedef void (__stdcall *PFNGLGETINTEGERVPROC) (GLenum, GLint*); +typedef PROC (__stdcall *PFNWGLGETPROCADDRESS) (LPCSTR); +typedef void (__stdcall *PFNGLFLUSHPROC) (); +typedef void (__stdcall *PFNGLFINISHPROC) (); +typedef void (__stdcall *PFNGLDRAWARRAYSPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (__stdcall *PFNGLCLEARPROC) (GLbitfield); +typedef void (__stdcall *PFNGLCOLORMASKPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +typedef void (__stdcall *PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (__stdcall *PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); +typedef void (__stdcall *PFNGLDELETETEXTURESPROC) (GLsizei n, GLuint *textures); +typedef void (__stdcall *PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); +typedef void (__stdcall *PFNGLCLEARCOLORPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a); +typedef void (__stdcall *PFNGLCLEARDEPTHPROC) (GLclampd depth); +typedef void (__stdcall *PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (__stdcall *PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height); + +extern PFNWGLGETPROCADDRESS wglGetProcAddress; +extern PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT; +extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + +extern PFNGLENABLEPROC glEnable; +extern PFNGLDISABLEPROC glDisable; +extern PFNGLCOLORMASKPROC glColorMask; +extern PFNGLGETFLOATVPROC glGetFloatv; +extern PFNGLGETSTRINGPROC glGetString; +extern PFNGLGETINTEGERVPROC glGetIntegerv; +extern PFNGLCLEARPROC glClear; +extern PFNGLCLEARCOLORPROC glClearColor; +extern PFNGLCLEARDEPTHPROC glClearDepth; +extern PFNGLVIEWPORTPROC glViewport; +extern PFNGLDRAWARRAYSPROC glDrawArrays; +extern PFNGLDRAWELEMENTSPROC glDrawElements; +extern PFNGLGENTEXTURESPROC glGenTextures; +extern PFNGLDELETETEXTURESPROC glDeleteTextures; +extern PFNGLBINDTEXTUREPROC glBindTexture; +extern PFNGLTEXPARAMETERIPROC glTexParameteri; +extern PFNGLFLUSHPROC glFlush; +extern PFNGLFINISHPROC glFinish; + +#elif defined(OVR_OS_LINUX) + +extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; + +#endif // defined(OVR_OS_WIN32) + +extern PFNGLDELETESHADERPROC glDeleteShader; +extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer; +extern PFNGLACTIVETEXTUREPROC glActiveTexture; +extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; +extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; +extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; +extern PFNGLBINDBUFFERPROC glBindBuffer; +extern PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; +extern PFNGLDELETEBUFFERSPROC glDeleteBuffers; +extern PFNGLBUFFERDATAPROC glBufferData; +extern PFNGLGENBUFFERSPROC glGenBuffers; +extern PFNGLMAPBUFFERPROC glMapBuffer; +extern PFNGLUNMAPBUFFERPROC glUnmapBuffer; +extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; +extern PFNGLGETSHADERIVPROC glGetShaderiv; +extern PFNGLCOMPILESHADERPROC glCompileShader; +extern PFNGLSHADERSOURCEPROC glShaderSource; +extern PFNGLCREATESHADERPROC glCreateShader; +extern PFNGLCREATEPROGRAMPROC glCreateProgram; +extern PFNGLATTACHSHADERPROC glAttachShader; +extern PFNGLDETACHSHADERPROC glDetachShader; +extern PFNGLDELETEPROGRAMPROC glDeleteProgram; +extern PFNGLUNIFORM1IPROC glUniform1i; +extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; +extern PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform; +extern PFNGLUSEPROGRAMPROC glUseProgram; +extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; +extern PFNGLGETPROGRAMIVPROC glGetProgramiv; +extern PFNGLLINKPROGRAMPROC glLinkProgram; +extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation; +extern PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; +extern PFNGLUNIFORM4FVPROC glUniform4fv; +extern PFNGLUNIFORM3FVPROC glUniform3fv; +extern PFNGLUNIFORM2FVPROC glUniform2fv; +extern PFNGLUNIFORM1FVPROC glUniform1fv; +extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; +extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; +extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray; + +extern void InitGLExtensions(); + +#endif // !defined(OVR_OS_MAC) + + +// Rendering primitive type used to render Model. +enum PrimitiveType +{ + Prim_Triangles, + Prim_Lines, + Prim_TriangleStrip, + Prim_Unknown, + Prim_Count +}; + +// Types of shaders that can be stored together in a ShaderSet. +enum ShaderStage +{ + Shader_Vertex = 0, + Shader_Fragment = 2, + Shader_Pixel = 2, + Shader_Count = 3, +}; + +enum MapFlags +{ + Map_Discard = 1, + Map_Read = 2, // do not use + Map_Unsynchronized = 4, // like D3D11_MAP_NO_OVERWRITE +}; + + +// Buffer types used for uploading geometry & constants. +enum BufferUsage +{ + Buffer_Unknown = 0, + Buffer_Vertex = 1, + Buffer_Index = 2, + Buffer_Uniform = 4, + Buffer_TypeMask = 0xff, + Buffer_ReadOnly = 0x100, // Buffer must be created with Data(). +}; + +enum TextureFormat +{ + Texture_RGBA = 0x0100, + Texture_Depth = 0x8000, + Texture_TypeMask = 0xff00, + Texture_SamplesMask = 0x00ff, + Texture_RenderTarget = 0x10000, + Texture_GenMipmaps = 0x20000, +}; + +// Texture sampling modes. +enum SampleMode +{ + Sample_Linear = 0, + Sample_Nearest = 1, + Sample_Anisotropic = 2, + Sample_FilterMask = 3, + + Sample_Repeat = 0, + Sample_Clamp = 4, + Sample_ClampBorder = 8, // If unsupported Clamp is used instead. + Sample_AddressMask =12, + + Sample_Count =13, +}; + + +// Rendering parameters/pointers describing GL rendering setup. +struct RenderParams +{ +#if defined(OVR_OS_WIN32) + HWND Window; +#elif defined(OVR_OS_LINUX) + Display* Disp; + Window Win; +#endif + + ovrSizei RTSize; + int Multisample; +}; + + +class Buffer : public RefCountBase<Buffer> +{ +public: + RenderParams* pParams; + size_t Size; + GLenum Use; + GLuint GLBuffer; + +public: + Buffer(RenderParams* r); + ~Buffer(); + + GLuint GetBuffer() { return GLBuffer; } + + virtual size_t GetSize() { return Size; } + virtual void* Map(size_t start, size_t size, int flags = 0); + virtual bool Unmap(void *m); + virtual bool Data(int use, const void* buffer, size_t size); +}; + +class Texture : public RefCountBase<Texture> +{ + bool IsUserAllocated; + +public: + RenderParams* pParams; + GLuint TexId; + int Width, Height; + + Texture(RenderParams* rp, int w, int h); + ~Texture(); + + virtual int GetWidth() const { return Width; } + virtual int GetHeight() const { return Height; } + + virtual void SetSampleMode(int sm); + + // Updates texture to point to specified resources + // - used for slave rendering. + void UpdatePlaceholderTexture(GLuint texId, + const Sizei& textureSize); + + virtual void Set(int slot, ShaderStage stage = Shader_Fragment) const; +}; + +// Base class for vertex and pixel shaders. Stored in ShaderSet. +class Shader : public RefCountBase<Shader> +{ + friend class ShaderSet; + +protected: + ShaderStage Stage; + +public: + Shader(ShaderStage s) : Stage(s) {} + virtual ~Shader() {} + + ShaderStage GetStage() const { return Stage; } + + virtual void Set(PrimitiveType) const { } + virtual void SetUniformBuffer(class Buffer* buffers, int i = 0) { OVR_UNUSED2(buffers, i); } + +protected: + virtual bool SetUniform(const char* name, int n, const float* v) { OVR_UNUSED3(name, n, v); return false; } + virtual bool SetUniformBool(const char* name, int n, const bool* v) { OVR_UNUSED3(name, n, v); return false; } +}; + + + +// A group of shaders, one per stage. +// A ShaderSet is applied for rendering with a given fill. +class ShaderSet : public RefCountBase<ShaderSet> +{ +protected: + Ptr<Shader> Shaders[Shader_Count]; + + struct Uniform + { + String Name; + int Location, Size; + int Type; // currently number of floats in vector + }; + Array<Uniform> UniformInfo; + +public: + GLuint Prog; + GLint ProjLoc, ViewLoc; + GLint TexLoc[8]; + bool UsesLighting; + int LightingVer; + + ShaderSet(); + ~ShaderSet(); + + virtual void SetShader(Shader *s); + virtual void UnsetShader(int stage); + Shader* GetShader(int stage) { return Shaders[stage]; } + + virtual void Set(PrimitiveType prim) const + { + glUseProgram(Prog); + + for (int i = 0; i < Shader_Count; i++) + if (Shaders[i]) + Shaders[i]->Set(prim); + } + + // Set a uniform (other than the standard matrices). It is undefined whether the + // uniforms from one shader occupy the same space as those in other shaders + // (unless a buffer is used, then each buffer is independent). + virtual bool SetUniform(const char* name, int n, const float* v); + bool SetUniform1f(const char* name, float x) + { + const float v[] = {x}; + return SetUniform(name, 1, v); + } + bool SetUniform2f(const char* name, float x, float y) + { + const float v[] = {x,y}; + return SetUniform(name, 2, v); + } + bool SetUniform3f(const char* name, float x, float y, float z) + { + const float v[] = {x,y,z}; + return SetUniform(name, 3, v); + } + bool SetUniform4f(const char* name, float x, float y, float z, float w = 1) + { + const float v[] = {x,y,z,w}; + return SetUniform(name, 4, v); + } + + bool SetUniformv(const char* name, const Vector3f& v) + { + const float a[] = {v.x,v.y,v.z,1}; + return SetUniform(name, 4, a); + } + + virtual bool SetUniform4x4f(const char* name, const Matrix4f& m) + { + Matrix4f mt = m.Transposed(); + return SetUniform(name, 16, &mt.M[0][0]); + } + +protected: + GLint GetGLShader(Shader* s); + bool Link(); +}; + + +// Fill combines a ShaderSet (vertex, pixel) with textures, if any. +// Every model has a fill. +class ShaderFill : public RefCountBase<ShaderFill> +{ + Ptr<ShaderSet> Shaders; + Ptr<class Texture> Textures[8]; + void* InputLayout; // HACK this should be abstracted + +public: + ShaderFill(ShaderSet* sh) : Shaders(sh) { InputLayout = NULL; } + ShaderFill(ShaderSet& sh) : Shaders(sh) { InputLayout = NULL; } + + ShaderSet* GetShaders() const { return Shaders; } + void* GetInputLayout() const { return InputLayout; } + + virtual void Set(PrimitiveType prim = Prim_Unknown) const { + Shaders->Set(prim); + for(int i = 0; i < 8; i++) + { + if(Textures[i]) + { + Textures[i]->Set(i); + } + } + } + + virtual void SetTexture(int i, class Texture* tex) { if (i < 8) Textures[i] = tex; } +}; + + +struct DisplayId +{ + // Windows + String MonitorName; // Monitor name for fullscreen mode + + // MacOS + long CgDisplayId; // CGDirectDisplayID + + DisplayId() : CgDisplayId(0) {} + DisplayId(long id) : CgDisplayId(id) {} + DisplayId(String m, long id=0) : MonitorName(m), CgDisplayId(id) {} + + operator bool () const + { + return MonitorName.GetLength() || CgDisplayId; + } + + bool operator== (const DisplayId& b) const + { + return CgDisplayId == b.CgDisplayId && + (strstr(MonitorName.ToCStr(), b.MonitorName.ToCStr()) || + strstr(b.MonitorName.ToCStr(), MonitorName.ToCStr())); + } +}; + + +class ShaderBase : public Shader +{ +public: + RenderParams* pParams; + unsigned char* UniformData; + int UniformsSize; + + enum VarType + { + VARTYPE_FLOAT, + VARTYPE_INT, + VARTYPE_BOOL, + }; + + struct Uniform + { + const char* Name; + VarType Type; + int Offset, Size; + }; + const Uniform* UniformRefl; + size_t UniformReflSize; + + ShaderBase(RenderParams* rp, ShaderStage stage) : Shader(stage), pParams(rp), UniformData(0), UniformsSize(0) {} + ~ShaderBase() + { + if (UniformData) + OVR_FREE(UniformData); + } + + void InitUniforms(const Uniform* refl, size_t reflSize); + bool SetUniform(const char* name, int n, const float* v); + bool SetUniformBool(const char* name, int n, const bool* v); +}; + + +template<ShaderStage SStage, GLenum SType> +class ShaderImpl : public ShaderBase +{ + friend class ShaderSet; + +public: + ShaderImpl(RenderParams* rp, void* s, size_t size, const Uniform* refl, size_t reflSize) + : ShaderBase(rp, SStage) + , GLShader(0) + { + bool success; + OVR_UNUSED(size); + success = Compile((const char*) s); + OVR_ASSERT(success); + InitUniforms(refl, reflSize); + } + ~ShaderImpl() + { + if (GLShader) + { + glDeleteShader(GLShader); + GLShader = 0; + } + } + bool Compile(const char* src) + { + if (!GLShader) + GLShader = glCreateShader(GLStage()); + + glShaderSource(GLShader, 1, &src, 0); + glCompileShader(GLShader); + GLint r; + glGetShaderiv(GLShader, GL_COMPILE_STATUS, &r); + if (!r) + { + GLchar msg[1024]; + glGetShaderInfoLog(GLShader, sizeof(msg), 0, msg); + if (msg[0]) + OVR_DEBUG_LOG(("Compiling shader\n%s\nfailed: %s\n", src, msg)); + + return 0; + } + return 1; + } + + GLenum GLStage() const + { + return SType; + } + +private: + GLuint GLShader; +}; + +typedef ShaderImpl<Shader_Vertex, GL_VERTEX_SHADER> VertexShader; +typedef ShaderImpl<Shader_Fragment, GL_FRAGMENT_SHADER> FragmentShader; + +}}} + +#endif // INC_OVR_CAPI_GL_Util_h diff --git a/LibOVR/Src/Kernel/OVR_Alg.cpp b/LibOVR/Src/Kernel/OVR_Alg.cpp new file mode 100644 index 0000000..2e52bc3 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Alg.cpp @@ -0,0 +1,57 @@ +/************************************************************************************ + +Filename : OVR_Alg.cpp +Content : Static lookup tables for Alg functions +Created : September 19, 2012 +Notes : + +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 "OVR_Types.h" + +namespace OVR { namespace Alg { + +//------------------------------------------------------------------------ +extern const UByte UpperBitTable[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +extern const UByte LowerBitTable[256] = +{ + 8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, + 5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0 +}; + + +}} // OVE::Alg diff --git a/LibOVR/Src/Kernel/OVR_Alg.h b/LibOVR/Src/Kernel/OVR_Alg.h new file mode 100644 index 0000000..e03cea0 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Alg.h @@ -0,0 +1,1060 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Alg.h +Content : Simple general purpose algorithms: Sort, Binary Search, etc. +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Alg_h +#define OVR_Alg_h + +#include "OVR_Types.h" +#include <string.h> + +namespace OVR { namespace Alg { + + +//----------------------------------------------------------------------------------- +// ***** Operator extensions + +template <typename T> OVR_FORCE_INLINE void Swap(T &a, T &b) +{ T temp(a); a = b; b = temp; } + + +// ***** min/max are not implemented in Visual Studio 6 standard STL + +template <typename T> OVR_FORCE_INLINE const T Min(const T a, const T b) +{ return (a < b) ? a : b; } + +template <typename T> OVR_FORCE_INLINE const T Max(const T a, const T b) +{ return (b < a) ? a : b; } + +template <typename T> OVR_FORCE_INLINE const T Clamp(const T v, const T minVal, const T maxVal) +{ return Max<T>(minVal, Min<T>(v, maxVal)); } + +template <typename T> OVR_FORCE_INLINE int Chop(T f) +{ return (int)f; } + +template <typename T> OVR_FORCE_INLINE T Lerp(T a, T b, T f) +{ return (b - a) * f + a; } + + +// These functions stand to fix a stupid VC++ warning (with /Wp64 on): +// "warning C4267: 'argument' : conversion from 'size_t' to 'const unsigned', possible loss of data" +// Use these functions instead of gmin/gmax if the argument has size +// of the pointer to avoid the warning. Though, functionally they are +// absolutelly the same as regular gmin/gmax. +template <typename T> OVR_FORCE_INLINE const T PMin(const T a, const T b) +{ + OVR_COMPILER_ASSERT(sizeof(T) == sizeof(UPInt)); + return (a < b) ? a : b; +} +template <typename T> OVR_FORCE_INLINE const T PMax(const T a, const T b) +{ + OVR_COMPILER_ASSERT(sizeof(T) == sizeof(UPInt)); + return (b < a) ? a : b; +} + + +template <typename T> OVR_FORCE_INLINE const T Abs(const T v) +{ return (v>=0) ? v : -v; } + + +//----------------------------------------------------------------------------------- +// ***** OperatorLess +// +template<class T> struct OperatorLess +{ + static bool Compare(const T& a, const T& b) + { + return a < b; + } +}; + + +//----------------------------------------------------------------------------------- +// ***** QuickSortSliced +// +// Sort any part of any array: plain, Array, ArrayPaged, ArrayUnsafe. +// The range is specified with start, end, where "end" is exclusive! +// The comparison predicate must be specified. +template<class Array, class Less> +void QuickSortSliced(Array& arr, UPInt start, UPInt end, Less less) +{ + enum + { + Threshold = 9 + }; + + if(end - start < 2) return; + + SPInt stack[80]; + SPInt* top = stack; + SPInt base = (SPInt)start; + SPInt limit = (SPInt)end; + + for(;;) + { + SPInt len = limit - base; + SPInt i, j, pivot; + + if(len > Threshold) + { + // we use base + len/2 as the pivot + pivot = base + len / 2; + Swap(arr[base], arr[pivot]); + + i = base + 1; + j = limit - 1; + + // now ensure that *i <= *base <= *j + if(less(arr[j], arr[i])) Swap(arr[j], arr[i]); + if(less(arr[base], arr[i])) Swap(arr[base], arr[i]); + if(less(arr[j], arr[base])) Swap(arr[j], arr[base]); + + for(;;) + { + do i++; while( less(arr[i], arr[base]) ); + do j--; while( less(arr[base], arr[j]) ); + + if( i > j ) + { + break; + } + + Swap(arr[i], arr[j]); + } + + Swap(arr[base], arr[j]); + + // now, push the largest sub-array + if(j - base > limit - i) + { + top[0] = base; + top[1] = j; + base = i; + } + else + { + top[0] = i; + top[1] = limit; + limit = j; + } + top += 2; + } + else + { + // the sub-array is small, perform insertion sort + j = base; + i = j + 1; + + for(; i < limit; j = i, i++) + { + for(; less(arr[j + 1], arr[j]); j--) + { + Swap(arr[j + 1], arr[j]); + if(j == base) + { + break; + } + } + } + if(top > stack) + { + top -= 2; + base = top[0]; + limit = top[1]; + } + else + { + break; + } + } + } +} + + +//----------------------------------------------------------------------------------- +// ***** QuickSortSliced +// +// Sort any part of any array: plain, Array, ArrayPaged, ArrayUnsafe. +// The range is specified with start, end, where "end" is exclusive! +// The data type must have a defined "<" operator. +template<class Array> +void QuickSortSliced(Array& arr, UPInt start, UPInt end) +{ + typedef typename Array::ValueType ValueType; + QuickSortSliced(arr, start, end, OperatorLess<ValueType>::Compare); +} + +// Same as corresponding G_QuickSortSliced but with checking array limits to avoid +// crash in the case of wrong comparator functor. +template<class Array, class Less> +bool QuickSortSlicedSafe(Array& arr, UPInt start, UPInt end, Less less) +{ + enum + { + Threshold = 9 + }; + + if(end - start < 2) return true; + + SPInt stack[80]; + SPInt* top = stack; + SPInt base = (SPInt)start; + SPInt limit = (SPInt)end; + + for(;;) + { + SPInt len = limit - base; + SPInt i, j, pivot; + + if(len > Threshold) + { + // we use base + len/2 as the pivot + pivot = base + len / 2; + Swap(arr[base], arr[pivot]); + + i = base + 1; + j = limit - 1; + + // now ensure that *i <= *base <= *j + if(less(arr[j], arr[i])) Swap(arr[j], arr[i]); + if(less(arr[base], arr[i])) Swap(arr[base], arr[i]); + if(less(arr[j], arr[base])) Swap(arr[j], arr[base]); + + for(;;) + { + do + { + i++; + if (i >= limit) + return false; + } while( less(arr[i], arr[base]) ); + do + { + j--; + if (j < 0) + return false; + } while( less(arr[base], arr[j]) ); + + if( i > j ) + { + break; + } + + Swap(arr[i], arr[j]); + } + + Swap(arr[base], arr[j]); + + // now, push the largest sub-array + if(j - base > limit - i) + { + top[0] = base; + top[1] = j; + base = i; + } + else + { + top[0] = i; + top[1] = limit; + limit = j; + } + top += 2; + } + else + { + // the sub-array is small, perform insertion sort + j = base; + i = j + 1; + + for(; i < limit; j = i, i++) + { + for(; less(arr[j + 1], arr[j]); j--) + { + Swap(arr[j + 1], arr[j]); + if(j == base) + { + break; + } + } + } + if(top > stack) + { + top -= 2; + base = top[0]; + limit = top[1]; + } + else + { + break; + } + } + } + return true; +} + +template<class Array> +bool QuickSortSlicedSafe(Array& arr, UPInt start, UPInt end) +{ + typedef typename Array::ValueType ValueType; + return QuickSortSlicedSafe(arr, start, end, OperatorLess<ValueType>::Compare); +} + +//----------------------------------------------------------------------------------- +// ***** QuickSort +// +// Sort an array Array, ArrayPaged, ArrayUnsafe. +// The array must have GetSize() function. +// The comparison predicate must be specified. +template<class Array, class Less> +void QuickSort(Array& arr, Less less) +{ + QuickSortSliced(arr, 0, arr.GetSize(), less); +} + +// checks for boundaries +template<class Array, class Less> +bool QuickSortSafe(Array& arr, Less less) +{ + return QuickSortSlicedSafe(arr, 0, arr.GetSize(), less); +} + + +//----------------------------------------------------------------------------------- +// ***** QuickSort +// +// Sort an array Array, ArrayPaged, ArrayUnsafe. +// The array must have GetSize() function. +// The data type must have a defined "<" operator. +template<class Array> +void QuickSort(Array& arr) +{ + typedef typename Array::ValueType ValueType; + QuickSortSliced(arr, 0, arr.GetSize(), OperatorLess<ValueType>::Compare); +} + +template<class Array> +bool QuickSortSafe(Array& arr) +{ + typedef typename Array::ValueType ValueType; + return QuickSortSlicedSafe(arr, 0, arr.GetSize(), OperatorLess<ValueType>::Compare); +} + +//----------------------------------------------------------------------------------- +// ***** InsertionSortSliced +// +// Sort any part of any array: plain, Array, ArrayPaged, ArrayUnsafe. +// The range is specified with start, end, where "end" is exclusive! +// The comparison predicate must be specified. +// Unlike Quick Sort, the Insertion Sort works much slower in average, +// but may be much faster on almost sorted arrays. Besides, it guarantees +// that the elements will not be swapped if not necessary. For example, +// an array with all equal elements will remain "untouched", while +// Quick Sort will considerably shuffle the elements in this case. +template<class Array, class Less> +void InsertionSortSliced(Array& arr, UPInt start, UPInt end, Less less) +{ + UPInt j = start; + UPInt i = j + 1; + UPInt limit = end; + + for(; i < limit; j = i, i++) + { + for(; less(arr[j + 1], arr[j]); j--) + { + Swap(arr[j + 1], arr[j]); + if(j <= start) + { + break; + } + } + } +} + + +//----------------------------------------------------------------------------------- +// ***** InsertionSortSliced +// +// Sort any part of any array: plain, Array, ArrayPaged, ArrayUnsafe. +// The range is specified with start, end, where "end" is exclusive! +// The data type must have a defined "<" operator. +template<class Array> +void InsertionSortSliced(Array& arr, UPInt start, UPInt end) +{ + typedef typename Array::ValueType ValueType; + InsertionSortSliced(arr, start, end, OperatorLess<ValueType>::Compare); +} + +//----------------------------------------------------------------------------------- +// ***** InsertionSort +// +// Sort an array Array, ArrayPaged, ArrayUnsafe. +// The array must have GetSize() function. +// The comparison predicate must be specified. + +template<class Array, class Less> +void InsertionSort(Array& arr, Less less) +{ + InsertionSortSliced(arr, 0, arr.GetSize(), less); +} + +//----------------------------------------------------------------------------------- +// ***** InsertionSort +// +// Sort an array Array, ArrayPaged, ArrayUnsafe. +// The array must have GetSize() function. +// The data type must have a defined "<" operator. +template<class Array> +void InsertionSort(Array& arr) +{ + typedef typename Array::ValueType ValueType; + InsertionSortSliced(arr, 0, arr.GetSize(), OperatorLess<ValueType>::Compare); +} + +//----------------------------------------------------------------------------------- +// ***** Median +// Returns a median value of the input array. +// Caveats: partially sorts the array, returns a reference to the array element +// TBD: This needs to be optimized and generalized +// +template<class Array> +typename Array::ValueType& Median(Array& arr) +{ + UPInt count = arr.GetSize(); + UPInt mid = (count - 1) / 2; + OVR_ASSERT(count > 0); + + for (UPInt j = 0; j <= mid; j++) + { + UPInt min = j; + for (UPInt k = j + 1; k < count; k++) + if (arr[k] < arr[min]) + min = k; + Swap(arr[j], arr[min]); + } + return arr[mid]; +} + +//----------------------------------------------------------------------------------- +// ***** LowerBoundSliced +// +template<class Array, class Value, class Less> +UPInt LowerBoundSliced(const Array& arr, UPInt start, UPInt end, const Value& val, Less less) +{ + SPInt first = (SPInt)start; + SPInt len = (SPInt)(end - start); + SPInt half; + SPInt middle; + + while(len > 0) + { + half = len >> 1; + middle = first + half; + if(less(arr[middle], val)) + { + first = middle + 1; + len = len - half - 1; + } + else + { + len = half; + } + } + return (UPInt)first; +} + + +//----------------------------------------------------------------------------------- +// ***** LowerBoundSliced +// +template<class Array, class Value> +UPInt LowerBoundSliced(const Array& arr, UPInt start, UPInt end, const Value& val) +{ + return LowerBoundSliced(arr, start, end, val, OperatorLess<Value>::Compare); +} + +//----------------------------------------------------------------------------------- +// ***** LowerBoundSized +// +template<class Array, class Value> +UPInt LowerBoundSized(const Array& arr, UPInt size, const Value& val) +{ + return LowerBoundSliced(arr, 0, size, val, OperatorLess<Value>::Compare); +} + +//----------------------------------------------------------------------------------- +// ***** LowerBound +// +template<class Array, class Value, class Less> +UPInt LowerBound(const Array& arr, const Value& val, Less less) +{ + return LowerBoundSliced(arr, 0, arr.GetSize(), val, less); +} + + +//----------------------------------------------------------------------------------- +// ***** LowerBound +// +template<class Array, class Value> +UPInt LowerBound(const Array& arr, const Value& val) +{ + return LowerBoundSliced(arr, 0, arr.GetSize(), val, OperatorLess<Value>::Compare); +} + + + +//----------------------------------------------------------------------------------- +// ***** UpperBoundSliced +// +template<class Array, class Value, class Less> +UPInt UpperBoundSliced(const Array& arr, UPInt start, UPInt end, const Value& val, Less less) +{ + SPInt first = (SPInt)start; + SPInt len = (SPInt)(end - start); + SPInt half; + SPInt middle; + + while(len > 0) + { + half = len >> 1; + middle = first + half; + if(less(val, arr[middle])) + { + len = half; + } + else + { + first = middle + 1; + len = len - half - 1; + } + } + return (UPInt)first; +} + + +//----------------------------------------------------------------------------------- +// ***** UpperBoundSliced +// +template<class Array, class Value> +UPInt UpperBoundSliced(const Array& arr, UPInt start, UPInt end, const Value& val) +{ + return UpperBoundSliced(arr, start, end, val, OperatorLess<Value>::Compare); +} + + +//----------------------------------------------------------------------------------- +// ***** UpperBoundSized +// +template<class Array, class Value> +UPInt UpperBoundSized(const Array& arr, UPInt size, const Value& val) +{ + return UpperBoundSliced(arr, 0, size, val, OperatorLess<Value>::Compare); +} + + +//----------------------------------------------------------------------------------- +// ***** UpperBound +// +template<class Array, class Value, class Less> +UPInt UpperBound(const Array& arr, const Value& val, Less less) +{ + return UpperBoundSliced(arr, 0, arr.GetSize(), val, less); +} + + +//----------------------------------------------------------------------------------- +// ***** UpperBound +// +template<class Array, class Value> +UPInt UpperBound(const Array& arr, const Value& val) +{ + return UpperBoundSliced(arr, 0, arr.GetSize(), val, OperatorLess<Value>::Compare); +} + + +//----------------------------------------------------------------------------------- +// ***** ReverseArray +// +template<class Array> void ReverseArray(Array& arr) +{ + SPInt from = 0; + SPInt to = arr.GetSize() - 1; + while(from < to) + { + Swap(arr[from], arr[to]); + ++from; + --to; + } +} + + +// ***** AppendArray +// +template<class CDst, class CSrc> +void AppendArray(CDst& dst, const CSrc& src) +{ + UPInt i; + for(i = 0; i < src.GetSize(); i++) + dst.PushBack(src[i]); +} + +//----------------------------------------------------------------------------------- +// ***** ArrayAdaptor +// +// A simple adapter that provides the GetSize() method and overloads +// operator []. Used to wrap plain arrays in QuickSort and such. +template<class T> class ArrayAdaptor +{ +public: + typedef T ValueType; + ArrayAdaptor() : Data(0), Size(0) {} + ArrayAdaptor(T* ptr, UPInt size) : Data(ptr), Size(size) {} + UPInt GetSize() const { return Size; } + const T& operator [] (UPInt i) const { return Data[i]; } + T& operator [] (UPInt i) { return Data[i]; } +private: + T* Data; + UPInt Size; +}; + + +//----------------------------------------------------------------------------------- +// ***** GConstArrayAdaptor +// +// A simple const adapter that provides the GetSize() method and overloads +// operator []. Used to wrap plain arrays in LowerBound and such. +template<class T> class ConstArrayAdaptor +{ +public: + typedef T ValueType; + ConstArrayAdaptor() : Data(0), Size(0) {} + ConstArrayAdaptor(const T* ptr, UPInt size) : Data(ptr), Size(size) {} + UPInt GetSize() const { return Size; } + const T& operator [] (UPInt i) const { return Data[i]; } +private: + const T* Data; + UPInt Size; +}; + + + +//----------------------------------------------------------------------------------- +extern const UByte UpperBitTable[256]; +extern const UByte LowerBitTable[256]; + + + +//----------------------------------------------------------------------------------- +inline UByte UpperBit(UPInt val) +{ +#ifndef OVR_64BIT_POINTERS + + if (val & 0xFFFF0000) + { + return (val & 0xFF000000) ? + UpperBitTable[(val >> 24) ] + 24: + UpperBitTable[(val >> 16) & 0xFF] + 16; + } + return (val & 0xFF00) ? + UpperBitTable[(val >> 8) & 0xFF] + 8: + UpperBitTable[(val ) & 0xFF]; + +#else + + if (val & 0xFFFFFFFF00000000) + { + if (val & 0xFFFF000000000000) + { + return (val & 0xFF00000000000000) ? + UpperBitTable[(val >> 56) ] + 56: + UpperBitTable[(val >> 48) & 0xFF] + 48; + } + return (val & 0xFF0000000000) ? + UpperBitTable[(val >> 40) & 0xFF] + 40: + UpperBitTable[(val >> 32) & 0xFF] + 32; + } + else + { + if (val & 0xFFFF0000) + { + return (val & 0xFF000000) ? + UpperBitTable[(val >> 24) ] + 24: + UpperBitTable[(val >> 16) & 0xFF] + 16; + } + return (val & 0xFF00) ? + UpperBitTable[(val >> 8) & 0xFF] + 8: + UpperBitTable[(val ) & 0xFF]; + } + +#endif +} + +//----------------------------------------------------------------------------------- +inline UByte LowerBit(UPInt val) +{ +#ifndef OVR_64BIT_POINTERS + + if (val & 0xFFFF) + { + return (val & 0xFF) ? + LowerBitTable[ val & 0xFF]: + LowerBitTable[(val >> 8) & 0xFF] + 8; + } + return (val & 0xFF0000) ? + LowerBitTable[(val >> 16) & 0xFF] + 16: + LowerBitTable[(val >> 24) & 0xFF] + 24; + +#else + + if (val & 0xFFFFFFFF) + { + if (val & 0xFFFF) + { + return (val & 0xFF) ? + LowerBitTable[ val & 0xFF]: + LowerBitTable[(val >> 8) & 0xFF] + 8; + } + return (val & 0xFF0000) ? + LowerBitTable[(val >> 16) & 0xFF] + 16: + LowerBitTable[(val >> 24) & 0xFF] + 24; + } + else + { + if (val & 0xFFFF00000000) + { + return (val & 0xFF00000000) ? + LowerBitTable[(val >> 32) & 0xFF] + 32: + LowerBitTable[(val >> 40) & 0xFF] + 40; + } + return (val & 0xFF000000000000) ? + LowerBitTable[(val >> 48) & 0xFF] + 48: + LowerBitTable[(val >> 56) & 0xFF] + 56; + } + +#endif +} + + + +// ******* Special (optimized) memory routines +// Note: null (bad) pointer is not tested +class MemUtil +{ +public: + + // Memory compare + static int Cmp (const void* p1, const void* p2, UPInt byteCount) { return memcmp(p1, p2, byteCount); } + static int Cmp16(const void* p1, const void* p2, UPInt int16Count); + static int Cmp32(const void* p1, const void* p2, UPInt int32Count); + static int Cmp64(const void* p1, const void* p2, UPInt int64Count); +}; + +// ** Inline Implementation + +inline int MemUtil::Cmp16(const void* p1, const void* p2, UPInt int16Count) +{ + SInt16* pa = (SInt16*)p1; + SInt16* pb = (SInt16*)p2; + unsigned ic = 0; + if (int16Count == 0) + return 0; + while (pa[ic] == pb[ic]) + if (++ic==int16Count) + return 0; + return pa[ic] > pb[ic] ? 1 : -1; +} +inline int MemUtil::Cmp32(const void* p1, const void* p2, UPInt int32Count) +{ + SInt32* pa = (SInt32*)p1; + SInt32* pb = (SInt32*)p2; + unsigned ic = 0; + if (int32Count == 0) + return 0; + while (pa[ic] == pb[ic]) + if (++ic==int32Count) + return 0; + return pa[ic] > pb[ic] ? 1 : -1; +} +inline int MemUtil::Cmp64(const void* p1, const void* p2, UPInt int64Count) +{ + SInt64* pa = (SInt64*)p1; + SInt64* pb = (SInt64*)p2; + unsigned ic = 0; + if (int64Count == 0) + return 0; + while (pa[ic] == pb[ic]) + if (++ic==int64Count) + return 0; + return pa[ic] > pb[ic] ? 1 : -1; +} + +// ** End Inline Implementation + + +//----------------------------------------------------------------------------------- +// ******* Byte Order Conversions +namespace ByteUtil { + + // *** Swap Byte Order + + // Swap the byte order of a byte array + inline void SwapOrder(void* pv, int size) + { + UByte* pb = (UByte*)pv; + UByte temp; + for (int i = 0; i < size>>1; i++) + { + temp = pb[size-1-i]; + pb[size-1-i] = pb[i]; + pb[i] = temp; + } + } + + // Swap the byte order of primitive types + inline UByte SwapOrder(UByte v) { return v; } + inline SByte SwapOrder(SByte v) { return v; } + inline UInt16 SwapOrder(UInt16 v) { return UInt16(v>>8)|UInt16(v<<8); } + inline SInt16 SwapOrder(SInt16 v) { return SInt16((UInt16(v)>>8)|(v<<8)); } + inline UInt32 SwapOrder(UInt32 v) { return (v>>24)|((v&0x00FF0000)>>8)|((v&0x0000FF00)<<8)|(v<<24); } + inline SInt32 SwapOrder(SInt32 p) { return (SInt32)SwapOrder(UInt32(p)); } + inline UInt64 SwapOrder(UInt64 v) + { + return (v>>56) | + ((v&UInt64(0x00FF000000000000ULL))>>40) | + ((v&UInt64(0x0000FF0000000000ULL))>>24) | + ((v&UInt64(0x000000FF00000000ULL))>>8) | + ((v&UInt64(0x00000000FF000000ULL))<<8) | + ((v&UInt64(0x0000000000FF0000ULL))<<24) | + ((v&UInt64(0x000000000000FF00ULL))<<40) | + (v<<56); + } + inline SInt64 SwapOrder(SInt64 v) { return (SInt64)SwapOrder(UInt64(v)); } + inline float SwapOrder(float p) + { + union { + float p; + UInt32 v; + } u; + u.p = p; + u.v = SwapOrder(u.v); + return u.p; + } + + inline double SwapOrder(double p) + { + union { + double p; + UInt64 v; + } u; + u.p = p; + u.v = SwapOrder(u.v); + return u.p; + } + + // *** Byte-order conversion + +#if (OVR_BYTE_ORDER == OVR_LITTLE_ENDIAN) + // Little Endian to System (LE) + inline UByte LEToSystem(UByte v) { return v; } + inline SByte LEToSystem(SByte v) { return v; } + inline UInt16 LEToSystem(UInt16 v) { return v; } + inline SInt16 LEToSystem(SInt16 v) { return v; } + inline UInt32 LEToSystem(UInt32 v) { return v; } + inline SInt32 LEToSystem(SInt32 v) { return v; } + inline UInt64 LEToSystem(UInt64 v) { return v; } + inline SInt64 LEToSystem(SInt64 v) { return v; } + inline float LEToSystem(float v) { return v; } + inline double LEToSystem(double v) { return v; } + + // Big Endian to System (LE) + inline UByte BEToSystem(UByte v) { return SwapOrder(v); } + inline SByte BEToSystem(SByte v) { return SwapOrder(v); } + inline UInt16 BEToSystem(UInt16 v) { return SwapOrder(v); } + inline SInt16 BEToSystem(SInt16 v) { return SwapOrder(v); } + inline UInt32 BEToSystem(UInt32 v) { return SwapOrder(v); } + inline SInt32 BEToSystem(SInt32 v) { return SwapOrder(v); } + inline UInt64 BEToSystem(UInt64 v) { return SwapOrder(v); } + inline SInt64 BEToSystem(SInt64 v) { return SwapOrder(v); } + inline float BEToSystem(float v) { return SwapOrder(v); } + inline double BEToSystem(double v) { return SwapOrder(v); } + + // System (LE) to Little Endian + inline UByte SystemToLE(UByte v) { return v; } + inline SByte SystemToLE(SByte v) { return v; } + inline UInt16 SystemToLE(UInt16 v) { return v; } + inline SInt16 SystemToLE(SInt16 v) { return v; } + inline UInt32 SystemToLE(UInt32 v) { return v; } + inline SInt32 SystemToLE(SInt32 v) { return v; } + inline UInt64 SystemToLE(UInt64 v) { return v; } + inline SInt64 SystemToLE(SInt64 v) { return v; } + inline float SystemToLE(float v) { return v; } + inline double SystemToLE(double v) { return v; } + + // System (LE) to Big Endian + inline UByte SystemToBE(UByte v) { return SwapOrder(v); } + inline SByte SystemToBE(SByte v) { return SwapOrder(v); } + inline UInt16 SystemToBE(UInt16 v) { return SwapOrder(v); } + inline SInt16 SystemToBE(SInt16 v) { return SwapOrder(v); } + inline UInt32 SystemToBE(UInt32 v) { return SwapOrder(v); } + inline SInt32 SystemToBE(SInt32 v) { return SwapOrder(v); } + inline UInt64 SystemToBE(UInt64 v) { return SwapOrder(v); } + inline SInt64 SystemToBE(SInt64 v) { return SwapOrder(v); } + inline float SystemToBE(float v) { return SwapOrder(v); } + inline double SystemToBE(double v) { return SwapOrder(v); } + +#elif (OVR_BYTE_ORDER == OVR_BIG_ENDIAN) + // Little Endian to System (BE) + inline UByte LEToSystem(UByte v) { return SwapOrder(v); } + inline SByte LEToSystem(SByte v) { return SwapOrder(v); } + inline UInt16 LEToSystem(UInt16 v) { return SwapOrder(v); } + inline SInt16 LEToSystem(SInt16 v) { return SwapOrder(v); } + inline UInt32 LEToSystem(UInt32 v) { return SwapOrder(v); } + inline SInt32 LEToSystem(SInt32 v) { return SwapOrder(v); } + inline UInt64 LEToSystem(UInt64 v) { return SwapOrder(v); } + inline SInt64 LEToSystem(SInt64 v) { return SwapOrder(v); } + inline float LEToSystem(float v) { return SwapOrder(v); } + inline double LEToSystem(double v) { return SwapOrder(v); } + + // Big Endian to System (BE) + inline UByte BEToSystem(UByte v) { return v; } + inline SByte BEToSystem(SByte v) { return v; } + inline UInt16 BEToSystem(UInt16 v) { return v; } + inline SInt16 BEToSystem(SInt16 v) { return v; } + inline UInt32 BEToSystem(UInt32 v) { return v; } + inline SInt32 BEToSystem(SInt32 v) { return v; } + inline UInt64 BEToSystem(UInt64 v) { return v; } + inline SInt64 BEToSystem(SInt64 v) { return v; } + inline float BEToSystem(float v) { return v; } + inline double BEToSystem(double v) { return v; } + + // System (BE) to Little Endian + inline UByte SystemToLE(UByte v) { return SwapOrder(v); } + inline SByte SystemToLE(SByte v) { return SwapOrder(v); } + inline UInt16 SystemToLE(UInt16 v) { return SwapOrder(v); } + inline SInt16 SystemToLE(SInt16 v) { return SwapOrder(v); } + inline UInt32 SystemToLE(UInt32 v) { return SwapOrder(v); } + inline SInt32 SystemToLE(SInt32 v) { return SwapOrder(v); } + inline UInt64 SystemToLE(UInt64 v) { return SwapOrder(v); } + inline SInt64 SystemToLE(SInt64 v) { return SwapOrder(v); } + inline float SystemToLE(float v) { return SwapOrder(v); } + inline double SystemToLE(double v) { return SwapOrder(v); } + + // System (BE) to Big Endian + inline UByte SystemToBE(UByte v) { return v; } + inline SByte SystemToBE(SByte v) { return v; } + inline UInt16 SystemToBE(UInt16 v) { return v; } + inline SInt16 SystemToBE(SInt16 v) { return v; } + inline UInt32 SystemToBE(UInt32 v) { return v; } + inline SInt32 SystemToBE(SInt32 v) { return v; } + inline UInt64 SystemToBE(UInt64 v) { return v; } + inline SInt64 SystemToBE(SInt64 v) { return v; } + inline float SystemToBE(float v) { return v; } + inline double SystemToBE(double v) { return v; } + +#else + #error "OVR_BYTE_ORDER must be defined to OVR_LITTLE_ENDIAN or OVR_BIG_ENDIAN" +#endif + +} // namespace ByteUtil + + + +// Used primarily for hardware interfacing such as sensor reports, firmware, etc. +// Reported data is all little-endian. +inline UInt16 DecodeUInt16(const UByte* buffer) +{ + return ByteUtil::LEToSystem ( *(const UInt16*)buffer ); +} + +inline SInt16 DecodeSInt16(const UByte* buffer) +{ + return ByteUtil::LEToSystem ( *(const SInt16*)buffer ); +} + +inline UInt32 DecodeUInt32(const UByte* buffer) +{ + return ByteUtil::LEToSystem ( *(const UInt32*)buffer ); +} + +inline SInt32 DecodeSInt32(const UByte* buffer) +{ + return ByteUtil::LEToSystem ( *(const SInt32*)buffer ); +} + +inline float DecodeFloat(const UByte* buffer) +{ + union { + UInt32 U; + float F; + }; + + U = DecodeUInt32(buffer); + return F; +} + +inline void EncodeUInt16(UByte* buffer, UInt16 val) +{ + *(UInt16*)buffer = ByteUtil::SystemToLE ( val ); +} + +inline void EncodeSInt16(UByte* buffer, SInt16 val) +{ + *(SInt16*)buffer = ByteUtil::SystemToLE ( val ); +} + +inline void EncodeUInt32(UByte* buffer, UInt32 val) +{ + *(UInt32*)buffer = ByteUtil::SystemToLE ( val ); +} + +inline void EncodeSInt32(UByte* buffer, SInt32 val) +{ + *(SInt32*)buffer = ByteUtil::SystemToLE ( val ); +} + +inline void EncodeFloat(UByte* buffer, float val) +{ + union { + UInt32 U; + float F; + }; + + F = val; + EncodeUInt32(buffer, U); +} + +// Converts an 8-bit binary-coded decimal +inline SByte DecodeBCD(UByte byte) +{ + UByte digit1 = (byte >> 4) & 0x0f; + UByte digit2 = byte & 0x0f; + int decimal = digit1 * 10 + digit2; // maximum value = 99 + return (SByte)decimal; +} + + +}} // OVR::Alg + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Allocator.cpp b/LibOVR/Src/Kernel/OVR_Allocator.cpp new file mode 100644 index 0000000..0f82561 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Allocator.cpp @@ -0,0 +1,95 @@ +/************************************************************************************ + +Filename : OVR_Allocator.cpp +Content : Installable memory allocator implementation +Created : September 19, 2012 +Notes : + +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 "OVR_Allocator.h" +#ifdef OVR_OS_MAC + #include <stdlib.h> +#else + #include <malloc.h> +#endif + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Allocator + +Allocator* Allocator::pInstance = 0; + +// Default AlignedAlloc implementation will delegate to Alloc/Free after doing rounding. +void* Allocator::AllocAligned(UPInt size, UPInt align) +{ + OVR_ASSERT((align & (align-1)) == 0); + align = (align > sizeof(UPInt)) ? align : sizeof(UPInt); + UPInt p = (UPInt)Alloc(size+align); + UPInt aligned = 0; + if (p) + { + aligned = (UPInt(p) + align-1) & ~(align-1); + if (aligned == p) + aligned += align; + *(((UPInt*)aligned)-1) = aligned-p; + } + return (void*)aligned; +} + +void Allocator::FreeAligned(void* p) +{ + UPInt src = UPInt(p) - *(((UPInt*)p)-1); + Free((void*)src); +} + + +//------------------------------------------------------------------------ +// ***** Default Allocator + +// This allocator is created and used if no other allocator is installed. +// Default allocator delegates to system malloc. + +void* DefaultAllocator::Alloc(UPInt size) +{ + return malloc(size); +} +void* DefaultAllocator::AllocDebug(UPInt size, const char* file, unsigned line) +{ +#if defined(OVR_CC_MSVC) && defined(_CRTDBG_MAP_ALLOC) + return _malloc_dbg(size, _NORMAL_BLOCK, file, line); +#else + OVR_UNUSED2(file, line); + return malloc(size); +#endif +} + +void* DefaultAllocator::Realloc(void* p, UPInt newSize) +{ + return realloc(p, newSize); +} +void DefaultAllocator::Free(void *p) +{ + return free(p); +} + + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_Allocator.h b/LibOVR/Src/Kernel/OVR_Allocator.h new file mode 100644 index 0000000..b862557 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Allocator.h @@ -0,0 +1,347 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Allocator.h +Content : Installable memory allocator +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Allocator_h +#define OVR_Allocator_h + +#include "OVR_Types.h" + +//----------------------------------------------------------------------------------- + +// ***** Disable template-unfriendly MS VC++ warnings +#if defined(OVR_CC_MSVC) +// Pragma to prevent long name warnings in in VC++ +#pragma warning(disable : 4503) +#pragma warning(disable : 4786) +// In MSVC 7.1, warning about placement new POD default initializer +#pragma warning(disable : 4345) +#endif + +// Un-define new so that placement constructors work +#undef new + + +//----------------------------------------------------------------------------------- +// ***** Placement new overrides + +// Calls constructor on own memory created with "new(ptr) type" +#ifndef __PLACEMENT_NEW_INLINE +#define __PLACEMENT_NEW_INLINE + +# if defined(OVR_CC_MWERKS) || defined(OVR_CC_BORLAND) || defined(OVR_CC_GNU) +# include <new> +# else + // Useful on MSVC + OVR_FORCE_INLINE void* operator new (OVR::UPInt n, void *ptr) { OVR_UNUSED(n); return ptr; } + OVR_FORCE_INLINE void operator delete (void *, void *) { } +# endif + +#endif // __PLACEMENT_NEW_INLINE + + + +//------------------------------------------------------------------------ +// ***** Macros to redefine class new/delete operators + +// Types specifically declared to allow disambiguation of address in +// class member operator new. + +#define OVR_MEMORY_REDEFINE_NEW_IMPL(class_name, check_delete) \ + void* operator new(UPInt sz) \ + { void *p = OVR_ALLOC_DEBUG(sz, __FILE__, __LINE__); return p; } \ + void* operator new(UPInt sz, const char* file, int line) \ + { void* p = OVR_ALLOC_DEBUG(sz, file, line); OVR_UNUSED2(file, line); return p; } \ + void operator delete(void *p) \ + { check_delete(class_name, p); OVR_FREE(p); } \ + void operator delete(void *p, const char*, int) \ + { check_delete(class_name, p); OVR_FREE(p); } + +#define OVR_MEMORY_DEFINE_PLACEMENT_NEW \ + void* operator new (UPInt n, void *ptr) { OVR_UNUSED(n); return ptr; } \ + void operator delete (void *ptr, void *ptr2) { OVR_UNUSED2(ptr,ptr2); } + + +#define OVR_MEMORY_CHECK_DELETE_NONE(class_name, p) + +// Redefined all delete/new operators in a class without custom memory initialization +#define OVR_MEMORY_REDEFINE_NEW(class_name) \ + OVR_MEMORY_REDEFINE_NEW_IMPL(class_name, OVR_MEMORY_CHECK_DELETE_NONE) + + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Construct / Destruct + +// Construct/Destruct functions are useful when new is redefined, as they can +// be called instead of placement new constructors. + + +template <class T> +OVR_FORCE_INLINE T* Construct(void *p) +{ + return ::new(p) T(); +} + +template <class T> +OVR_FORCE_INLINE T* Construct(void *p, const T& source) +{ + return ::new(p) T(source); +} + +// Same as above, but allows for a different type of constructor. +template <class T, class S> +OVR_FORCE_INLINE T* ConstructAlt(void *p, const S& source) +{ + return ::new(p) T(source); +} + +template <class T, class S1, class S2> +OVR_FORCE_INLINE T* ConstructAlt(void *p, const S1& src1, const S2& src2) +{ + return ::new(p) T(src1, src2); +} + +template <class T> +OVR_FORCE_INLINE void ConstructArray(void *p, UPInt count) +{ + UByte *pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + { + Construct<T>(pdata); + } +} + +template <class T> +OVR_FORCE_INLINE void ConstructArray(void *p, UPInt count, const T& source) +{ + UByte *pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + { + Construct<T>(pdata, source); + } +} + +template <class T> +OVR_FORCE_INLINE void Destruct(T *pobj) +{ + pobj->~T(); + OVR_UNUSED1(pobj); // Fix incorrect 'unused variable' MSVC warning. +} + +template <class T> +OVR_FORCE_INLINE void DestructArray(T *pobj, UPInt count) +{ + for (UPInt i=0; i<count; ++i, ++pobj) + pobj->~T(); +} + + +//----------------------------------------------------------------------------------- +// ***** Allocator + +// Allocator defines a memory allocation interface that developers can override +// to to provide memory for OVR; an instance of this class is typically created on +// application startup and passed into System or OVR::System constructor. +// +// +// Users implementing this interface must provide three functions: Alloc, Free, +// and Realloc. Implementations of these functions must honor the requested alignment. +// Although arbitrary alignment requests are possible, requested alignment will +// typically be small, such as 16 bytes or less. + +class Allocator +{ + friend class System; +public: + + // *** Standard Alignment Alloc/Free + + // Allocate memory of specified size with default alignment. + // Alloc of size==0 will allocate a tiny block & return a valid pointer; + // this makes it suitable for new operator. + virtual void* Alloc(UPInt size) = 0; + // Same as Alloc, but provides an option of passing debug data. + virtual void* AllocDebug(UPInt size, const char* file, unsigned line) + { OVR_UNUSED2(file, line); return Alloc(size); } + + // Reallocate memory block to a new size, copying data if necessary. Returns the pointer to + // new memory block, which may be the same as original pointer. Will return 0 if reallocation + // failed, in which case previous memory is still valid. + // Realloc to decrease size will never fail. + // Realloc of pointer == 0 is equivalent to Alloc + // Realloc to size == 0, shrinks to the minimal size, pointer remains valid and requires Free(). + virtual void* Realloc(void* p, UPInt newSize) = 0; + + // Frees memory allocated by Alloc/Realloc. + // Free of null pointer is valid and will do nothing. + virtual void Free(void *p) = 0; + + + // *** Standard Alignment Alloc/Free + + // Allocate memory of specified alignment. + // Memory allocated with AllocAligned MUST be freed with FreeAligned. + // Default implementation will delegate to Alloc/Free after doing rounding. + virtual void* AllocAligned(UPInt size, UPInt align); + // Frees memory allocated with AllocAligned. + virtual void FreeAligned(void* p); + + // Returns the pointer to the current globally installed Allocator instance. + // This pointer is used for most of the memory allocations. + static Allocator* GetInstance() { return pInstance; } + + +protected: + // onSystemShutdown is called on the allocator during System::Shutdown. + // At this point, all allocations should've been freed. + virtual void onSystemShutdown() { } + +public: + static void setInstance(Allocator* palloc) + { + OVR_ASSERT((pInstance == 0) || (palloc == 0)); + pInstance = palloc; + } + +private: + + static Allocator* pInstance; +}; + + + +//------------------------------------------------------------------------ +// ***** Allocator_SingletonSupport + +// Allocator_SingletonSupport is a Allocator wrapper class that implements +// the InitSystemSingleton static function, used to create a global singleton +// used for the OVR::System default argument initialization. +// +// End users implementing custom Allocator interface don't need to make use of this base +// class; they can just create an instance of their own class on stack and pass it to System. + +template<class D> +class Allocator_SingletonSupport : public Allocator +{ + struct AllocContainer + { + UPInt Data[(sizeof(D) + sizeof(UPInt)-1) / sizeof(UPInt)]; + bool Initialized; + AllocContainer() : Initialized(0) { } + }; + + AllocContainer* pContainer; + +public: + Allocator_SingletonSupport() : pContainer(0) { } + + // Creates a singleton instance of this Allocator class used + // on OVR_DEFAULT_ALLOCATOR during System initialization. + static D* InitSystemSingleton() + { + static AllocContainer Container; + OVR_ASSERT(Container.Initialized == false); + + Allocator_SingletonSupport<D> *presult = Construct<D>((void*)Container.Data); + presult->pContainer = &Container; + Container.Initialized = true; + return (D*)presult; + } + +protected: + virtual void onSystemShutdown() + { + Allocator::onSystemShutdown(); + if (pContainer) + { + pContainer->Initialized = false; + Destruct((D*)this); + pContainer = 0; + } + } +}; + +//------------------------------------------------------------------------ +// ***** Default Allocator + +// This allocator is created and used if no other allocator is installed. +// Default allocator delegates to system malloc. + +class DefaultAllocator : public Allocator_SingletonSupport<DefaultAllocator> +{ +public: + virtual void* Alloc(UPInt size); + virtual void* AllocDebug(UPInt size, const char* file, unsigned line); + virtual void* Realloc(void* p, UPInt newSize); + virtual void Free(void *p); +}; + + +//------------------------------------------------------------------------ +// ***** Memory Allocation Macros + +// These macros should be used for global allocation. In the future, these +// macros will allows allocation to be extended with debug file/line information +// if necessary. + +#define OVR_REALLOC(p,s) OVR::Allocator::GetInstance()->Realloc((p),(s)) +#define OVR_FREE(p) OVR::Allocator::GetInstance()->Free((p)) +#define OVR_ALLOC_ALIGNED(s,a) OVR::Allocator::GetInstance()->AllocAligned((s),(a)) +#define OVR_FREE_ALIGNED(p) OVR::Allocator::GetInstance()->FreeAligned((p)) + +#ifdef OVR_BUILD_DEBUG +#define OVR_ALLOC(s) OVR::Allocator::GetInstance()->AllocDebug((s), __FILE__, __LINE__) +#define OVR_ALLOC_DEBUG(s,f,l) OVR::Allocator::GetInstance()->AllocDebug((s), f, l) +#else +#define OVR_ALLOC(s) OVR::Allocator::GetInstance()->Alloc((s)) +#define OVR_ALLOC_DEBUG(s,f,l) OVR::Allocator::GetInstance()->Alloc((s)) +#endif + +//------------------------------------------------------------------------ + +// Base class that overrides the new and delete operators. +// Deriving from this class, even as a multiple base, incurs no space overhead. +class NewOverrideBase +{ +public: + + // Redefine all new & delete operators. + OVR_MEMORY_REDEFINE_NEW(NewOverrideBase) +}; + + +} // OVR + + +// Redefine operator 'new' if necessary. +#if defined(OVR_DEFINE_NEW) +#define new OVR_DEFINE_NEW +#endif + + +#endif // OVR_Memory diff --git a/LibOVR/Src/Kernel/OVR_Array.h b/LibOVR/Src/Kernel/OVR_Array.h new file mode 100644 index 0000000..7a715ba --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Array.h @@ -0,0 +1,833 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Array.h +Content : Template implementation for Array +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Array_h +#define OVR_Array_h + +#include "OVR_ContainerAllocator.h" + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** ArrayDefaultPolicy +// +// Default resize behavior. No minimal capacity, Granularity=4, +// Shrinking as needed. ArrayConstPolicy actually is the same as +// ArrayDefaultPolicy, but parametrized with constants. +// This struct is used only in order to reduce the template "matroska". +struct ArrayDefaultPolicy +{ + ArrayDefaultPolicy() : Capacity(0) {} + ArrayDefaultPolicy(const ArrayDefaultPolicy&) : Capacity(0) {} + + UPInt GetMinCapacity() const { return 0; } + UPInt GetGranularity() const { return 4; } + bool NeverShrinking() const { return 0; } + + UPInt GetCapacity() const { return Capacity; } + void SetCapacity(UPInt capacity) { Capacity = capacity; } +private: + UPInt Capacity; +}; + + +//----------------------------------------------------------------------------------- +// ***** ArrayConstPolicy +// +// Statically parametrized resizing behavior: +// MinCapacity, Granularity, and Shrinking flag. +template<int MinCapacity=0, int Granularity=4, bool NeverShrink=false> +struct ArrayConstPolicy +{ + typedef ArrayConstPolicy<MinCapacity, Granularity, NeverShrink> SelfType; + + ArrayConstPolicy() : Capacity(0) {} + ArrayConstPolicy(const SelfType&) : Capacity(0) {} + + UPInt GetMinCapacity() const { return MinCapacity; } + UPInt GetGranularity() const { return Granularity; } + bool NeverShrinking() const { return NeverShrink; } + + UPInt GetCapacity() const { return Capacity; } + void SetCapacity(UPInt capacity) { Capacity = capacity; } +private: + UPInt Capacity; +}; + +//----------------------------------------------------------------------------------- +// ***** ArrayDataBase +// +// Basic operations with array data: Reserve, Resize, Free, ArrayPolicy. +// For internal use only: ArrayData,ArrayDataCC and others. +template<class T, class Allocator, class SizePolicy> +struct ArrayDataBase +{ + typedef T ValueType; + typedef Allocator AllocatorType; + typedef SizePolicy SizePolicyType; + typedef ArrayDataBase<T, Allocator, SizePolicy> SelfType; + + ArrayDataBase() + : Data(0), Size(0), Policy() {} + + ArrayDataBase(const SizePolicy& p) + : Data(0), Size(0), Policy(p) {} + + ~ArrayDataBase() + { + Allocator::DestructArray(Data, Size); + Allocator::Free(Data); + } + + UPInt GetCapacity() const + { + return Policy.GetCapacity(); + } + + void ClearAndRelease() + { + Allocator::DestructArray(Data, Size); + Allocator::Free(Data); + Data = 0; + Size = 0; + Policy.SetCapacity(0); + } + + void Reserve(UPInt newCapacity) + { + if (Policy.NeverShrinking() && newCapacity < GetCapacity()) + return; + + if (newCapacity < Policy.GetMinCapacity()) + newCapacity = Policy.GetMinCapacity(); + + // Resize the buffer. + if (newCapacity == 0) + { + if (Data) + { + Allocator::Free(Data); + Data = 0; + } + Policy.SetCapacity(0); + } + else + { + UPInt gran = Policy.GetGranularity(); + newCapacity = (newCapacity + gran - 1) / gran * gran; + if (Data) + { + if (Allocator::IsMovable()) + { + Data = (T*)Allocator::Realloc(Data, sizeof(T) * newCapacity); + } + else + { + T* newData = (T*)Allocator::Alloc(sizeof(T) * newCapacity); + UPInt i, s; + s = (Size < newCapacity) ? Size : newCapacity; + for (i = 0; i < s; ++i) + { + Allocator::Construct(&newData[i], Data[i]); + Allocator::Destruct(&Data[i]); + } + for (i = s; i < Size; ++i) + { + Allocator::Destruct(&Data[i]); + } + Allocator::Free(Data); + Data = newData; + } + } + else + { + Data = (T*)Allocator::Alloc(sizeof(T) * newCapacity); + //memset(Buffer, 0, (sizeof(ValueType) * newSize)); // Do we need this? + } + Policy.SetCapacity(newCapacity); + // OVR_ASSERT(Data); // need to throw (or something) on alloc failure! + } + } + + // This version of Resize DOES NOT construct the elements. + // It's done to optimize PushBack, which uses a copy constructor + // instead of the default constructor and assignment + void ResizeNoConstruct(UPInt newSize) + { + UPInt oldSize = Size; + + if (newSize < oldSize) + { + Allocator::DestructArray(Data + newSize, oldSize - newSize); + if (newSize < (Policy.GetCapacity() >> 1)) + { + Reserve(newSize); + } + } + else if(newSize >= Policy.GetCapacity()) + { + Reserve(newSize + (newSize >> 2)); + } + //! IMPORTANT to modify Size only after Reserve completes, because garbage collectable + // array may use this array and may traverse it during Reserve (in the case, if + // collection occurs because of heap limit exceeded). + Size = newSize; + } + + ValueType* Data; + UPInt Size; + SizePolicy Policy; +}; + + + +//----------------------------------------------------------------------------------- +// ***** ArrayData +// +// General purpose array data. +// For internal use only in Array, ArrayLH, ArrayPOD and so on. +template<class T, class Allocator, class SizePolicy> +struct ArrayData : ArrayDataBase<T, Allocator, SizePolicy> +{ + typedef T ValueType; + typedef Allocator AllocatorType; + typedef SizePolicy SizePolicyType; + typedef ArrayDataBase<T, Allocator, SizePolicy> BaseType; + typedef ArrayData <T, Allocator, SizePolicy> SelfType; + + ArrayData() + : BaseType() { } + + ArrayData(UPInt size) + : BaseType() { Resize(size); } + + ArrayData(const SelfType& a) + : BaseType(a.Policy) { Append(a.Data, a.Size); } + + + void Resize(UPInt newSize) + { + UPInt oldSize = this->Size; + BaseType::ResizeNoConstruct(newSize); + if(newSize > oldSize) + Allocator::ConstructArray(this->Data + oldSize, newSize - oldSize); + } + + void PushBack(const ValueType& val) + { + BaseType::ResizeNoConstruct(this->Size + 1); + Allocator::Construct(this->Data + this->Size - 1, val); + } + + template<class S> + void PushBackAlt(const S& val) + { + BaseType::ResizeNoConstruct(this->Size + 1); + Allocator::ConstructAlt(this->Data + this->Size - 1, val); + } + + // Append the given data to the array. + void Append(const ValueType other[], UPInt count) + { + if (count) + { + UPInt oldSize = this->Size; + BaseType::ResizeNoConstruct(this->Size + count); + Allocator::ConstructArray(this->Data + oldSize, count, other); + } + } +}; + + + +//----------------------------------------------------------------------------------- +// ***** ArrayDataCC +// +// A modification of ArrayData that always copy-constructs new elements +// using a specified DefaultValue. For internal use only in ArrayCC. +template<class T, class Allocator, class SizePolicy> +struct ArrayDataCC : ArrayDataBase<T, Allocator, SizePolicy> +{ + typedef T ValueType; + typedef Allocator AllocatorType; + typedef SizePolicy SizePolicyType; + typedef ArrayDataBase<T, Allocator, SizePolicy> BaseType; + typedef ArrayDataCC <T, Allocator, SizePolicy> SelfType; + + ArrayDataCC(const ValueType& defval) + : BaseType(), DefaultValue(defval) { } + + ArrayDataCC(const ValueType& defval, UPInt size) + : BaseType(), DefaultValue(defval) { Resize(size); } + + ArrayDataCC(const SelfType& a) + : BaseType(a.Policy), DefaultValue(a.DefaultValue) { Append(a.Data, a.Size); } + + + void Resize(UPInt newSize) + { + UPInt oldSize = this->Size; + BaseType::ResizeNoConstruct(newSize); + if(newSize > oldSize) + Allocator::ConstructArray(this->Data + oldSize, newSize - oldSize, DefaultValue); + } + + void PushBack(const ValueType& val) + { + BaseType::ResizeNoConstruct(this->Size + 1); + Allocator::Construct(this->Data + this->Size - 1, val); + } + + template<class S> + void PushBackAlt(const S& val) + { + BaseType::ResizeNoConstruct(this->Size + 1); + Allocator::ConstructAlt(this->Data + this->Size - 1, val); + } + + // Append the given data to the array. + void Append(const ValueType other[], UPInt count) + { + if (count) + { + UPInt oldSize = this->Size; + BaseType::ResizeNoConstruct(this->Size + count); + Allocator::ConstructArray(this->Data + oldSize, count, other); + } + } + + ValueType DefaultValue; +}; + + + + + +//----------------------------------------------------------------------------------- +// ***** ArrayBase +// +// Resizable array. The behavior can be POD (suffix _POD) and +// Movable (no suffix) depending on the allocator policy. +// In case of _POD the constructors and destructors are not called. +// +// Arrays can't handle non-movable objects! Don't put anything in here +// that can't be moved around by bitwise copy. +// +// The addresses of elements are not persistent! Don't keep the address +// of an element; the array contents will move around as it gets resized. +template<class ArrayData> +class ArrayBase +{ +public: + typedef typename ArrayData::ValueType ValueType; + typedef typename ArrayData::AllocatorType AllocatorType; + typedef typename ArrayData::SizePolicyType SizePolicyType; + typedef ArrayBase<ArrayData> SelfType; + + +#undef new + OVR_MEMORY_REDEFINE_NEW(ArrayBase) +// Redefine operator 'new' if necessary. +#if defined(OVR_DEFINE_NEW) +#define new OVR_DEFINE_NEW +#endif + + + ArrayBase() + : Data() {} + ArrayBase(UPInt size) + : Data(size) {} + ArrayBase(const SelfType& a) + : Data(a.Data) {} + + ArrayBase(const ValueType& defval) + : Data(defval) {} + ArrayBase(const ValueType& defval, UPInt size) + : Data(defval, size) {} + + SizePolicyType* GetSizePolicy() const { return Data.Policy; } + void SetSizePolicy(const SizePolicyType& p) { Data.Policy = p; } + + bool NeverShrinking()const { return Data.Policy.NeverShrinking(); } + UPInt GetSize() const { return Data.Size; } + bool IsEmpty() const { return Data.Size == 0; } + UPInt GetCapacity() const { return Data.GetCapacity(); } + UPInt GetNumBytes() const { return Data.GetCapacity() * sizeof(ValueType); } + + void ClearAndRelease() { Data.ClearAndRelease(); } + void Clear() { Data.Resize(0); } + void Resize(UPInt newSize) { Data.Resize(newSize); } + + // Reserve can only increase the capacity + void Reserve(UPInt newCapacity) + { + if (newCapacity > Data.GetCapacity()) + Data.Reserve(newCapacity); + } + + // Basic access. + ValueType& At(UPInt index) + { + OVR_ASSERT(index < Data.Size); + return Data.Data[index]; + } + const ValueType& At(UPInt index) const + { + OVR_ASSERT(index < Data.Size); + return Data.Data[index]; + } + + ValueType ValueAt(UPInt index) const + { + OVR_ASSERT(index < Data.Size); + return Data.Data[index]; + } + + // Basic access. + ValueType& operator [] (UPInt index) + { + OVR_ASSERT(index < Data.Size); + return Data.Data[index]; + } + const ValueType& operator [] (UPInt index) const + { + OVR_ASSERT(index < Data.Size); + return Data.Data[index]; + } + + // Raw pointer to the data. Use with caution! + const ValueType* GetDataPtr() const { return Data.Data; } + ValueType* GetDataPtr() { return Data.Data; } + + // Insert the given element at the end of the array. + void PushBack(const ValueType& val) + { + // DO NOT pass elements of your own vector into + // push_back()! Since we're using references, + // resize() may munge the element storage! + // OVR_ASSERT(&val < &Buffer[0] || &val > &Buffer[BufferSize]); + Data.PushBack(val); + } + + template<class S> + void PushBackAlt(const S& val) + { + Data.PushBackAlt(val); + } + + // Remove the last element. + void PopBack(UPInt count = 1) + { + OVR_ASSERT(Data.Size >= count); + Data.Resize(Data.Size - count); + } + + ValueType& PushDefault() + { + Data.PushBack(ValueType()); + return Back(); + } + + ValueType Pop() + { + ValueType t = Back(); + PopBack(); + return t; + } + + + // Access the first element. + ValueType& Front() { return At(0); } + const ValueType& Front() const { return At(0); } + + // Access the last element. + ValueType& Back() { return At(Data.Size - 1); } + const ValueType& Back() const { return At(Data.Size - 1); } + + // Array copy. Copies the contents of a into this array. + const SelfType& operator = (const SelfType& a) + { + Resize(a.GetSize()); + for (UPInt i = 0; i < Data.Size; i++) { + *(Data.Data + i) = a[i]; + } + return *this; + } + + // Removing multiple elements from the array. + void RemoveMultipleAt(UPInt index, UPInt num) + { + OVR_ASSERT(index + num <= Data.Size); + if (Data.Size == num) + { + Clear(); + } + else + { + AllocatorType::DestructArray(Data.Data + index, num); + AllocatorType::CopyArrayForward( + Data.Data + index, + Data.Data + index + num, + Data.Size - num - index); + Data.Size -= num; + } + } + + // Removing an element from the array is an expensive operation! + // It compacts only after removing the last element. + // If order of elements in the array is not important then use + // RemoveAtUnordered, that could be much faster than the regular + // RemoveAt. + void RemoveAt(UPInt index) + { + OVR_ASSERT(index < Data.Size); + if (Data.Size == 1) + { + Clear(); + } + else + { + AllocatorType::Destruct(Data.Data + index); + AllocatorType::CopyArrayForward( + Data.Data + index, + Data.Data + index + 1, + Data.Size - 1 - index); + --Data.Size; + } + } + + // Removes an element from the array without respecting of original order of + // elements for better performance. Do not use on array where order of elements + // is important, otherwise use it instead of regular RemoveAt(). + void RemoveAtUnordered(UPInt index) + { + OVR_ASSERT(index < Data.Size); + if (Data.Size == 1) + { + Clear(); + } + else + { + // copy the last element into the 'index' position + // and decrement the size (instead of moving all elements + // in [index + 1 .. size - 1] range). + const UPInt lastElemIndex = Data.Size - 1; + if (index < lastElemIndex) + { + AllocatorType::Destruct(Data.Data + index); + AllocatorType::Construct(Data.Data + index, Data.Data[lastElemIndex]); + } + AllocatorType::Destruct(Data.Data + lastElemIndex); + --Data.Size; + } + } + + // Insert the given object at the given index shifting all the elements up. + void InsertAt(UPInt index, const ValueType& val = ValueType()) + { + OVR_ASSERT(index <= Data.Size); + + Data.Resize(Data.Size + 1); + if (index < Data.Size - 1) + { + AllocatorType::CopyArrayBackward( + Data.Data + index + 1, + Data.Data + index, + Data.Size - 1 - index); + } + AllocatorType::Construct(Data.Data + index, val); + } + + // Insert the given object at the given index shifting all the elements up. + void InsertMultipleAt(UPInt index, UPInt num, const ValueType& val = ValueType()) + { + OVR_ASSERT(index <= Data.Size); + + Data.Resize(Data.Size + num); + if (index < Data.Size - num) + { + AllocatorType::CopyArrayBackward( + Data.Data + index + num, + Data.Data + index, + Data.Size - num - index); + } + for (UPInt i = 0; i < num; ++i) + AllocatorType::Construct(Data.Data + index + i, val); + } + + // Append the given data to the array. + void Append(const SelfType& other) + { + Append(other.Data.Data, other.GetSize()); + } + + // Append the given data to the array. + void Append(const ValueType other[], UPInt count) + { + Data.Append(other, count); + } + + class Iterator + { + SelfType* pArray; + SPInt CurIndex; + + public: + Iterator() : pArray(0), CurIndex(-1) {} + Iterator(SelfType* parr, SPInt idx = 0) : pArray(parr), CurIndex(idx) {} + + bool operator==(const Iterator& it) const { return pArray == it.pArray && CurIndex == it.CurIndex; } + bool operator!=(const Iterator& it) const { return pArray != it.pArray || CurIndex != it.CurIndex; } + + Iterator& operator++() + { + if (pArray) + { + if (CurIndex < (SPInt)pArray->GetSize()) + ++CurIndex; + } + return *this; + } + Iterator operator++(int) + { + Iterator it(*this); + operator++(); + return it; + } + Iterator& operator--() + { + if (pArray) + { + if (CurIndex >= 0) + --CurIndex; + } + return *this; + } + Iterator operator--(int) + { + Iterator it(*this); + operator--(); + return it; + } + Iterator operator+(int delta) const + { + return Iterator(pArray, CurIndex + delta); + } + Iterator operator-(int delta) const + { + return Iterator(pArray, CurIndex - delta); + } + SPInt operator-(const Iterator& right) const + { + OVR_ASSERT(pArray == right.pArray); + return CurIndex - right.CurIndex; + } + ValueType& operator*() const { OVR_ASSERT(pArray); return (*pArray)[CurIndex]; } + ValueType* operator->() const { OVR_ASSERT(pArray); return &(*pArray)[CurIndex]; } + ValueType* GetPtr() const { OVR_ASSERT(pArray); return &(*pArray)[CurIndex]; } + + bool IsFinished() const { return !pArray || CurIndex < 0 || CurIndex >= (int)pArray->GetSize(); } + + void Remove() + { + if (!IsFinished()) + pArray->RemoveAt(CurIndex); + } + + SPInt GetIndex() const { return CurIndex; } + }; + + Iterator Begin() { return Iterator(this); } + Iterator End() { return Iterator(this, (SPInt)GetSize()); } + Iterator Last() { return Iterator(this, (SPInt)GetSize() - 1); } + + class ConstIterator + { + const SelfType* pArray; + SPInt CurIndex; + + public: + ConstIterator() : pArray(0), CurIndex(-1) {} + ConstIterator(const SelfType* parr, SPInt idx = 0) : pArray(parr), CurIndex(idx) {} + + bool operator==(const ConstIterator& it) const { return pArray == it.pArray && CurIndex == it.CurIndex; } + bool operator!=(const ConstIterator& it) const { return pArray != it.pArray || CurIndex != it.CurIndex; } + + ConstIterator& operator++() + { + if (pArray) + { + if (CurIndex < (int)pArray->GetSize()) + ++CurIndex; + } + return *this; + } + ConstIterator operator++(int) + { + ConstIterator it(*this); + operator++(); + return it; + } + ConstIterator& operator--() + { + if (pArray) + { + if (CurIndex >= 0) + --CurIndex; + } + return *this; + } + ConstIterator operator--(int) + { + ConstIterator it(*this); + operator--(); + return it; + } + ConstIterator operator+(int delta) const + { + return ConstIterator(pArray, CurIndex + delta); + } + ConstIterator operator-(int delta) const + { + return ConstIterator(pArray, CurIndex - delta); + } + SPInt operator-(const ConstIterator& right) const + { + OVR_ASSERT(pArray == right.pArray); + return CurIndex - right.CurIndex; + } + const ValueType& operator*() const { OVR_ASSERT(pArray); return (*pArray)[CurIndex]; } + const ValueType* operator->() const { OVR_ASSERT(pArray); return &(*pArray)[CurIndex]; } + const ValueType* GetPtr() const { OVR_ASSERT(pArray); return &(*pArray)[CurIndex]; } + + bool IsFinished() const { return !pArray || CurIndex < 0 || CurIndex >= (int)pArray->GetSize(); } + + SPInt GetIndex() const { return CurIndex; } + }; + ConstIterator Begin() const { return ConstIterator(this); } + ConstIterator End() const { return ConstIterator(this, (SPInt)GetSize()); } + ConstIterator Last() const { return ConstIterator(this, (SPInt)GetSize() - 1); } + +protected: + ArrayData Data; +}; + + + +//----------------------------------------------------------------------------------- +// ***** Array +// +// General purpose array for movable objects that require explicit +// construction/destruction. +template<class T, class SizePolicy=ArrayDefaultPolicy> +class Array : public ArrayBase<ArrayData<T, ContainerAllocator<T>, SizePolicy> > +{ +public: + typedef T ValueType; + typedef ContainerAllocator<T> AllocatorType; + typedef SizePolicy SizePolicyType; + typedef Array<T, SizePolicy> SelfType; + typedef ArrayBase<ArrayData<T, ContainerAllocator<T>, SizePolicy> > BaseType; + + Array() : BaseType() {} + Array(UPInt size) : BaseType(size) {} + Array(const SizePolicyType& p) : BaseType() { SetSizePolicy(p); } + Array(const SelfType& a) : BaseType(a) {} + const SelfType& operator=(const SelfType& a) { BaseType::operator=(a); return *this; } +}; + +// ***** ArrayPOD +// +// General purpose array for movable objects that DOES NOT require +// construction/destruction. Constructors and destructors are not called! +// Global heap is in use. +template<class T, class SizePolicy=ArrayDefaultPolicy> +class ArrayPOD : public ArrayBase<ArrayData<T, ContainerAllocator_POD<T>, SizePolicy> > +{ +public: + typedef T ValueType; + typedef ContainerAllocator_POD<T> AllocatorType; + typedef SizePolicy SizePolicyType; + typedef ArrayPOD<T, SizePolicy> SelfType; + typedef ArrayBase<ArrayData<T, ContainerAllocator_POD<T>, SizePolicy> > BaseType; + + ArrayPOD() : BaseType() {} + ArrayPOD(UPInt size) : BaseType(size) {} + ArrayPOD(const SizePolicyType& p) : BaseType() { SetSizePolicy(p); } + ArrayPOD(const SelfType& a) : BaseType(a) {} + const SelfType& operator=(const SelfType& a) { BaseType::operator=(a); return *this; } +}; + + +// ***** ArrayCPP +// +// General purpose, fully C++ compliant array. Can be used with non-movable data. +// Global heap is in use. +template<class T, class SizePolicy=ArrayDefaultPolicy> +class ArrayCPP : public ArrayBase<ArrayData<T, ContainerAllocator_CPP<T>, SizePolicy> > +{ +public: + typedef T ValueType; + typedef ContainerAllocator_CPP<T> AllocatorType; + typedef SizePolicy SizePolicyType; + typedef ArrayCPP<T, SizePolicy> SelfType; + typedef ArrayBase<ArrayData<T, ContainerAllocator_CPP<T>, SizePolicy> > BaseType; + + ArrayCPP() : BaseType() {} + ArrayCPP(UPInt size) : BaseType(size) {} + ArrayCPP(const SizePolicyType& p) : BaseType() { SetSizePolicy(p); } + ArrayCPP(const SelfType& a) : BaseType(a) {} + const SelfType& operator=(const SelfType& a) { BaseType::operator=(a); return *this; } +}; + + +// ***** ArrayCC +// +// A modification of the array that uses the given default value to +// construct the elements. The constructors and destructors are +// properly called, the objects must be movable. + +template<class T, class SizePolicy=ArrayDefaultPolicy> +class ArrayCC : public ArrayBase<ArrayDataCC<T, ContainerAllocator<T>, SizePolicy> > +{ +public: + typedef T ValueType; + typedef ContainerAllocator<T> AllocatorType; + typedef SizePolicy SizePolicyType; + typedef ArrayCC<T, SizePolicy> SelfType; + typedef ArrayBase<ArrayDataCC<T, ContainerAllocator<T>, SizePolicy> > BaseType; + + ArrayCC(const ValueType& defval) : BaseType(defval) {} + ArrayCC(const ValueType& defval, UPInt size) : BaseType(defval, size) {} + ArrayCC(const ValueType& defval, const SizePolicyType& p) : BaseType(defval) { SetSizePolicy(p); } + ArrayCC(const SelfType& a) : BaseType(a) {} + const SelfType& operator=(const SelfType& a) { BaseType::operator=(a); return *this; } +}; + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Atomic.cpp b/LibOVR/Src/Kernel/OVR_Atomic.cpp new file mode 100644 index 0000000..9ea6e76 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Atomic.cpp @@ -0,0 +1,162 @@ +/************************************************************************************ + +Filename : OVR_Atomic.cpp +Content : Contains atomic operations and inline fastest locking + functionality. Will contain #ifdefs for OS efficiency. + Have non-thread-safe implementation if not available. +Created : September 19, 2012 +Notes : + +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 "OVR_Atomic.h" +#include "OVR_Allocator.h" + +#ifdef OVR_ENABLE_THREADS + +// Include Windows 8-Metro compatible Synchronization API +#if defined(OVR_OS_WIN32) && defined(NTDDI_WIN8) && (NTDDI_VERSION >= NTDDI_WIN8) +#include <synchapi.h> +#endif + + +namespace OVR { + +// ***** Windows Lock implementation + +#if defined(OVR_OS_WIN32) + +// ***** Standard Win32 Lock implementation + +// Constructors +Lock::Lock(unsigned spinCount) +{ +#if defined(NTDDI_WIN8) && (NTDDI_VERSION >= NTDDI_WIN8) + // On Windows 8 we use InitializeCriticalSectionEx due to Metro-Compatibility + InitializeCriticalSectionEx(&cs, spinCount, + OVR_DEBUG_SELECT(NULL, CRITICAL_SECTION_NO_DEBUG_INFO)); +#else + // Spin count init critical section function prototype for Window NT + typedef BOOL (WINAPI *Function_InitializeCriticalSectionAndSpinCount) + (LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount); + + + // Try to load function dynamically so that we don't require NT + // On Windows NT we will use InitializeCriticalSectionAndSpinCount + static bool initTried = 0; + static Function_InitializeCriticalSectionAndSpinCount pInitFn = 0; + + if (!initTried) + { + HMODULE hmodule = ::LoadLibrary(OVR_STR("kernel32.dll")); + pInitFn = (Function_InitializeCriticalSectionAndSpinCount) + ::GetProcAddress(hmodule, "InitializeCriticalSectionAndSpinCount"); + initTried = true; + } + + // Initialize the critical section + if (pInitFn) + pInitFn(&cs, spinCount); + else + ::InitializeCriticalSection(&cs); +#endif + +} + + +Lock::~Lock() +{ + DeleteCriticalSection(&cs); +} + + +#endif + + +//------------------------------------------------------------------------------------- +// ***** SharedLock + +// This is a general purpose globally shared Lock implementation that should probably be +// moved to Kernel. +// May in theory busy spin-wait if we hit contention on first lock creation, +// but this shouldn't matter in practice since Lock* should be cached. + + +enum { LockInitMarker = 0xFFFFFFFF }; + +Lock* SharedLock::GetLockAddRef() +{ + int oldUseCount; + + do { + oldUseCount = UseCount; + if (oldUseCount == LockInitMarker) + continue; + + if (oldUseCount == 0) + { + // Initialize marker + if (AtomicOps<int>::CompareAndSet_Sync(&UseCount, 0, LockInitMarker)) + { + Construct<Lock>(Buffer); + do { } + while (!AtomicOps<int>::CompareAndSet_Sync(&UseCount, LockInitMarker, 1)); + return toLock(); + } + continue; + } + + } while (!AtomicOps<int>::CompareAndSet_NoSync(&UseCount, oldUseCount, oldUseCount + 1)); + + return toLock(); +} + +void SharedLock::ReleaseLock(Lock* plock) +{ + OVR_UNUSED(plock); + OVR_ASSERT(plock == toLock()); + + int oldUseCount; + + do { + oldUseCount = UseCount; + OVR_ASSERT(oldUseCount != LockInitMarker); + + if (oldUseCount == 1) + { + // Initialize marker + if (AtomicOps<int>::CompareAndSet_Sync(&UseCount, 1, LockInitMarker)) + { + Destruct<Lock>(toLock()); + + do { } + while (!AtomicOps<int>::CompareAndSet_Sync(&UseCount, LockInitMarker, 0)); + + return; + } + continue; + } + + } while (!AtomicOps<int>::CompareAndSet_NoSync(&UseCount, oldUseCount, oldUseCount - 1)); +} + +} // OVR + +#endif // OVR_ENABLE_THREADS diff --git a/LibOVR/Src/Kernel/OVR_Atomic.h b/LibOVR/Src/Kernel/OVR_Atomic.h new file mode 100644 index 0000000..b826251 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Atomic.h @@ -0,0 +1,890 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Atomic.h +Content : Contains atomic operations and inline fastest locking + functionality. Will contain #ifdefs for OS efficiency. + Have non-thread-safe implementaion if not available. +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ +#ifndef OVR_Atomic_h +#define OVR_Atomic_h + +#include "OVR_Types.h" + +// Include System thread functionality. +#if defined(OVR_OS_WIN32) +#include <windows.h> +#else +#include <pthread.h> +#endif + + +namespace OVR { + + +// ****** Declared classes + +// If there is NO thread support we implement AtomicOps and +// Lock objects as no-ops. The other classes are not defined. +template<class C> class AtomicOps; +template<class T> class AtomicInt; +template<class T> class AtomicPtr; + +class Lock; + + +//----------------------------------------------------------------------------------- +// ***** AtomicOps + +// Atomic operations are provided by the AtomicOps templates class, +// implemented through system-specific AtomicOpsRaw specializations. +// It provides several fundamental operations such as Exchange, ExchangeAdd +// CompareAndSet, and Store_Release. Each function includes several memory +// synchronization versions, important for multiprocessing CPUs with weak +// memory consistency. The following memory fencing strategies are supported: +// +// - NoSync. No memory synchronization is done for atomic op. +// - Release. All other memory writes are completed before atomic op +// writes its results. +// - Acquire. Further memory reads are forced to wait until atomic op +// executes, guaranteeing that the right values will be seen. +// - Sync. A combination of Release and Acquire. + + +// *** AtomicOpsRaw + +// AtomicOpsRaw is a specialized template that provides atomic operations +// used by AtomicOps. This class has two fundamental qualities: (1) it +// defines a type T of correct size, and (2) provides operations that work +// atomically, such as Exchange_Sync and CompareAndSet_Release. + +// AtomicOpsRawBase class contains shared constants/classes for AtomicOpsRaw. +// The primary thing is does is define sync class objects, whose destructor and +// constructor provide places to insert appropriate synchronization calls, on +// systems where such calls are necessary. So far, the breakdown is as follows: +// +// - X86 systems don't need custom syncs, since their exchange/atomic +// instructions are implicitly synchronized. +// - PowerPC requires lwsync/isync instructions that can use this mechanism. +// - If some other systems require a mechanism where syncing type is associated +// with a particular instruction, the default implementation (which implements +// all Sync, Acquire, and Release modes in terms of NoSync and fence) may not +// work. Ii that case it will need to be #ifdef-ed conditionally. + +struct AtomicOpsRawBase +{ +#if !defined(OVR_ENABLE_THREADS) || defined(OVR_CPU_X86) || defined(OVR_OS_WIN32) || defined(OVR_OS_IPHONE) + // Need to have empty constructor to avoid class 'unused' variable warning. + struct FullSync { inline FullSync() { } }; + struct AcquireSync { inline AcquireSync() { } }; + struct ReleaseSync { inline ReleaseSync() { } }; + +#elif defined(OVR_CPU_PPC64) || defined(OVR_CPU_PPC) + struct FullSync { inline FullSync() { asm volatile("sync\n"); } ~FullSync() { asm volatile("isync\n"); } }; + struct AcquireSync { inline AcquireSync() { } ~AcquireSync() { asm volatile("isync\n"); } }; + struct ReleaseSync { inline ReleaseSync() { asm volatile("sync\n"); } }; + +#elif defined(OVR_CPU_MIPS) + struct FullSync { inline FullSync() { asm volatile("sync\n"); } ~FullSync() { asm volatile("sync\n"); } }; + struct AcquireSync { inline AcquireSync() { } ~AcquireSync() { asm volatile("sync\n"); } }; + struct ReleaseSync { inline ReleaseSync() { asm volatile("sync\n"); } }; + +#elif defined(OVR_CPU_ARM) + struct FullSync { inline FullSync() { asm volatile("dmb\n"); } ~FullSync() { asm volatile("dmb\n"); } }; + struct AcquireSync { inline AcquireSync() { } ~AcquireSync() { asm volatile("dmb\n"); } }; + struct ReleaseSync { inline ReleaseSync() { asm volatile("dmb\n"); } }; + + +#elif defined(OVR_CC_GNU) && (__GNUC__ >= 4) + // __sync functions are already full sync + struct FullSync { inline FullSync() { } }; + struct AcquireSync { inline AcquireSync() { } }; + struct ReleaseSync { inline ReleaseSync() { } }; +#endif +}; + + +// 4-Byte raw data atomic op implementation class. +struct AtomicOpsRaw_4ByteImpl : public AtomicOpsRawBase +{ +#if !defined(OVR_ENABLE_THREADS) + + // Provide a type for no-thread-support cases. Used by AtomicOpsRaw_DefImpl. + typedef UInt32 T; + + // *** Thread - Safe Atomic Versions. + +#elif defined(OVR_OS_WIN32) + + // Use special defined for VC6, where volatile is not used and + // InterlockedCompareExchange is declared incorrectly. + typedef LONG T; +#if defined(OVR_CC_MSVC) && (OVR_CC_MSVC < 1300) + typedef T* InterlockTPtr; + typedef LPVOID ET; + typedef ET* InterlockETPtr; +#else + typedef volatile T* InterlockTPtr; + typedef T ET; + typedef InterlockTPtr InterlockETPtr; +#endif + inline static T Exchange_NoSync(volatile T* p, T val) { return InterlockedExchange((InterlockTPtr)p, val); } + inline static T ExchangeAdd_NoSync(volatile T* p, T val) { return InterlockedExchangeAdd((InterlockTPtr)p, val); } + inline static bool CompareAndSet_NoSync(volatile T* p, T c, T val) { return InterlockedCompareExchange((InterlockETPtr)p, (ET)val, (ET)c) == (ET)c; } + +#elif defined(OVR_CPU_PPC64) || defined(OVR_CPU_PPC) + typedef UInt32 T; + static inline UInt32 Exchange_NoSync(volatile UInt32 *i, UInt32 j) + { + UInt32 ret; + + asm volatile("1:\n\t" + "lwarx %[r],0,%[i]\n\t" + "stwcx. %[j],0,%[i]\n\t" + "bne- 1b\n" + : "+m" (*i), [r] "=&b" (ret) : [i] "b" (i), [j] "b" (j) : "cc", "memory"); + + return ret; + } + + static inline UInt32 ExchangeAdd_NoSync(volatile UInt32 *i, UInt32 j) + { + UInt32 dummy, ret; + + asm volatile("1:\n\t" + "lwarx %[r],0,%[i]\n\t" + "add %[o],%[r],%[j]\n\t" + "stwcx. %[o],0,%[i]\n\t" + "bne- 1b\n" + : "+m" (*i), [r] "=&b" (ret), [o] "=&r" (dummy) : [i] "b" (i), [j] "b" (j) : "cc", "memory"); + + return ret; + } + + static inline bool CompareAndSet_NoSync(volatile UInt32 *i, UInt32 c, UInt32 value) + { + UInt32 ret; + + asm volatile("1:\n\t" + "lwarx %[r],0,%[i]\n\t" + "cmpw 0,%[r],%[cmp]\n\t" + "mfcr %[r]\n\t" + "bne- 2f\n\t" + "stwcx. %[val],0,%[i]\n\t" + "bne- 1b\n\t" + "2:\n" + : "+m" (*i), [r] "=&b" (ret) : [i] "b" (i), [cmp] "b" (c), [val] "b" (value) : "cc", "memory"); + + return (ret & 0x20000000) ? 1 : 0; + } + +#elif defined(OVR_CPU_MIPS) + typedef UInt32 T; + + static inline UInt32 Exchange_NoSync(volatile UInt32 *i, UInt32 j) + { + UInt32 ret; + + asm volatile("1:\n\t" + "ll %[r],0(%[i])\n\t" + "sc %[j],0(%[i])\n\t" + "beq %[j],$0,1b\n\t" + "nop \n" + : "+m" (*i), [r] "=&d" (ret) : [i] "d" (i), [j] "d" (j) : "cc", "memory"); + + return ret; + } + + static inline UInt32 ExchangeAdd_NoSync(volatile UInt32 *i, UInt32 j) + { + UInt32 ret; + + asm volatile("1:\n\t" + "ll %[r],0(%[i])\n\t" + "addu %[j],%[r],%[j]\n\t" + "sc %[j],0(%[i])\n\t" + "beq %[j],$0,1b\n\t" + "nop \n" + : "+m" (*i), [r] "=&d" (ret) : [i] "d" (i), [j] "d" (j) : "cc", "memory"); + + return ret; + } + + static inline bool CompareAndSet_NoSync(volatile UInt32 *i, UInt32 c, UInt32 value) + { + UInt32 ret, dummy; + + asm volatile("1:\n\t" + "move %[r],$0\n\t" + "ll %[o],0(%[i])\n\t" + "bne %[o],%[c],2f\n\t" + "move %[r],%[v]\n\t" + "sc %[r],0(%[i])\n\t" + "beq %[r],$0,1b\n\t" + "nop \n\t" + "2:\n" + : "+m" (*i),[r] "=&d" (ret), [o] "=&d" (dummy) : [i] "d" (i), [c] "d" (c), [v] "d" (value) + : "cc", "memory"); + + return ret; + } + +#elif defined(OVR_CPU_ARM) && defined(OVR_CC_ARM) + typedef UInt32 T; + + static inline UInt32 Exchange_NoSync(volatile UInt32 *i, UInt32 j) + { + for(;;) + { + T r = __ldrex(i); + if (__strex(j, i) == 0) + return r; + } + } + static inline UInt32 ExchangeAdd_NoSync(volatile UInt32 *i, UInt32 j) + { + for(;;) + { + T r = __ldrex(i); + if (__strex(r + j, i) == 0) + return r; + } + } + + static inline bool CompareAndSet_NoSync(volatile UInt32 *i, UInt32 c, UInt32 value) + { + for(;;) + { + T r = __ldrex(i); + if (r != c) + return 0; + if (__strex(value, i) == 0) + return 1; + } + } + +#elif defined(OVR_CPU_ARM) + typedef UInt32 T; + + static inline UInt32 Exchange_NoSync(volatile UInt32 *i, UInt32 j) + { + UInt32 ret, dummy; + + asm volatile("1:\n\t" + "ldrex %[r],[%[i]]\n\t" + "strex %[t],%[j],[%[i]]\n\t" + "cmp %[t],#0\n\t" + "bne 1b\n\t" + : "+m" (*i), [r] "=&r" (ret), [t] "=&r" (dummy) : [i] "r" (i), [j] "r" (j) : "cc", "memory"); + + return ret; + } + + static inline UInt32 ExchangeAdd_NoSync(volatile UInt32 *i, UInt32 j) + { + UInt32 ret, dummy, test; + + asm volatile("1:\n\t" + "ldrex %[r],[%[i]]\n\t" + "add %[o],%[r],%[j]\n\t" + "strex %[t],%[o],[%[i]]\n\t" + "cmp %[t],#0\n\t" + "bne 1b\n\t" + : "+m" (*i), [r] "=&r" (ret), [o] "=&r" (dummy), [t] "=&r" (test) : [i] "r" (i), [j] "r" (j) : "cc", "memory"); + + return ret; + } + + static inline bool CompareAndSet_NoSync(volatile UInt32 *i, UInt32 c, UInt32 value) + { + UInt32 ret = 1, dummy, test; + + asm volatile("1:\n\t" + "ldrex %[o],[%[i]]\n\t" + "cmp %[o],%[c]\n\t" + "bne 2f\n\t" + "strex %[r],%[v],[%[i]]\n\t" + "cmp %[r],#0\n\t" + "bne 1b\n\t" + "2:\n" + : "+m" (*i),[r] "=&r" (ret), [o] "=&r" (dummy), [t] "=&r" (test) : [i] "r" (i), [c] "r" (c), [v] "r" (value) + : "cc", "memory"); + + return !ret; + } + +#elif defined(OVR_CPU_X86) + typedef UInt32 T; + + static inline UInt32 Exchange_NoSync(volatile UInt32 *i, UInt32 j) + { + asm volatile("xchgl %1,%[i]\n" + : "+m" (*i), "=q" (j) : [i] "m" (*i), "1" (j) : "cc", "memory"); + + return j; + } + + static inline UInt32 ExchangeAdd_NoSync(volatile UInt32 *i, UInt32 j) + { + asm volatile("lock; xaddl %1,%[i]\n" + : "+m" (*i), "+q" (j) : [i] "m" (*i) : "cc", "memory"); + + return j; + } + + static inline bool CompareAndSet_NoSync(volatile UInt32 *i, UInt32 c, UInt32 value) + { + UInt32 ret; + + asm volatile("lock; cmpxchgl %[v],%[i]\n" + : "+m" (*i), "=a" (ret) : [i] "m" (*i), "1" (c), [v] "q" (value) : "cc", "memory"); + + return (ret == c); + } + +#elif defined(OVR_CC_GNU) && (__GNUC__ >= 4 && __GNUC_MINOR__ >= 1) + + typedef UInt32 T; + + static inline T Exchange_NoSync(volatile T *i, T j) + { + T v; + do { + v = *i; + } while (!__sync_bool_compare_and_swap(i, v, j)); + return v; + } + + static inline T ExchangeAdd_NoSync(volatile T *i, T j) + { + return __sync_fetch_and_add(i, j); + } + + static inline bool CompareAndSet_NoSync(volatile T *i, T c, T value) + { + return __sync_bool_compare_and_swap(i, c, value); + } + +#endif // OS +}; + + +// 8-Byte raw data data atomic op implementation class. +// Currently implementation is provided only on systems with 64-bit pointers. +struct AtomicOpsRaw_8ByteImpl : public AtomicOpsRawBase +{ +#if !defined(OVR_64BIT_POINTERS) || !defined(OVR_ENABLE_THREADS) + + // Provide a type for no-thread-support cases. Used by AtomicOpsRaw_DefImpl. + typedef UInt64 T; + + // *** Thread - Safe OS specific versions. +#elif defined(OVR_OS_WIN32) + + // This is only for 64-bit systems. + typedef LONG64 T; + typedef volatile T* InterlockTPtr; + inline static T Exchange_NoSync(volatile T* p, T val) { return InterlockedExchange64((InterlockTPtr)p, val); } + inline static T ExchangeAdd_NoSync(volatile T* p, T val) { return InterlockedExchangeAdd64((InterlockTPtr)p, val); } + inline static bool CompareAndSet_NoSync(volatile T* p, T c, T val) { return InterlockedCompareExchange64((InterlockTPtr)p, val, c) == c; } + +#elif defined(OVR_CPU_PPC64) + + typedef UInt64 T; + + static inline UInt64 Exchange_NoSync(volatile UInt64 *i, UInt64 j) + { + UInt64 dummy, ret; + + asm volatile("1:\n\t" + "ldarx %[r],0,%[i]\n\t" + "mr %[o],%[j]\n\t" + "stdcx. %[o],0,%[i]\n\t" + "bne- 1b\n" + : "+m" (*i), [r] "=&b" (ret), [o] "=&r" (dummy) : [i] "b" (i), [j] "b" (j) : "cc"); + + return ret; + } + + static inline UInt64 ExchangeAdd_NoSync(volatile UInt64 *i, UInt64 j) + { + UInt64 dummy, ret; + + asm volatile("1:\n\t" + "ldarx %[r],0,%[i]\n\t" + "add %[o],%[r],%[j]\n\t" + "stdcx. %[o],0,%[i]\n\t" + "bne- 1b\n" + : "+m" (*i), [r] "=&b" (ret), [o] "=&r" (dummy) : [i] "b" (i), [j] "b" (j) : "cc"); + + return ret; + } + + static inline bool CompareAndSet_NoSync(volatile UInt64 *i, UInt64 c, UInt64 value) + { + UInt64 ret, dummy; + + asm volatile("1:\n\t" + "ldarx %[r],0,%[i]\n\t" + "cmpw 0,%[r],%[cmp]\n\t" + "mfcr %[r]\n\t" + "bne- 2f\n\t" + "stdcx. %[val],0,%[i]\n\t" + "bne- 1b\n\t" + "2:\n" + : "+m" (*i), [r] "=&b" (ret), [o] "=&r" (dummy) : [i] "b" (i), [cmp] "b" (c), [val] "b" (value) : "cc"); + + return (ret & 0x20000000) ? 1 : 0; + } + +#elif defined(OVR_CC_GNU) && (__GNUC__ >= 4 && __GNUC_MINOR__ >= 1) + + typedef UInt64 T; + + static inline T Exchange_NoSync(volatile T *i, T j) + { + T v; + do { + v = *i; + } while (!__sync_bool_compare_and_swap(i, v, j)); + return v; + } + + static inline T ExchangeAdd_NoSync(volatile T *i, T j) + { + return __sync_fetch_and_add(i, j); + } + + static inline bool CompareAndSet_NoSync(volatile T *i, T c, T value) + { + return __sync_bool_compare_and_swap(i, c, value); + } + +#endif // OS +}; + + +// Default implementation for AtomicOpsRaw; provides implementation of mem-fenced +// atomic operations where fencing is done with a sync object wrapped around a NoSync +// operation implemented in the base class. If such implementation is not possible +// on a given platform, #ifdefs can be used to disable it and then op functions can be +// implemented individually in the appropriate AtomicOpsRaw<size> class. + +template<class O> +struct AtomicOpsRaw_DefImpl : public O +{ + typedef typename O::T O_T; + typedef typename O::FullSync O_FullSync; + typedef typename O::AcquireSync O_AcquireSync; + typedef typename O::ReleaseSync O_ReleaseSync; + + // If there is no thread support, provide the default implementation. In this case, + // the base class (0) must still provide the T declaration. +#ifndef OVR_ENABLE_THREADS + + // Atomic exchange of val with argument. Returns old val. + inline static O_T Exchange_NoSync(volatile O_T* p, O_T val) { O_T old = *p; *p = val; return old; } + // Adds a new val to argument; returns its old val. + inline static O_T ExchangeAdd_NoSync(volatile O_T* p, O_T val) { O_T old = *p; *p += val; return old; } + // Compares the argument data with 'c' val. + // If succeeded, stores val int '*p' and returns true; otherwise returns false. + inline static bool CompareAndSet_NoSync(volatile O_T* p, O_T c, O_T val) { if (*p==c) { *p = val; return 1; } return 0; } + +#endif + + // If NoSync wrapped implementation may not be possible, it this block should be + // replaced with per-function implementation in O. + // "AtomicOpsRaw_DefImpl<O>::" prefix in calls below. + inline static O_T Exchange_Sync(volatile O_T* p, O_T val) { O_FullSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::Exchange_NoSync(p, val); } + inline static O_T Exchange_Release(volatile O_T* p, O_T val) { O_ReleaseSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::Exchange_NoSync(p, val); } + inline static O_T Exchange_Acquire(volatile O_T* p, O_T val) { O_AcquireSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::Exchange_NoSync(p, val); } + inline static O_T ExchangeAdd_Sync(volatile O_T* p, O_T val) { O_FullSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::ExchangeAdd_NoSync(p, val); } + inline static O_T ExchangeAdd_Release(volatile O_T* p, O_T val) { O_ReleaseSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::ExchangeAdd_NoSync(p, val); } + inline static O_T ExchangeAdd_Acquire(volatile O_T* p, O_T val) { O_AcquireSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::ExchangeAdd_NoSync(p, val); } + inline static bool CompareAndSet_Sync(volatile O_T* p, O_T c, O_T val) { O_FullSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::CompareAndSet_NoSync(p,c,val); } + inline static bool CompareAndSet_Release(volatile O_T* p, O_T c, O_T val) { O_ReleaseSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::CompareAndSet_NoSync(p,c,val); } + inline static bool CompareAndSet_Acquire(volatile O_T* p, O_T c, O_T val) { O_AcquireSync sync; OVR_UNUSED(sync); return AtomicOpsRaw_DefImpl<O>::CompareAndSet_NoSync(p,c,val); } + + // Loads and stores with memory fence. These have only the relevant versions. +#ifdef OVR_CPU_X86 + // On X86, Store_Release is implemented as exchange. Note that we can also + // consider 'sfence' in the future, although it is not as compatible with older CPUs. + inline static void Store_Release(volatile O_T* p, O_T val) { Exchange_Release(p, val); } +#else + inline static void Store_Release(volatile O_T* p, O_T val) { O_ReleaseSync sync; OVR_UNUSED(sync); *p = val; } +#endif + inline static O_T Load_Acquire(const volatile O_T* p) { O_AcquireSync sync; OVR_UNUSED(sync); return *p; } +}; + + +template<int size> +struct AtomicOpsRaw : public AtomicOpsRawBase { }; + +template<> +struct AtomicOpsRaw<4> : public AtomicOpsRaw_DefImpl<AtomicOpsRaw_4ByteImpl> +{ + // Ensure that assigned type size is correct. + AtomicOpsRaw() + { OVR_COMPILER_ASSERT(sizeof(AtomicOpsRaw_DefImpl<AtomicOpsRaw_4ByteImpl>::T) == 4); } +}; +template<> +struct AtomicOpsRaw<8> : public AtomicOpsRaw_DefImpl<AtomicOpsRaw_8ByteImpl> +{ + AtomicOpsRaw() + { OVR_COMPILER_ASSERT(sizeof(AtomicOpsRaw_DefImpl<AtomicOpsRaw_8ByteImpl>::T) == 8); } +}; + + +// *** AtomicOps - implementation of atomic Ops for specified class + +// Implements atomic ops on a class, provided that the object is either +// 4 or 8 bytes in size (depending on the AtomicOpsRaw specializations +// available). Relies on AtomicOpsRaw for much of implementation. + +template<class C> +class AtomicOps +{ + typedef AtomicOpsRaw<sizeof(C)> Ops; + typedef typename Ops::T T; + typedef volatile typename Ops::T* PT; + // We cast through unions to (1) avoid pointer size compiler warnings + // and (2) ensure that there are no problems with strict pointer aliasing. + union C2T_union { C c; T t; }; + +public: + // General purpose implementation for standard syncs. + inline static C Exchange_Sync(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::Exchange_Sync((PT)p, u.t); return u.c; } + inline static C Exchange_Release(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::Exchange_Release((PT)p, u.t); return u.c; } + inline static C Exchange_Acquire(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::Exchange_Acquire((PT)p, u.t); return u.c; } + inline static C Exchange_NoSync(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::Exchange_NoSync((PT)p, u.t); return u.c; } + inline static C ExchangeAdd_Sync(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::ExchangeAdd_Sync((PT)p, u.t); return u.c; } + inline static C ExchangeAdd_Release(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::ExchangeAdd_Release((PT)p, u.t); return u.c; } + inline static C ExchangeAdd_Acquire(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::ExchangeAdd_Acquire((PT)p, u.t); return u.c; } + inline static C ExchangeAdd_NoSync(volatile C* p, C val) { C2T_union u; u.c = val; u.t = Ops::ExchangeAdd_NoSync((PT)p, u.t); return u.c; } + inline static bool CompareAndSet_Sync(volatile C* p, C c, C val) { C2T_union u,cu; u.c = val; cu.c = c; return Ops::CompareAndSet_Sync((PT)p, cu.t, u.t); } + inline static bool CompareAndSet_Release(volatile C* p, C c, C val){ C2T_union u,cu; u.c = val; cu.c = c; return Ops::CompareAndSet_Release((PT)p, cu.t, u.t); } + inline static bool CompareAndSet_Relse(volatile C* p, C c, C val){ C2T_union u,cu; u.c = val; cu.c = c; return Ops::CompareAndSet_Acquire((PT)p, cu.t, u.t); } + inline static bool CompareAndSet_NoSync(volatile C* p, C c, C val) { C2T_union u,cu; u.c = val; cu.c = c; return Ops::CompareAndSet_NoSync((PT)p, cu.t, u.t); } + // Loads and stores with memory fence. These have only the relevant versions. + inline static void Store_Release(volatile C* p, C val) { C2T_union u; u.c = val; Ops::Store_Release((PT)p, u.t); } + inline static C Load_Acquire(const volatile C* p) { C2T_union u; u.t = Ops::Load_Acquire((PT)p); return u.c; } +}; + + + +// Atomic value base class - implements operations shared for integers and pointers. +template<class T> +class AtomicValueBase +{ +protected: + typedef AtomicOps<T> Ops; +public: + + volatile T Value; + + inline AtomicValueBase() { } + explicit inline AtomicValueBase(T val) { Ops::Store_Release(&Value, val); } + + // Most libraries (TBB and Joshua Scholar's) library do not do Load_Acquire + // here, since most algorithms do not require atomic loads. Needs some research. + inline operator T() const { return Value; } + + // *** Standard Atomic inlines + inline T Exchange_Sync(T val) { return Ops::Exchange_Sync(&Value, val); } + inline T Exchange_Release(T val) { return Ops::Exchange_Release(&Value, val); } + inline T Exchange_Acquire(T val) { return Ops::Exchange_Acquire(&Value, val); } + inline T Exchange_NoSync(T val) { return Ops::Exchange_NoSync(&Value, val); } + inline bool CompareAndSet_Sync(T c, T val) { return Ops::CompareAndSet_Sync(&Value, c, val); } + inline bool CompareAndSet_Release(T c, T val) { return Ops::CompareAndSet_Release(&Value, c, val); } + inline bool CompareAndSet_Acquire(T c, T val) { return Ops::CompareAndSet_Relse(&Value, c, val); } + inline bool CompareAndSet_NoSync(T c, T val) { return Ops::CompareAndSet_NoSync(&Value, c, val); } + // Load & Store. + inline void Store_Release(T val) { Ops::Store_Release(&Value, val); } + inline T Load_Acquire() const { return Ops::Load_Acquire(&Value); } +}; + + +// ***** AtomicPtr - Atomic pointer template + +// This pointer class supports atomic assignments with release, +// increment / decrement operations, and conditional compare + set. + +template<class T> +class AtomicPtr : public AtomicValueBase<T*> +{ + typedef typename AtomicValueBase<T*>::Ops Ops; + +public: + // Initialize pointer value to 0 by default; use Store_Release only with explicit constructor. + inline AtomicPtr() : AtomicValueBase<T*>() { this->Value = 0; } + explicit inline AtomicPtr(T* val) : AtomicValueBase<T*>(val) { } + + // Pointer access. + inline T* operator -> () const { return this->Load_Acquire(); } + + // It looks like it is convenient to have Load_Acquire characteristics + // for this, since that is convenient for algorithms such as linked + // list traversals that can be added to bu another thread. + inline operator T* () const { return this->Load_Acquire(); } + + + // *** Standard Atomic inlines (applicable to pointers) + + // ExhangeAdd considers pointer size for pointers. + template<class I> + inline T* ExchangeAdd_Sync(I incr) { return Ops::ExchangeAdd_Sync(&this->Value, ((T*)0) + incr); } + template<class I> + inline T* ExchangeAdd_Release(I incr) { return Ops::ExchangeAdd_Release(&this->Value, ((T*)0) + incr); } + template<class I> + inline T* ExchangeAdd_Acquire(I incr) { return Ops::ExchangeAdd_Acquire(&this->Value, ((T*)0) + incr); } + template<class I> + inline T* ExchangeAdd_NoSync(I incr) { return Ops::ExchangeAdd_NoSync(&this->Value, ((T*)0) + incr); } + + // *** Atomic Operators + + inline T* operator = (T* val) { this->Store_Release(val); return val; } + + template<class I> + inline T* operator += (I val) { return ExchangeAdd_Sync(val) + val; } + template<class I> + inline T* operator -= (I val) { return operator += (-val); } + + inline T* operator ++ () { return ExchangeAdd_Sync(1) + 1; } + inline T* operator -- () { return ExchangeAdd_Sync(-1) - 1; } + inline T* operator ++ (int) { return ExchangeAdd_Sync(1); } + inline T* operator -- (int) { return ExchangeAdd_Sync(-1); } +}; + + +// ***** AtomicInt - Atomic integer template + +// Implements an atomic integer type; the exact type to use is provided +// as an argument. Supports atomic Acquire / Release semantics, atomic +// arithmetic operations, and atomic conditional compare + set. + +template<class T> +class AtomicInt : public AtomicValueBase<T> +{ + typedef typename AtomicValueBase<T>::Ops Ops; + +public: + inline AtomicInt() : AtomicValueBase<T>() { } + explicit inline AtomicInt(T val) : AtomicValueBase<T>(val) { } + + + // *** Standard Atomic inlines (applicable to int) + inline T ExchangeAdd_Sync(T val) { return Ops::ExchangeAdd_Sync(&this->Value, val); } + inline T ExchangeAdd_Release(T val) { return Ops::ExchangeAdd_Release(&this->Value, val); } + inline T ExchangeAdd_Acquire(T val) { return Ops::ExchangeAdd_Acquire(&this->Value, val); } + inline T ExchangeAdd_NoSync(T val) { return Ops::ExchangeAdd_NoSync(&this->Value, val); } + // These increments could be more efficient because they don't return a value. + inline void Increment_Sync() { ExchangeAdd_Sync((T)1); } + inline void Increment_Release() { ExchangeAdd_Release((T)1); } + inline void Increment_Acquire() { ExchangeAdd_Acquire((T)1); } + inline void Increment_NoSync() { ExchangeAdd_NoSync((T)1); } + + // *** Atomic Operators + + inline T operator = (T val) { this->Store_Release(val); return val; } + inline T operator += (T val) { return ExchangeAdd_Sync(val) + val; } + inline T operator -= (T val) { return ExchangeAdd_Sync(0 - val) - val; } + + inline T operator ++ () { return ExchangeAdd_Sync((T)1) + 1; } + inline T operator -- () { return ExchangeAdd_Sync(((T)0)-1) - 1; } + inline T operator ++ (int) { return ExchangeAdd_Sync((T)1); } + inline T operator -- (int) { return ExchangeAdd_Sync(((T)0)-1); } + + // More complex atomic operations. Leave it to compiler whether to optimize them or not. + T operator &= (T arg) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp & arg; + } while(!this->CompareAndSet_Sync(comp, newVal)); + return newVal; + } + + T operator |= (T arg) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp | arg; + } while(!this->CompareAndSet_Sync(comp, newVal)); + return newVal; + } + + T operator ^= (T arg) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp ^ arg; + } while(!this->CompareAndSet_Sync(comp, newVal)); + return newVal; + } + + T operator *= (T arg) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp * arg; + } while(!this->CompareAndSet_Sync(comp, newVal)); + return newVal; + } + + T operator /= (T arg) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp / arg; + } while(!CompareAndSet_Sync(comp, newVal)); + return newVal; + } + + T operator >>= (unsigned bits) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp >> bits; + } while(!CompareAndSet_Sync(comp, newVal)); + return newVal; + } + + T operator <<= (unsigned bits) + { + T comp, newVal; + do { + comp = this->Value; + newVal = comp << bits; + } while(!this->CompareAndSet_Sync(comp, newVal)); + return newVal; + } +}; + + + +//----------------------------------------------------------------------------------- +// ***** Lock + +// Lock is a simplest and most efficient mutual-exclusion lock class. +// Unlike Mutex, it cannot be waited on. + +class Lock +{ + // NOTE: Locks are not allocatable and they themselves should not allocate + // memory by standard means. This is the case because StandardAllocator + // relies on this class. + // Make 'delete' private. Don't do this for 'new' since it can be redefined. + void operator delete(void*) {} + + + // *** Lock implementation for various platforms. + +#if !defined(OVR_ENABLE_THREADS) + +public: + // With no thread support, lock does nothing. + inline Lock() { } + inline Lock(unsigned) { } + inline ~Lock() { } + inline void DoLock() { } + inline void Unlock() { } + + // Windows. +#elif defined(OVR_OS_WIN32) + + CRITICAL_SECTION cs; +public: + Lock(unsigned spinCount = 0); + ~Lock(); + // Locking functions. + inline void DoLock() { ::EnterCriticalSection(&cs); } + inline void Unlock() { ::LeaveCriticalSection(&cs); } + +#else + pthread_mutex_t mutex; + +public: + static pthread_mutexattr_t RecursiveAttr; + static bool RecursiveAttrInit; + + Lock (unsigned dummy = 0) + { + OVR_UNUSED(dummy); + if (!RecursiveAttrInit) + { + pthread_mutexattr_init(&RecursiveAttr); + pthread_mutexattr_settype(&RecursiveAttr, PTHREAD_MUTEX_RECURSIVE); + RecursiveAttrInit = 1; + } + pthread_mutex_init(&mutex,&RecursiveAttr); + } + ~Lock () { pthread_mutex_destroy(&mutex); } + inline void DoLock() { pthread_mutex_lock(&mutex); } + inline void Unlock() { pthread_mutex_unlock(&mutex); } + +#endif // OVR_ENABLE_THREDS + + +public: + // Locker class, used for automatic locking + class Locker + { + public: + Lock *pLock; + inline Locker(Lock *plock) + { pLock = plock; pLock->DoLock(); } + inline ~Locker() + { pLock->Unlock(); } + }; +}; + + +//------------------------------------------------------------------------------------- +// Globally shared Lock implementation used for MessageHandlers, etc. + +class SharedLock +{ +public: + SharedLock() : UseCount(0) {} + + Lock* GetLockAddRef(); + void ReleaseLock(Lock* plock); + +private: + Lock* toLock() { return (Lock*)Buffer; } + + // UseCount and max alignment. + volatile int UseCount; + UInt64 Buffer[(sizeof(Lock)+sizeof(UInt64)-1)/sizeof(UInt64)]; +}; + + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Color.h b/LibOVR/Src/Kernel/OVR_Color.h new file mode 100644 index 0000000..cf536da --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Color.h @@ -0,0 +1,66 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Color.h +Content : Contains color struct. +Created : February 7, 2013 +Notes : + +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. + +************************************************************************************/ +#ifndef OVR_Color_h +#define OVR_Color_h + +#include "OVR_Types.h" + +namespace OVR { + +struct Color +{ + UByte R,G,B,A; + + Color() {} + + // Constructs color by channel. Alpha is set to 0xFF (fully visible) + // if not specified. + Color(unsigned char r,unsigned char g,unsigned char b, unsigned char a = 0xFF) + : R(r), G(g), B(b), A(a) { } + + // 0xAARRGGBB - Common HTML color Hex layout + Color(unsigned c) + : R((unsigned char)(c>>16)), G((unsigned char)(c>>8)), + B((unsigned char)c), A((unsigned char)(c>>24)) { } + + bool operator==(const Color& b) const + { + return R == b.R && G == b.G && B == b.B && A == b.A; + } + + void GetRGBA(float *r, float *g, float *b, float* a) const + { + *r = R / 255.0f; + *g = G / 255.0f; + *b = B / 255.0f; + *a = A / 255.0f; + } +}; + +} + +#endif diff --git a/LibOVR/Src/Kernel/OVR_ContainerAllocator.h b/LibOVR/Src/Kernel/OVR_ContainerAllocator.h new file mode 100644 index 0000000..afc0e6a --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_ContainerAllocator.h @@ -0,0 +1,267 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_ContainerAllocator.h +Content : Template allocators and constructors for containers. +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_ContainerAllocator_h +#define OVR_ContainerAllocator_h + +#include "OVR_Allocator.h" +#include <string.h> + + +namespace OVR { + + +//----------------------------------------------------------------------------------- +// ***** Container Allocator + +// ContainerAllocator serves as a template argument for allocations done by +// containers, such as Array and Hash; replacing it could allow allocator +// substitution in containers. + +class ContainerAllocatorBase +{ +public: + static void* Alloc(UPInt size) { return OVR_ALLOC(size); } + static void* Realloc(void* p, UPInt newSize) { return OVR_REALLOC(p, newSize); } + static void Free(void *p) { OVR_FREE(p); } +}; + + + +//----------------------------------------------------------------------------------- +// ***** Constructors, Destructors, Copiers + +// Plain Old Data - movable, no special constructors/destructor. +template<class T> +class ConstructorPOD +{ +public: + static void Construct(void *) {} + static void Construct(void *p, const T& source) + { + *(T*)p = source; + } + + // Same as above, but allows for a different type of constructor. + template <class S> + static void ConstructAlt(void *p, const S& source) + { + *(T*)p = source; + } + + static void ConstructArray(void*, UPInt) {} + + static void ConstructArray(void* p, UPInt count, const T& source) + { + UByte *pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + *(T*)pdata = source; + } + + static void ConstructArray(void* p, UPInt count, const T* psource) + { + memcpy(p, psource, sizeof(T) * count); + } + + static void Destruct(T*) {} + static void DestructArray(T*, UPInt) {} + + static void CopyArrayForward(T* dst, const T* src, UPInt count) + { + memmove(dst, src, count * sizeof(T)); + } + + static void CopyArrayBackward(T* dst, const T* src, UPInt count) + { + memmove(dst, src, count * sizeof(T)); + } + + static bool IsMovable() { return true; } +}; + + +//----------------------------------------------------------------------------------- +// ***** ConstructorMov +// +// Correct C++ construction and destruction for movable objects +template<class T> +class ConstructorMov +{ +public: + static void Construct(void* p) + { + OVR::Construct<T>(p); + } + + static void Construct(void* p, const T& source) + { + OVR::Construct<T>(p, source); + } + + // Same as above, but allows for a different type of constructor. + template <class S> + static void ConstructAlt(void* p, const S& source) + { + OVR::ConstructAlt<T,S>(p, source); + } + + static void ConstructArray(void* p, UPInt count) + { + UByte* pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + Construct(pdata); + } + + static void ConstructArray(void* p, UPInt count, const T& source) + { + UByte* pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + Construct(pdata, source); + } + + static void ConstructArray(void* p, UPInt count, const T* psource) + { + UByte* pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + Construct(pdata, *psource++); + } + + static void Destruct(T* p) + { + p->~T(); + OVR_UNUSED(p); // Suppress silly MSVC warning + } + + static void DestructArray(T* p, UPInt count) + { + p += count - 1; + for (UPInt i=0; i<count; ++i, --p) + p->~T(); + } + + static void CopyArrayForward(T* dst, const T* src, UPInt count) + { + memmove(dst, src, count * sizeof(T)); + } + + static void CopyArrayBackward(T* dst, const T* src, UPInt count) + { + memmove(dst, src, count * sizeof(T)); + } + + static bool IsMovable() { return true; } +}; + + +//----------------------------------------------------------------------------------- +// ***** ConstructorCPP +// +// Correct C++ construction and destruction for movable objects +template<class T> +class ConstructorCPP +{ +public: + static void Construct(void* p) + { + OVR::Construct<T>(p); + } + + static void Construct(void* p, const T& source) + { + OVR::Construct<T>(p, source); + } + + // Same as above, but allows for a different type of constructor. + template <class S> + static void ConstructAlt(void* p, const S& source) + { + OVR::ConstructAlt<T,S>(p, source); + } + + static void ConstructArray(void* p, UPInt count) + { + UByte* pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + Construct(pdata); + } + + static void ConstructArray(void* p, UPInt count, const T& source) + { + UByte* pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + Construct(pdata, source); + } + + static void ConstructArray(void* p, UPInt count, const T* psource) + { + UByte* pdata = (UByte*)p; + for (UPInt i=0; i< count; ++i, pdata += sizeof(T)) + Construct(pdata, *psource++); + } + + static void Destruct(T* p) + { + p->~T(); + OVR_UNUSED(p); // Suppress silly MSVC warning + } + + static void DestructArray(T* p, UPInt count) + { + p += count - 1; + for (UPInt i=0; i<count; ++i, --p) + p->~T(); + } + + static void CopyArrayForward(T* dst, const T* src, UPInt count) + { + for(UPInt i = 0; i < count; ++i) + dst[i] = src[i]; + } + + static void CopyArrayBackward(T* dst, const T* src, UPInt count) + { + for(UPInt i = count; i; --i) + dst[i-1] = src[i-1]; + } + + static bool IsMovable() { return false; } +}; + + +//----------------------------------------------------------------------------------- +// ***** Container Allocator with movement policy +// +// Simple wraps as specialized allocators +template<class T> struct ContainerAllocator_POD : ContainerAllocatorBase, ConstructorPOD<T> {}; +template<class T> struct ContainerAllocator : ContainerAllocatorBase, ConstructorMov<T> {}; +template<class T> struct ContainerAllocator_CPP : ContainerAllocatorBase, ConstructorCPP<T> {}; + + +} // OVR + + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Deque.h b/LibOVR/Src/Kernel/OVR_Deque.h new file mode 100644 index 0000000..ca242ad --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Deque.h @@ -0,0 +1,296 @@ +/************************************************************************************ + +Filename : OVR_Deque.h +Content : Deque container +Created : Nov. 15, 2013 +Authors : Dov Katz + +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. + +*************************************************************************************/ + +#ifndef OVR_Deque_h +#define OVR_Deque_h + +namespace OVR{ + +template <class Elem> +class Deque +{ +public: + enum + { + DefaultCapacity = 500 + }; + + Deque(int capacity = DefaultCapacity); + virtual ~Deque(void); + + virtual void PushBack (const Elem &Item); // Adds Item to the end + virtual void PushFront (const Elem &Item); // Adds Item to the beginning + virtual Elem PopBack (void); // Removes Item from the end + virtual Elem PopFront (void); // Removes Item from the beginning + virtual const Elem& PeekBack (int count = 0) const; // Returns count-th Item from the end + virtual const Elem& PeekFront (int count = 0) const; // Returns count-th Item from the beginning + + virtual inline UPInt GetSize (void) const; // Returns Number of Elements + virtual inline UPInt GetCapacity(void) const; // Returns the maximum possible number of elements + virtual void Clear (void); // Remove all elements + virtual inline bool IsEmpty () const; + virtual inline bool IsFull () const; + +protected: + Elem *Data; // The actual Data array + const int Capacity; // Deque capacity + int Beginning; // Index of the first element + int End; // Index of the next after last element + + // Instead of calculating the number of elements, using this variable + // is much more convenient. + int ElemCount; + +private: + Deque& operator= (const Deque& q) { }; // forbidden + Deque(const Deque<Elem> &OtherDeque) { }; +}; + +template <class Elem> +class InPlaceMutableDeque : public Deque<Elem> +{ +public: + InPlaceMutableDeque( int capacity = Deque<Elem>::DefaultCapacity ) : Deque<Elem>( capacity ) {} + virtual ~InPlaceMutableDeque() {}; + + using Deque<Elem>::PeekBack; + using Deque<Elem>::PeekFront; + virtual Elem& PeekBack (int count = 0); // Returns count-th Item from the end + virtual Elem& PeekFront (int count = 0); // Returns count-th Item from the beginning +}; + +// Same as Deque, but allows to write more elements than maximum capacity +// Old elements are lost as they are overwritten with the new ones +template <class Elem> +class CircularBuffer : public InPlaceMutableDeque<Elem> +{ +public: + CircularBuffer(int MaxSize = Deque<Elem>::DefaultCapacity) : InPlaceMutableDeque<Elem>(MaxSize) { }; + + // The following methods are inline as a workaround for a VS bug causing erroneous C4505 warnings + // See: http://stackoverflow.com/questions/3051992/compiler-warning-at-c-template-base-class + inline virtual void PushBack (const Elem &Item); // Adds Item to the end, overwriting the oldest element at the beginning if necessary + inline virtual void PushFront (const Elem &Item); // Adds Item to the beginning, overwriting the oldest element at the end if necessary +}; + +//---------------------------------------------------------------------------------- + +// Deque Constructor function +template <class Elem> +Deque<Elem>::Deque(int capacity) : +Capacity( capacity ), Beginning(0), End(0), ElemCount(0) +{ + Data = (Elem*) OVR_ALLOC(Capacity * sizeof(Elem)); + ConstructArray<Elem>(Data, Capacity); +} + +// Deque Destructor function +template <class Elem> +Deque<Elem>::~Deque(void) +{ + DestructArray<Elem>(Data, Capacity); + OVR_FREE(Data); +} + +template <class Elem> +void Deque<Elem>::Clear() +{ + Beginning = 0; + End = 0; + ElemCount = 0; + + DestructArray<Elem>(Data, Capacity); + ConstructArray<Elem>(Data, Capacity); +} + +// Push functions +template <class Elem> +void Deque<Elem>::PushBack(const Elem &Item) +{ + // Error Check: Make sure we aren't + // exceeding our maximum storage space + OVR_ASSERT( ElemCount < Capacity ); + + Data[ End++ ] = Item; + ++ElemCount; + + // Check for wrap-around + if (End >= Capacity) + End -= Capacity; +} + +template <class Elem> +void Deque<Elem>::PushFront(const Elem &Item) +{ + // Error Check: Make sure we aren't + // exceeding our maximum storage space + OVR_ASSERT( ElemCount < Capacity ); + + Beginning--; + // Check for wrap-around + if (Beginning < 0) + Beginning += Capacity; + + Data[ Beginning ] = Item; + ++ElemCount; +} + +// Pop functions +template <class Elem> +Elem Deque<Elem>::PopFront(void) +{ + // Error Check: Make sure we aren't reading from an empty Deque + OVR_ASSERT( ElemCount > 0 ); + + Elem ReturnValue = Data[ Beginning ]; + Destruct<Elem>(&Data[ Beginning ]); + Construct<Elem>(&Data[ Beginning ]); + + ++Beginning; + --ElemCount; + + // Check for wrap-around + if (Beginning >= Capacity) + Beginning -= Capacity; + + return ReturnValue; +} + +template <class Elem> +Elem Deque<Elem>::PopBack(void) +{ + // Error Check: Make sure we aren't reading from an empty Deque + OVR_ASSERT( ElemCount > 0 ); + + End--; + --ElemCount; + + // Check for wrap-around + if (End < 0) + End += Capacity; + + Elem ReturnValue = Data[ End ]; + Destruct<Elem>(&Data[ End ]); + Construct<Elem>(&Data[ End ]); + + return ReturnValue; +} + +// Peek functions +template <class Elem> +const Elem& Deque<Elem>::PeekFront(int count) const +{ + // Error Check: Make sure we aren't reading from an empty Deque + OVR_ASSERT( ElemCount > count ); + + int idx = Beginning + count; + if (idx >= Capacity) + idx -= Capacity; + return Data[ idx ]; +} + +template <class Elem> +const Elem& Deque<Elem>::PeekBack(int count) const +{ + // Error Check: Make sure we aren't reading from an empty Deque + OVR_ASSERT( ElemCount > count ); + + int idx = End - count - 1; + if (idx < 0) + idx += Capacity; + return Data[ idx ]; +} + +// Mutable Peek functions +template <class Elem> +Elem& InPlaceMutableDeque<Elem>::PeekFront(int count) +{ + // Error Check: Make sure we aren't reading from an empty Deque + OVR_ASSERT( Deque<Elem>::ElemCount > count ); + + int idx = Deque<Elem>::Beginning + count; + if (idx >= Deque<Elem>::Capacity) + idx -= Deque<Elem>::Capacity; + return Deque<Elem>::Data[ idx ]; +} + +template <class Elem> +Elem& InPlaceMutableDeque<Elem>::PeekBack(int count) +{ + // Error Check: Make sure we aren't reading from an empty Deque + OVR_ASSERT( Deque<Elem>::ElemCount > count ); + + int idx = Deque<Elem>::End - count - 1; + if (idx < 0) + idx += Deque<Elem>::Capacity; + return Deque<Elem>::Data[ idx ]; +} + +template <class Elem> +inline UPInt Deque<Elem>::GetCapacity(void) const +{ + return Deque<Elem>::Capacity; +} + +template <class Elem> +inline UPInt Deque<Elem>::GetSize(void) const +{ + return Deque<Elem>::ElemCount; +} + +template <class Elem> +inline bool Deque<Elem>::IsEmpty(void) const +{ + return Deque<Elem>::ElemCount==0; +} + +template <class Elem> +inline bool Deque<Elem>::IsFull(void) const +{ + return Deque<Elem>::ElemCount==Deque<Elem>::Capacity; +} + +// ******* CircularBuffer<Elem> ******* +// Push functions +template <class Elem> +void CircularBuffer<Elem>::PushBack(const Elem &Item) +{ + if (this->IsFull()) + this->PopFront(); + Deque<Elem>::PushBack(Item); +} + +template <class Elem> +void CircularBuffer<Elem>::PushFront(const Elem &Item) +{ + if (this->IsFull()) + this->PopBack(); + Deque<Elem>::PushFront(Item); +} + +}; + +#endif diff --git a/LibOVR/Src/Kernel/OVR_File.cpp b/LibOVR/Src/Kernel/OVR_File.cpp new file mode 100644 index 0000000..31ab516 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_File.cpp @@ -0,0 +1,582 @@ +/************************************************************************** + +Filename : OVR_File.cpp +Content : File wrapper class implementation (Win32) + +Created : April 5, 1999 +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. + +**************************************************************************/ + +#define GFILE_CXX + +// Standard C library (Captain Obvious guarantees!) +#include <stdio.h> + +#include "OVR_File.h" + +namespace OVR { + +// Buffered file adds buffering to an existing file +// FILEBUFFER_SIZE defines the size of internal buffer, while +// FILEBUFFER_TOLERANCE controls the amount of data we'll effectively try to buffer +#define FILEBUFFER_SIZE (8192-8) +#define FILEBUFFER_TOLERANCE 4096 + +// ** Constructor/Destructor + +// Hidden constructor +// Not supposed to be used +BufferedFile::BufferedFile() : DelegatedFile(0) +{ + pBuffer = (UByte*)OVR_ALLOC(FILEBUFFER_SIZE); + BufferMode = NoBuffer; + FilePos = 0; + Pos = 0; + DataSize = 0; +} + +// Takes another file as source +BufferedFile::BufferedFile(File *pfile) : DelegatedFile(pfile) +{ + pBuffer = (UByte*)OVR_ALLOC(FILEBUFFER_SIZE); + BufferMode = NoBuffer; + FilePos = pfile->LTell(); + Pos = 0; + DataSize = 0; +} + + +// Destructor +BufferedFile::~BufferedFile() +{ + // Flush in case there's data + if (pFile) + FlushBuffer(); + // Get rid of buffer + if (pBuffer) + OVR_FREE(pBuffer); +} + +/* +bool BufferedFile::VCopy(const Object &source) +{ + if (!DelegatedFile::VCopy(source)) + return 0; + + // Data members + BufferedFile *psource = (BufferedFile*)&source; + + // Buffer & the mode it's in + pBuffer = psource->pBuffer; + BufferMode = psource->BufferMode; + Pos = psource->Pos; + DataSize = psource->DataSize; + return 1; +} +*/ + +// Initializes buffering to a certain mode +bool BufferedFile::SetBufferMode(BufferModeType mode) +{ + if (!pBuffer) + return false; + if (mode == BufferMode) + return true; + + FlushBuffer(); + + // Can't set write mode if we can't write + if ((mode==WriteBuffer) && (!pFile || !pFile->IsWritable()) ) + return 0; + + // And SetMode + BufferMode = mode; + Pos = 0; + DataSize = 0; + return 1; +} + +// Flushes buffer +void BufferedFile::FlushBuffer() +{ + switch(BufferMode) + { + case WriteBuffer: + // Write data in buffer + FilePos += pFile->Write(pBuffer,Pos); + Pos = 0; + break; + + case ReadBuffer: + // Seek back & reset buffer data + if ((DataSize-Pos)>0) + FilePos = pFile->LSeek(-(int)(DataSize-Pos), Seek_Cur); + DataSize = 0; + Pos = 0; + break; + default: + // not handled! + break; + } +} + +// Reloads data for ReadBuffer +void BufferedFile::LoadBuffer() +{ + if (BufferMode == ReadBuffer) + { + // We should only reload once all of pre-loaded buffer is consumed. + OVR_ASSERT(Pos == DataSize); + + // WARNING: Right now LoadBuffer() assumes the buffer's empty + int sz = pFile->Read(pBuffer,FILEBUFFER_SIZE); + DataSize = sz<0 ? 0 : (unsigned)sz; + Pos = 0; + FilePos += DataSize; + } +} + + +// ** Overridden functions + +// We override all the functions that can possibly +// require buffer mode switch, flush, or extra calculations + +// Tell() requires buffer adjustment +int BufferedFile::Tell() +{ + if (BufferMode == ReadBuffer) + return int (FilePos - DataSize + Pos); + + int pos = pFile->Tell(); + // Adjust position based on buffer mode & data + if (pos!=-1) + { + OVR_ASSERT(BufferMode != ReadBuffer); + if (BufferMode == WriteBuffer) + pos += Pos; + } + return pos; +} + +SInt64 BufferedFile::LTell() +{ + if (BufferMode == ReadBuffer) + return FilePos - DataSize + Pos; + + SInt64 pos = pFile->LTell(); + if (pos!=-1) + { + OVR_ASSERT(BufferMode != ReadBuffer); + if (BufferMode == WriteBuffer) + pos += Pos; + } + return pos; +} + +int BufferedFile::GetLength() +{ + int len = pFile->GetLength(); + // If writing through buffer, file length may actually be bigger + if ((len!=-1) && (BufferMode==WriteBuffer)) + { + int currPos = pFile->Tell() + Pos; + if (currPos>len) + len = currPos; + } + return len; +} +SInt64 BufferedFile::LGetLength() +{ + SInt64 len = pFile->LGetLength(); + // If writing through buffer, file length may actually be bigger + if ((len!=-1) && (BufferMode==WriteBuffer)) + { + SInt64 currPos = pFile->LTell() + Pos; + if (currPos>len) + len = currPos; + } + return len; +} + +/* +bool BufferedFile::Stat(FileStats *pfs) +{ + // Have to fix up length is stat + if (pFile->Stat(pfs)) + { + if (BufferMode==WriteBuffer) + { + SInt64 currPos = pFile->LTell() + Pos; + if (currPos > pfs->Size) + { + pfs->Size = currPos; + // ?? + pfs->Blocks = (pfs->Size+511) >> 9; + } + } + return 1; + } + return 0; +} +*/ + +int BufferedFile::Write(const UByte *psourceBuffer, int numBytes) +{ + if ( (BufferMode==WriteBuffer) || SetBufferMode(WriteBuffer)) + { + // If not data space in buffer, flush + if ((FILEBUFFER_SIZE-(int)Pos)<numBytes) + { + FlushBuffer(); + // If bigger then tolerance, just write directly + if (numBytes>FILEBUFFER_TOLERANCE) + { + int sz = pFile->Write(psourceBuffer,numBytes); + if (sz > 0) + FilePos += sz; + return sz; + } + } + + // Enough space in buffer.. so copy to it + memcpy(pBuffer+Pos, psourceBuffer, numBytes); + Pos += numBytes; + return numBytes; + } + int sz = pFile->Write(psourceBuffer,numBytes); + if (sz > 0) + FilePos += sz; + return sz; +} + +int BufferedFile::Read(UByte *pdestBuffer, int numBytes) +{ + if ( (BufferMode==ReadBuffer) || SetBufferMode(ReadBuffer)) + { + // Data in buffer... copy it + if ((int)(DataSize-Pos) >= numBytes) + { + memcpy(pdestBuffer, pBuffer+Pos, numBytes); + Pos += numBytes; + return numBytes; + } + + // Not enough data in buffer, copy buffer + int readBytes = DataSize-Pos; + memcpy(pdestBuffer, pBuffer+Pos, readBytes); + numBytes -= readBytes; + pdestBuffer += readBytes; + Pos = DataSize; + + // Don't reload buffer if more then tolerance + // (No major advantage, and we don't want to write a loop) + if (numBytes>FILEBUFFER_TOLERANCE) + { + numBytes = pFile->Read(pdestBuffer,numBytes); + if (numBytes > 0) + { + FilePos += numBytes; + Pos = DataSize = 0; + } + return readBytes + ((numBytes==-1) ? 0 : numBytes); + } + + // Reload the buffer + // WARNING: Right now LoadBuffer() assumes the buffer's empty + LoadBuffer(); + if ((int)(DataSize-Pos) < numBytes) + numBytes = (int)DataSize-Pos; + + memcpy(pdestBuffer, pBuffer+Pos, numBytes); + Pos += numBytes; + return numBytes + readBytes; + + /* + // Alternative Read implementation. The one above is probably better + // due to FILEBUFFER_TOLERANCE. + int total = 0; + + do { + int bufferBytes = (int)(DataSize-Pos); + int copyBytes = (bufferBytes > numBytes) ? numBytes : bufferBytes; + + memcpy(pdestBuffer, pBuffer+Pos, copyBytes); + numBytes -= copyBytes; + pdestBuffer += copyBytes; + Pos += copyBytes; + total += copyBytes; + + if (numBytes == 0) + break; + LoadBuffer(); + + } while (DataSize > 0); + + return total; + */ + } + int sz = pFile->Read(pdestBuffer,numBytes); + if (sz > 0) + FilePos += sz; + return sz; +} + + +int BufferedFile::SkipBytes(int numBytes) +{ + int skippedBytes = 0; + + // Special case for skipping a little data in read buffer + if (BufferMode==ReadBuffer) + { + skippedBytes = (((int)DataSize-(int)Pos) >= numBytes) ? numBytes : (DataSize-Pos); + Pos += skippedBytes; + numBytes -= skippedBytes; + } + + if (numBytes) + { + numBytes = pFile->SkipBytes(numBytes); + // Make sure we return the actual number skipped, or error + if (numBytes!=-1) + { + skippedBytes += numBytes; + FilePos += numBytes; + Pos = DataSize = 0; + } + else if (skippedBytes <= 0) + skippedBytes = -1; + } + return skippedBytes; +} + +int BufferedFile::BytesAvailable() +{ + int available = pFile->BytesAvailable(); + // Adjust available size based on buffers + switch(BufferMode) + { + case ReadBuffer: + available += DataSize-Pos; + break; + case WriteBuffer: + available -= Pos; + if (available<0) + available= 0; + break; + default: + break; + } + return available; +} + +bool BufferedFile::Flush() +{ + FlushBuffer(); + return pFile->Flush(); +} + +// Seeking could be optimized better.. +int BufferedFile::Seek(int offset, int origin) +{ + if (BufferMode == ReadBuffer) + { + if (origin == Seek_Cur) + { + // Seek can fall either before or after Pos in the buffer, + // but it must be within bounds. + if (((unsigned(offset) + Pos)) <= DataSize) + { + Pos += offset; + return int (FilePos - DataSize + Pos); + } + + // Lightweight buffer "Flush". We do this to avoid an extra seek + // back operation which would take place if we called FlushBuffer directly. + origin = Seek_Set; + OVR_ASSERT(((FilePos - DataSize + Pos) + (UInt64)offset) < ~(UInt64)0); + offset = (int)(FilePos - DataSize + Pos) + offset; + Pos = DataSize = 0; + } + else if (origin == Seek_Set) + { + if (((unsigned)offset - (FilePos-DataSize)) <= DataSize) + { + OVR_ASSERT((FilePos-DataSize) < ~(UInt64)0); + Pos = (unsigned)offset - (unsigned)(FilePos-DataSize); + return offset; + } + Pos = DataSize = 0; + } + else + { + FlushBuffer(); + } + } + else + { + FlushBuffer(); + } + + /* + // Old Seek Logic + if (origin == Seek_Cur && offset + Pos < DataSize) + { + //OVR_ASSERT((FilePos - DataSize) >= (FilePos - DataSize + Pos + offset)); + Pos += offset; + OVR_ASSERT(int (Pos) >= 0); + return int (FilePos - DataSize + Pos); + } + else if (origin == Seek_Set && unsigned(offset) >= FilePos - DataSize && unsigned(offset) < FilePos) + { + Pos = unsigned(offset - FilePos + DataSize); + OVR_ASSERT(int (Pos) >= 0); + return int (FilePos - DataSize + Pos); + } + + FlushBuffer(); + */ + + + FilePos = pFile->Seek(offset,origin); + return int (FilePos); +} + +SInt64 BufferedFile::LSeek(SInt64 offset, int origin) +{ + if (BufferMode == ReadBuffer) + { + if (origin == Seek_Cur) + { + // Seek can fall either before or after Pos in the buffer, + // but it must be within bounds. + if (((unsigned(offset) + Pos)) <= DataSize) + { + Pos += (unsigned)offset; + return SInt64(FilePos - DataSize + Pos); + } + + // Lightweight buffer "Flush". We do this to avoid an extra seek + // back operation which would take place if we called FlushBuffer directly. + origin = Seek_Set; + offset = (SInt64)(FilePos - DataSize + Pos) + offset; + Pos = DataSize = 0; + } + else if (origin == Seek_Set) + { + if (((UInt64)offset - (FilePos-DataSize)) <= DataSize) + { + Pos = (unsigned)((UInt64)offset - (FilePos-DataSize)); + return offset; + } + Pos = DataSize = 0; + } + else + { + FlushBuffer(); + } + } + else + { + FlushBuffer(); + } + +/* + OVR_ASSERT(BufferMode != NoBuffer); + + if (origin == Seek_Cur && offset + Pos < DataSize) + { + Pos += int (offset); + return FilePos - DataSize + Pos; + } + else if (origin == Seek_Set && offset >= SInt64(FilePos - DataSize) && offset < SInt64(FilePos)) + { + Pos = unsigned(offset - FilePos + DataSize); + return FilePos - DataSize + Pos; + } + + FlushBuffer(); + */ + + FilePos = pFile->LSeek(offset,origin); + return FilePos; +} + +int BufferedFile::CopyFromStream(File *pstream, int byteSize) +{ + // We can't rely on overridden Write() + // because delegation doesn't override virtual pointers + // So, just re-implement + UByte buff[0x4000]; + int count = 0; + int szRequest, szRead, szWritten; + + while(byteSize) + { + szRequest = (byteSize > int(sizeof(buff))) ? int(sizeof(buff)) : byteSize; + + szRead = pstream->Read(buff,szRequest); + szWritten = 0; + if (szRead > 0) + szWritten = Write(buff,szRead); + + count +=szWritten; + byteSize-=szWritten; + if (szWritten < szRequest) + break; + } + return count; +} + +// Closing files +bool BufferedFile::Close() +{ + switch(BufferMode) + { + case WriteBuffer: + FlushBuffer(); + break; + case ReadBuffer: + // No need to seek back on close + BufferMode = NoBuffer; + break; + default: + break; + } + return pFile->Close(); +} + + +// ***** Global path helpers + +// Find trailing short filename in a path. +const char* OVR_CDECL GetShortFilename(const char* purl) +{ + UPInt len = OVR_strlen(purl); + for (UPInt i=len; i>0; i--) + if (purl[i]=='\\' || purl[i]=='/') + return purl+i+1; + return purl; +} + +} // OVR + diff --git a/LibOVR/Src/Kernel/OVR_File.h b/LibOVR/Src/Kernel/OVR_File.h new file mode 100644 index 0000000..a8dc615 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_File.h @@ -0,0 +1,529 @@ +/************************************************************************************ + +PublicHeader: Kernel +Filename : OVR_File.h +Content : Header for all internal file management - functions and structures + to be inherited by OS specific subclasses. +Created : September 19, 2012 +Notes : + +Notes : errno may not be preserved across use of BaseFile member functions + : Directories cannot be deleted while files opened from them are in use + (For the GetFullName function) + +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. + +************************************************************************************/ + +#ifndef OVR_File_h +#define OVR_File_h + +#include "OVR_RefCount.h" +#include "OVR_Std.h" +#include "OVR_Alg.h" + +#include <stdio.h> +#include "OVR_String.h" + +namespace OVR { + +// ***** Declared classes +class FileConstants; +class File; +class DelegatedFile; +class BufferedFile; + + +// ***** Flags for File & Directory accesses + +class FileConstants +{ +public: + + // *** File open flags + enum OpenFlags + { + Open_Read = 1, + Open_Write = 2, + Open_ReadWrite = 3, + + // Opens file and truncates it to zero length + // - file must have write permission + // - when used with Create, it opens an existing + // file and empties it or creates a new file + Open_Truncate = 4, + + // Creates and opens new file + // - does not erase contents if file already + // exists unless combined with Truncate + Open_Create = 8, + + // Returns an error value if the file already exists + Open_CreateOnly = 24, + + // Open file with buffering + Open_Buffered = 32 + }; + + // *** File Mode flags + enum Modes + { + Mode_Read = 0444, + Mode_Write = 0222, + Mode_Execute = 0111, + + Mode_ReadWrite = 0666 + }; + + // *** Seek operations + enum SeekOps + { + Seek_Set = 0, + Seek_Cur = 1, + Seek_End = 2 + }; + + // *** Errors + enum Errors + { + Error_FileNotFound = 0x1001, + Error_Access = 0x1002, + Error_IOError = 0x1003, + Error_DiskFull = 0x1004 + }; +}; + + +//----------------------------------------------------------------------------------- +// ***** File Class + +// The pure virtual base random-access file +// This is a base class to all files + +class File : public RefCountBase<File>, public FileConstants +{ +public: + File() { } + // ** Location Information + + // Returns a file name path relative to the 'reference' directory + // This is often a path that was used to create a file + // (this is not a global path, global path can be obtained with help of directory) + virtual const char* GetFilePath() = 0; + + + // ** File Information + + // Return 1 if file's usable (open) + virtual bool IsValid() = 0; + // Return 1 if file's writable, otherwise 0 + virtual bool IsWritable() = 0; + + // Return position + virtual int Tell() = 0; + virtual SInt64 LTell() = 0; + + // File size + virtual int GetLength() = 0; + virtual SInt64 LGetLength() = 0; + + // Returns file stats + // 0 for failure + //virtual bool Stat(FileStats *pfs) = 0; + + // Return errno-based error code + // Useful if any other function failed + virtual int GetErrorCode() = 0; + + + // ** Stream implementation & I/O + + // Blocking write, will write in the given number of bytes to the stream + // Returns : -1 for error + // Otherwise number of bytes read + virtual int Write(const UByte *pbufer, int numBytes) = 0; + // Blocking read, will read in the given number of bytes or less from the stream + // Returns : -1 for error + // Otherwise number of bytes read, + // if 0 or < numBytes, no more bytes available; end of file or the other side of stream is closed + virtual int Read(UByte *pbufer, int numBytes) = 0; + + // Skips (ignores) a given # of bytes + // Same return values as Read + virtual int SkipBytes(int numBytes) = 0; + + // Returns the number of bytes available to read from a stream without blocking + // For a file, this should generally be number of bytes to the end + virtual int BytesAvailable() = 0; + + // Causes any implementation's buffered data to be delivered to destination + // Return 0 for error + virtual bool Flush() = 0; + + + // Need to provide a more optimized implementation that doe snot necessarily involve a lot of seeking + inline bool IsEOF() { return !BytesAvailable(); } + + + // Seeking + // Returns new position, -1 for error + virtual int Seek(int offset, int origin=Seek_Set) = 0; + virtual SInt64 LSeek(SInt64 offset, int origin=Seek_Set) = 0; + // Seek simplification + int SeekToBegin() {return Seek(0); } + int SeekToEnd() {return Seek(0,Seek_End); } + int Skip(int numBytes) {return Seek(numBytes,Seek_Cur); } + + + // Appends other file data from a stream + // Return -1 for error, else # of bytes written + virtual int CopyFromStream(File *pstream, int byteSize) = 0; + + // Closes the file + // After close, file cannot be accessed + virtual bool Close() = 0; + + + // ***** Inlines for convenient primitive type serialization + + // Read/Write helpers +private: + UInt64 PRead64() { UInt64 v = 0; Read((UByte*)&v, 8); return v; } + UInt32 PRead32() { UInt32 v = 0; Read((UByte*)&v, 4); return v; } + UInt16 PRead16() { UInt16 v = 0; Read((UByte*)&v, 2); return v; } + UByte PRead8() { UByte v = 0; Read((UByte*)&v, 1); return v; } + void PWrite64(UInt64 v) { Write((UByte*)&v, 8); } + void PWrite32(UInt32 v) { Write((UByte*)&v, 4); } + void PWrite16(UInt16 v) { Write((UByte*)&v, 2); } + void PWrite8(UByte v) { Write((UByte*)&v, 1); } + +public: + + // Writing primitive types - Little Endian + inline void WriteUByte(UByte v) { PWrite8((UByte)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteSByte(SByte v) { PWrite8((UByte)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteUInt8(UByte v) { PWrite8((UByte)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteSInt8(SByte v) { PWrite8((UByte)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteUInt16(UInt16 v) { PWrite16((UInt16)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteSInt16(SInt16 v) { PWrite16((UInt16)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteUInt32(UInt32 v) { PWrite32((UInt32)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteSInt32(SInt32 v) { PWrite32((UInt32)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteUInt64(UInt64 v) { PWrite64((UInt64)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteSInt64(SInt64 v) { PWrite64((UInt64)Alg::ByteUtil::SystemToLE(v)); } + inline void WriteFloat(float v) { v = Alg::ByteUtil::SystemToLE(v); Write((UByte*)&v, 4); } + inline void WriteDouble(double v) { v = Alg::ByteUtil::SystemToLE(v); Write((UByte*)&v, 8); } + // Writing primitive types - Big Endian + inline void WriteUByteBE(UByte v) { PWrite8((UByte)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteSByteBE(SByte v) { PWrite8((UByte)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteUInt8BE(UInt16 v) { PWrite8((UByte)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteSInt8BE(SInt16 v) { PWrite8((UByte)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteUInt16BE(UInt16 v) { PWrite16((UInt16)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteSInt16BE(UInt16 v) { PWrite16((UInt16)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteUInt32BE(UInt32 v) { PWrite32((UInt32)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteSInt32BE(UInt32 v) { PWrite32((UInt32)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteUInt64BE(UInt64 v) { PWrite64((UInt64)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteSInt64BE(UInt64 v) { PWrite64((UInt64)Alg::ByteUtil::SystemToBE(v)); } + inline void WriteFloatBE(float v) { v = Alg::ByteUtil::SystemToBE(v); Write((UByte*)&v, 4); } + inline void WriteDoubleBE(double v) { v = Alg::ByteUtil::SystemToBE(v); Write((UByte*)&v, 8); } + + // Reading primitive types - Little Endian + inline UByte ReadUByte() { return (UByte)Alg::ByteUtil::LEToSystem(PRead8()); } + inline SByte ReadSByte() { return (SByte)Alg::ByteUtil::LEToSystem(PRead8()); } + inline UByte ReadUInt8() { return (UByte)Alg::ByteUtil::LEToSystem(PRead8()); } + inline SByte ReadSInt8() { return (SByte)Alg::ByteUtil::LEToSystem(PRead8()); } + inline UInt16 ReadUInt16() { return (UInt16)Alg::ByteUtil::LEToSystem(PRead16()); } + inline SInt16 ReadSInt16() { return (SInt16)Alg::ByteUtil::LEToSystem(PRead16()); } + inline UInt32 ReadUInt32() { return (UInt32)Alg::ByteUtil::LEToSystem(PRead32()); } + inline SInt32 ReadSInt32() { return (SInt32)Alg::ByteUtil::LEToSystem(PRead32()); } + inline UInt64 ReadUInt64() { return (UInt64)Alg::ByteUtil::LEToSystem(PRead64()); } + inline SInt64 ReadSInt64() { return (SInt64)Alg::ByteUtil::LEToSystem(PRead64()); } + inline float ReadFloat() { float v = 0.0f; Read((UByte*)&v, 4); return Alg::ByteUtil::LEToSystem(v); } + inline double ReadDouble() { double v = 0.0; Read((UByte*)&v, 8); return Alg::ByteUtil::LEToSystem(v); } + // Reading primitive types - Big Endian + inline UByte ReadUByteBE() { return (UByte)Alg::ByteUtil::BEToSystem(PRead8()); } + inline SByte ReadSByteBE() { return (SByte)Alg::ByteUtil::BEToSystem(PRead8()); } + inline UByte ReadUInt8BE() { return (UByte)Alg::ByteUtil::BEToSystem(PRead8()); } + inline SByte ReadSInt8BE() { return (SByte)Alg::ByteUtil::BEToSystem(PRead8()); } + inline UInt16 ReadUInt16BE() { return (UInt16)Alg::ByteUtil::BEToSystem(PRead16()); } + inline SInt16 ReadSInt16BE() { return (SInt16)Alg::ByteUtil::BEToSystem(PRead16()); } + inline UInt32 ReadUInt32BE() { return (UInt32)Alg::ByteUtil::BEToSystem(PRead32()); } + inline SInt32 ReadSInt32BE() { return (SInt32)Alg::ByteUtil::BEToSystem(PRead32()); } + inline UInt64 ReadUInt64BE() { return (UInt64)Alg::ByteUtil::BEToSystem(PRead64()); } + inline SInt64 ReadSInt64BE() { return (SInt64)Alg::ByteUtil::BEToSystem(PRead64()); } + inline float ReadFloatBE() { float v = 0.0f; Read((UByte*)&v, 4); return Alg::ByteUtil::BEToSystem(v); } + inline double ReadDoubleBE() { double v = 0.0; Read((UByte*)&v, 8); return Alg::ByteUtil::BEToSystem(v); } +}; + + +// *** Delegated File + +class DelegatedFile : public File +{ +protected: + // Delegating file pointer + Ptr<File> pFile; + + // Hidden default constructor + DelegatedFile() : pFile(0) { } + DelegatedFile(const DelegatedFile &source) : File() { OVR_UNUSED(source); } +public: + // Constructors + DelegatedFile(File *pfile) : pFile(pfile) { } + + // ** Location Information + virtual const char* GetFilePath() { return pFile->GetFilePath(); } + + // ** File Information + virtual bool IsValid() { return pFile && pFile->IsValid(); } + virtual bool IsWritable() { return pFile->IsWritable(); } +// virtual bool IsRecoverable() { return pFile->IsRecoverable(); } + + virtual int Tell() { return pFile->Tell(); } + virtual SInt64 LTell() { return pFile->LTell(); } + + virtual int GetLength() { return pFile->GetLength(); } + virtual SInt64 LGetLength() { return pFile->LGetLength(); } + + //virtual bool Stat(FileStats *pfs) { return pFile->Stat(pfs); } + + virtual int GetErrorCode() { return pFile->GetErrorCode(); } + + // ** Stream implementation & I/O + virtual int Write(const UByte *pbuffer, int numBytes) { return pFile->Write(pbuffer,numBytes); } + virtual int Read(UByte *pbuffer, int numBytes) { return pFile->Read(pbuffer,numBytes); } + + virtual int SkipBytes(int numBytes) { return pFile->SkipBytes(numBytes); } + + virtual int BytesAvailable() { return pFile->BytesAvailable(); } + + virtual bool Flush() { return pFile->Flush(); } + + // Seeking + virtual int Seek(int offset, int origin=Seek_Set) { return pFile->Seek(offset,origin); } + virtual SInt64 LSeek(SInt64 offset, int origin=Seek_Set) { return pFile->LSeek(offset,origin); } + + virtual int CopyFromStream(File *pstream, int byteSize) { return pFile->CopyFromStream(pstream,byteSize); } + + // Closing the file + virtual bool Close() { return pFile->Close(); } +}; + + +//----------------------------------------------------------------------------------- +// ***** Buffered File + +// This file class adds buffering to an existing file +// Buffered file never fails by itself; if there's not +// enough memory for buffer, no buffer's used + +class BufferedFile : public DelegatedFile +{ +protected: + enum BufferModeType + { + NoBuffer, + ReadBuffer, + WriteBuffer + }; + + // Buffer & the mode it's in + UByte* pBuffer; + BufferModeType BufferMode; + // Position in buffer + unsigned Pos; + // Data in buffer if reading + unsigned DataSize; + // Underlying file position + UInt64 FilePos; + + // Initializes buffering to a certain mode + bool SetBufferMode(BufferModeType mode); + // Flushes buffer + // WriteBuffer - write data to disk, ReadBuffer - reset buffer & fix file position + void FlushBuffer(); + // Loads data into ReadBuffer + // WARNING: Right now LoadBuffer() assumes the buffer's empty + void LoadBuffer(); + + // Hidden constructor + BufferedFile(); + inline BufferedFile(const BufferedFile &source) : DelegatedFile() { OVR_UNUSED(source); } +public: + + // Constructor + // - takes another file as source + BufferedFile(File *pfile); + ~BufferedFile(); + + + // ** Overridden functions + + // We override all the functions that can possibly + // require buffer mode switch, flush, or extra calculations + virtual int Tell(); + virtual SInt64 LTell(); + + virtual int GetLength(); + virtual SInt64 LGetLength(); + +// virtual bool Stat(GFileStats *pfs); + + virtual int Write(const UByte *pbufer, int numBytes); + virtual int Read(UByte *pbufer, int numBytes); + + virtual int SkipBytes(int numBytes); + + virtual int BytesAvailable(); + + virtual bool Flush(); + + virtual int Seek(int offset, int origin=Seek_Set); + virtual SInt64 LSeek(SInt64 offset, int origin=Seek_Set); + + virtual int CopyFromStream(File *pstream, int byteSize); + + virtual bool Close(); +}; + + +//----------------------------------------------------------------------------------- +// ***** Memory File + +class MemoryFile : public File +{ +public: + + const char* GetFilePath() { return FilePath.ToCStr(); } + + bool IsValid() { return Valid; } + bool IsWritable() { return false; } + + bool Flush() { return true; } + int GetErrorCode() { return 0; } + + int Tell() { return FileIndex; } + SInt64 LTell() { return (SInt64) FileIndex; } + + int GetLength() { return FileSize; } + SInt64 LGetLength() { return (SInt64) FileSize; } + + bool Close() + { + Valid = false; + return false; + } + + int CopyFromStream(File *pstream, int byteSize) + { OVR_UNUSED2(pstream, byteSize); + return 0; + } + + int Write(const UByte *pbuffer, int numBytes) + { OVR_UNUSED2(pbuffer, numBytes); + return 0; + } + + int Read(UByte *pbufer, int numBytes) + { + if (FileIndex + numBytes > FileSize) + { + numBytes = FileSize - FileIndex; + } + + if (numBytes > 0) + { + ::memcpy (pbufer, &FileData [FileIndex], numBytes); + + FileIndex += numBytes; + } + + return numBytes; + } + + int SkipBytes(int numBytes) + { + if (FileIndex + numBytes > FileSize) + { + numBytes = FileSize - FileIndex; + } + + FileIndex += numBytes; + + return numBytes; + } + + int BytesAvailable() + { + return (FileSize - FileIndex); + } + + int Seek(int offset, int origin = Seek_Set) + { + switch (origin) + { + case Seek_Set : FileIndex = offset; break; + case Seek_Cur : FileIndex += offset; break; + case Seek_End : FileIndex = FileSize - offset; break; + } + + return FileIndex; + } + + SInt64 LSeek(SInt64 offset, int origin = Seek_Set) + { + return (SInt64) Seek((int) offset, origin); + } + +public: + + MemoryFile (const String& fileName, const UByte *pBuffer, int buffSize) + : FilePath(fileName) + { + FileData = pBuffer; + FileSize = buffSize; + FileIndex = 0; + Valid = (!fileName.IsEmpty() && pBuffer && buffSize > 0) ? true : false; + } + + // pfileName should be encoded as UTF-8 to support international file names. + MemoryFile (const char* pfileName, const UByte *pBuffer, int buffSize) + : FilePath(pfileName) + { + FileData = pBuffer; + FileSize = buffSize; + FileIndex = 0; + Valid = (pfileName && pBuffer && buffSize > 0) ? true : false; + } +private: + + String FilePath; + const UByte *FileData; + int FileSize; + int FileIndex; + bool Valid; +}; + + +// ***** Global path helpers + +// Find trailing short filename in a path. +const char* OVR_CDECL GetShortFilename(const char* purl); + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_FileFILE.cpp b/LibOVR/Src/Kernel/OVR_FileFILE.cpp new file mode 100644 index 0000000..8478086 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_FileFILE.cpp @@ -0,0 +1,595 @@ +/************************************************************************** + +Filename : OVR_FileFILE.cpp +Content : File wrapper class implementation (Win32) + +Created : April 5, 1999 +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. + +**************************************************************************/ + +#define GFILE_CXX + +#include "OVR_Types.h" +#include "OVR_Log.h" + +// Standard C library (Captain Obvious guarantees!) +#include <stdio.h> +#ifndef OVR_OS_WINCE +#include <sys/stat.h> +#endif + +#include "OVR_SysFile.h" + +#ifndef OVR_OS_WINCE +#include <errno.h> +#endif + +namespace OVR { + +// ***** File interface + +// ***** FILEFile - C streams file + +static int SFerror () +{ + if (errno == ENOENT) + return FileConstants::Error_FileNotFound; + else if (errno == EACCES || errno == EPERM) + return FileConstants::Error_Access; + else if (errno == ENOSPC) + return FileConstants::Error_DiskFull; + else + return FileConstants::Error_IOError; +}; + +#ifdef OVR_OS_WIN32 +#include "windows.h" +// A simple helper class to disable/enable system error mode, if necessary +// Disabling happens conditionally only if a drive name is involved +class SysErrorModeDisabler +{ + BOOL Disabled; + UINT OldMode; +public: + SysErrorModeDisabler(const char* pfileName) + { + if (pfileName && (pfileName[0]!=0) && pfileName[1]==':') + { + Disabled = 1; + OldMode = ::SetErrorMode(SEM_FAILCRITICALERRORS); + } + else + Disabled = 0; + } + + ~SysErrorModeDisabler() + { + if (Disabled) ::SetErrorMode(OldMode); + } +}; +#else +class SysErrorModeDisabler +{ +public: + SysErrorModeDisabler(const char* pfileName) { OVR_UNUSED(pfileName); } +}; +#endif // OVR_OS_WIN32 + + +// This macro enables verification of I/O results after seeks against a pre-loaded +// full file buffer copy. This is generally not necessary, but can been used to debug +// memory corruptions; we've seen this fail due to EAX2/DirectSound corrupting memory +// under FMOD with XP64 (32-bit) and Realtek HA Audio driver. +//#define GFILE_VERIFY_SEEK_ERRORS + + +// This is the simplest possible file implementation, it wraps around the descriptor +// This file is delegated to by SysFile. + +class FILEFile : public File +{ +protected: + + // Allocated filename + String FileName; + + // File handle & open mode + bool Opened; + FILE* fs; + int OpenFlags; + // Error code for last request + int ErrorCode; + + int LastOp; + +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + UByte* pFileTestBuffer; + unsigned FileTestLength; + unsigned TestPos; // File pointer position during tests. +#endif + +public: + + FILEFile() + { + Opened = 0; FileName = ""; + +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + pFileTestBuffer =0; + FileTestLength =0; + TestPos =0; +#endif + } + // Initialize file by opening it + FILEFile(const String& fileName, int flags, int Mode); + // The 'pfileName' should be encoded as UTF-8 to support international file names. + FILEFile(const char* pfileName, int flags, int Mode); + + ~FILEFile() + { + if (Opened) + Close(); + } + + virtual const char* GetFilePath(); + + // ** File Information + virtual bool IsValid(); + virtual bool IsWritable(); + + // Return position / file size + virtual int Tell(); + virtual SInt64 LTell(); + virtual int GetLength(); + virtual SInt64 LGetLength(); + +// virtual bool Stat(FileStats *pfs); + virtual int GetErrorCode(); + + // ** Stream implementation & I/O + virtual int Write(const UByte *pbuffer, int numBytes); + virtual int Read(UByte *pbuffer, int numBytes); + virtual int SkipBytes(int numBytes); + virtual int BytesAvailable(); + virtual bool Flush(); + virtual int Seek(int offset, int origin); + virtual SInt64 LSeek(SInt64 offset, int origin); + + virtual int CopyFromStream(File *pStream, int byteSize); + virtual bool Close(); +private: + void init(); +}; + + +// Initialize file by opening it +FILEFile::FILEFile(const String& fileName, int flags, int mode) + : FileName(fileName), OpenFlags(flags) +{ + OVR_UNUSED(mode); + init(); +} + +// The 'pfileName' should be encoded as UTF-8 to support international file names. +FILEFile::FILEFile(const char* pfileName, int flags, int mode) + : FileName(pfileName), OpenFlags(flags) +{ + OVR_UNUSED(mode); + init(); +} + +void FILEFile::init() +{ + // Open mode for file's open + const char *omode = "rb"; + + if (OpenFlags & Open_Truncate) + { + if (OpenFlags & Open_Read) + omode = "w+b"; + else + omode = "wb"; + } + else if (OpenFlags & Open_Create) + { + if (OpenFlags & Open_Read) + omode = "a+b"; + else + omode = "ab"; + } + else if (OpenFlags & Open_Write) + omode = "r+b"; + +#ifdef OVR_OS_WIN32 + SysErrorModeDisabler disabler(FileName.ToCStr()); +#endif + +#if defined(OVR_CC_MSVC) && (OVR_CC_MSVC >= 1400) + wchar_t womode[16]; + wchar_t *pwFileName = (wchar_t*)OVR_ALLOC((UTF8Util::GetLength(FileName.ToCStr())+1) * sizeof(wchar_t)); + UTF8Util::DecodeString(pwFileName, FileName.ToCStr()); + OVR_ASSERT(strlen(omode) < sizeof(womode)/sizeof(womode[0])); + UTF8Util::DecodeString(womode, omode); + _wfopen_s(&fs, pwFileName, womode); + OVR_FREE(pwFileName); +#else + fs = fopen(FileName.ToCStr(), omode); +#endif + if (fs) + rewind (fs); + Opened = (fs != NULL); + // Set error code + if (!Opened) + ErrorCode = SFerror(); + else + { + // If we are testing file seek correctness, pre-load the entire file so + // that we can do comparison tests later. +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + TestPos = 0; + fseek(fs, 0, SEEK_END); + FileTestLength = ftell(fs); + fseek(fs, 0, SEEK_SET); + pFileTestBuffer = (UByte*)OVR_ALLOC(FileTestLength); + if (pFileTestBuffer) + { + OVR_ASSERT(FileTestLength == (unsigned)Read(pFileTestBuffer, FileTestLength)); + Seek(0, Seek_Set); + } +#endif + + ErrorCode = 0; + } + LastOp = 0; +} + + +const char* FILEFile::GetFilePath() +{ + return FileName.ToCStr(); +} + + +// ** File Information +bool FILEFile::IsValid() +{ + return Opened; +} +bool FILEFile::IsWritable() +{ + return IsValid() && (OpenFlags&Open_Write); +} +/* +bool FILEFile::IsRecoverable() +{ + return IsValid() && ((OpenFlags&OVR_FO_SAFETRUNC) == OVR_FO_SAFETRUNC); +} +*/ + +// Return position / file size +int FILEFile::Tell() +{ + int pos = (int)ftell (fs); + if (pos < 0) + ErrorCode = SFerror(); + return pos; +} + +SInt64 FILEFile::LTell() +{ + SInt64 pos = ftell(fs); + if (pos < 0) + ErrorCode = SFerror(); + return pos; +} + +int FILEFile::GetLength() +{ + int pos = Tell(); + if (pos >= 0) + { + Seek (0, Seek_End); + int size = Tell(); + Seek (pos, Seek_Set); + return size; + } + return -1; +} +SInt64 FILEFile::LGetLength() +{ + SInt64 pos = LTell(); + if (pos >= 0) + { + LSeek (0, Seek_End); + SInt64 size = LTell(); + LSeek (pos, Seek_Set); + return size; + } + return -1; +} + +int FILEFile::GetErrorCode() +{ + return ErrorCode; +} + +// ** Stream implementation & I/O +int FILEFile::Write(const UByte *pbuffer, int numBytes) +{ + if (LastOp && LastOp != Open_Write) + fflush(fs); + LastOp = Open_Write; + int written = (int) fwrite(pbuffer, 1, numBytes, fs); + if (written < numBytes) + ErrorCode = SFerror(); + +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + if (written > 0) + TestPos += written; +#endif + + return written; +} + +int FILEFile::Read(UByte *pbuffer, int numBytes) +{ + if (LastOp && LastOp != Open_Read) + fflush(fs); + LastOp = Open_Read; + int read = (int) fread(pbuffer, 1, numBytes, fs); + if (read < numBytes) + ErrorCode = SFerror(); + +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + if (read > 0) + { + // Read-in data must match our pre-loaded buffer data! + UByte* pcompareBuffer = pFileTestBuffer + TestPos; + for (int i=0; i< read; i++) + { + OVR_ASSERT(pcompareBuffer[i] == pbuffer[i]); + } + + //OVR_ASSERT(!memcmp(pFileTestBuffer + TestPos, pbuffer, read)); + TestPos += read; + OVR_ASSERT(ftell(fs) == (int)TestPos); + } +#endif + + return read; +} + +// Seeks ahead to skip bytes +int FILEFile::SkipBytes(int numBytes) +{ + SInt64 pos = LTell(); + SInt64 newPos = LSeek(numBytes, Seek_Cur); + + // Return -1 for major error + if ((pos==-1) || (newPos==-1)) + { + return -1; + } + //ErrorCode = ((NewPos-Pos)<numBytes) ? errno : 0; + + return int (newPos-(int)pos); +} + +// Return # of bytes till EOF +int FILEFile::BytesAvailable() +{ + SInt64 pos = LTell(); + SInt64 endPos = LGetLength(); + + // Return -1 for major error + if ((pos==-1) || (endPos==-1)) + { + ErrorCode = SFerror(); + return 0; + } + else + ErrorCode = 0; + + return int (endPos-(int)pos); +} + +// Flush file contents +bool FILEFile::Flush() +{ + return !fflush(fs); +} + +int FILEFile::Seek(int offset, int origin) +{ + int newOrigin = 0; + switch(origin) + { + case Seek_Set: newOrigin = SEEK_SET; break; + case Seek_Cur: newOrigin = SEEK_CUR; break; + case Seek_End: newOrigin = SEEK_END; break; + } + + if (newOrigin == SEEK_SET && offset == Tell()) + return Tell(); + + if (fseek (fs, offset, newOrigin)) + { +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + OVR_ASSERT(0); +#endif + return -1; + } + +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + // Track file position after seeks for read verification later. + switch(origin) + { + case Seek_Set: TestPos = offset; break; + case Seek_Cur: TestPos += offset; break; + case Seek_End: TestPos = FileTestLength + offset; break; + } + OVR_ASSERT((int)TestPos == Tell()); +#endif + + return (int)Tell(); +} + +SInt64 FILEFile::LSeek(SInt64 offset, int origin) +{ + return Seek((int)offset,origin); +} + +int FILEFile::CopyFromStream(File *pstream, int byteSize) +{ + UByte buff[0x4000]; + int count = 0; + int szRequest, szRead, szWritten; + + while (byteSize) + { + szRequest = (byteSize > int(sizeof(buff))) ? int(sizeof(buff)) : byteSize; + + szRead = pstream->Read(buff, szRequest); + szWritten = 0; + if (szRead > 0) + szWritten = Write(buff, szRead); + + count += szWritten; + byteSize -= szWritten; + if (szWritten < szRequest) + break; + } + return count; +} + + +bool FILEFile::Close() +{ +#ifdef OVR_FILE_VERIFY_SEEK_ERRORS + if (pFileTestBuffer) + { + OVR_FREE(pFileTestBuffer); + pFileTestBuffer = 0; + FileTestLength = 0; + } +#endif + + bool closeRet = !fclose(fs); + + if (!closeRet) + { + ErrorCode = SFerror(); + return 0; + } + else + { + Opened = 0; + fs = 0; + ErrorCode = 0; + } + + // Handle safe truncate + /* + if ((OpenFlags & OVR_FO_SAFETRUNC) == OVR_FO_SAFETRUNC) + { + // Delete original file (if it existed) + DWORD oldAttributes = FileUtilWin32::GetFileAttributes(FileName); + if (oldAttributes!=0xFFFFFFFF) + if (!FileUtilWin32::DeleteFile(FileName)) + { + // Try to remove the readonly attribute + FileUtilWin32::SetFileAttributes(FileName, oldAttributes & (~FILE_ATTRIBUTE_READONLY) ); + // And delete the file again + if (!FileUtilWin32::DeleteFile(FileName)) + return 0; + } + + // Rename temp file to real filename + if (!FileUtilWin32::MoveFile(TempName, FileName)) + { + //ErrorCode = errno; + return 0; + } + } + */ + return 1; +} + +/* +bool FILEFile::CloseCancel() +{ + bool closeRet = (bool)::CloseHandle(fd); + + if (!closeRet) + { + //ErrorCode = errno; + return 0; + } + else + { + Opened = 0; + fd = INVALID_HANDLE_VALUE; + ErrorCode = 0; + } + + // Handle safe truncate (delete tmp file, leave original unchanged) + if ((OpenFlags&OVR_FO_SAFETRUNC) == OVR_FO_SAFETRUNC) + if (!FileUtilWin32::DeleteFile(TempName)) + { + //ErrorCode = errno; + return 0; + } + return 1; +} +*/ + +Ptr<File> FileFILEOpen(const String& path, int flags, int mode) +{ + Ptr<File> result = *new FILEFile(path, flags, mode); + return result; +} + +// Helper function: obtain file information time. +bool SysFile::GetFileStat(FileStat* pfileStat, const String& path) +{ +#if defined(OVR_OS_WIN32) + // 64-bit implementation on Windows. + struct __stat64 fileStat; + // Stat returns 0 for success. + wchar_t *pwpath = (wchar_t*)OVR_ALLOC((UTF8Util::GetLength(path.ToCStr())+1)*sizeof(wchar_t)); + UTF8Util::DecodeString(pwpath, path.ToCStr()); + + int ret = _wstat64(pwpath, &fileStat); + OVR_FREE(pwpath); + if (ret) return false; +#else + struct stat fileStat; + // Stat returns 0 for success. + if (stat(path, &fileStat) != 0) + return false; +#endif + pfileStat->AccessTime = fileStat.st_atime; + pfileStat->ModifyTime = fileStat.st_mtime; + pfileStat->FileSize = fileStat.st_size; + return true; +} + +} // Namespace OVR diff --git a/LibOVR/Src/Kernel/OVR_Hash.h b/LibOVR/Src/Kernel/OVR_Hash.h new file mode 100644 index 0000000..04c4db8 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Hash.h @@ -0,0 +1,1302 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_Hash.h +Content : Template hash-table/set implementation +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Hash_h +#define OVR_Hash_h + +#include "OVR_ContainerAllocator.h" +#include "OVR_Alg.h" + +// 'new' operator is redefined/used in this file. +#undef new + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Hash Table Implementation + +// HastSet and Hash. +// +// Hash table, linear probing, internal chaining. One interesting/nice thing +// about this implementation is that the table itself is a flat chunk of memory +// containing no pointers, only relative indices. If the key and value types +// of the Hash contain no pointers, then the Hash can be serialized using raw IO. +// +// Never shrinks, unless you explicitly Clear() it. Expands on +// demand, though. For best results, if you know roughly how big your +// table will be, default it to that size when you create it. +// +// Key usability feature: +// +// 1. Allows node hash values to either be cached or not. +// +// 2. Allows for alternative keys with methods such as GetAlt(). Handy +// if you need to search nodes by their components; no need to create +// temporary nodes. +// + + +// *** Hash functors: +// +// IdentityHash - use when the key is already a good hash +// HFixedSizeHash - general hash based on object's in-memory representation. + + +// Hash is just the input value; can use this for integer-indexed hash tables. +template<class C> +class IdentityHash +{ +public: + UPInt operator()(const C& data) const + { return (UPInt) data; } +}; + +// Computes a hash of an object's representation. +template<class C> +class FixedSizeHash +{ +public: + // Alternative: "sdbm" hash function, suggested at same web page + // above, http::/www.cs.yorku.ca/~oz/hash.html + // This is somewhat slower then Bernstein, but it works way better than the above + // hash function for hashing large numbers of 32-bit ints. + static OVR_FORCE_INLINE UPInt SDBM_Hash(const void* data_in, UPInt size, UPInt seed = 5381) + { + const UByte* data = (const UByte*) data_in; + UPInt h = seed; + while (size > 0) + { + size--; + h = (h << 16) + (h << 6) - h + (UPInt)data[size]; + } + return h; + } + + UPInt operator()(const C& data) const + { + unsigned char* p = (unsigned char*) &data; + int size = sizeof(C); + + return SDBM_Hash(p, size); + } +}; + + + +// *** HashsetEntry Entry types. + +// Compact hash table Entry type that re-computes hash keys during hash traversal. +// Good to use if the hash function is cheap or the hash value is already cached in C. +template<class C, class HashF> +class HashsetEntry +{ +public: + // Internal chaining for collisions. + SPInt NextInChain; + C Value; + + HashsetEntry() + : NextInChain(-2) { } + HashsetEntry(const HashsetEntry& e) + : NextInChain(e.NextInChain), Value(e.Value) { } + HashsetEntry(const C& key, SPInt next) + : NextInChain(next), Value(key) { } + + bool IsEmpty() const { return NextInChain == -2; } + bool IsEndOfChain() const { return NextInChain == -1; } + + // Cached hash value access - can be optimized bu storing hash locally. + // Mask value only needs to be used if SetCachedHash is not implemented. + UPInt GetCachedHash(UPInt maskValue) const { return HashF()(Value) & maskValue; } + void SetCachedHash(UPInt) {} + + void Clear() + { + Value.~C(); // placement delete + NextInChain = -2; + } + // Free is only used from dtor of hash; Clear is used during regular operations: + // assignment, hash reallocations, value reassignments, so on. + void Free() { Clear(); } +}; + +// Hash table Entry type that caches the Entry hash value for nodes, so that it +// does not need to be re-computed during access. +template<class C, class HashF> +class HashsetCachedEntry +{ +public: + // Internal chaining for collisions. + SPInt NextInChain; + UPInt HashValue; + C Value; + + HashsetCachedEntry() + : NextInChain(-2) { } + HashsetCachedEntry(const HashsetCachedEntry& e) + : NextInChain(e.NextInChain), HashValue(e.HashValue), Value(e.Value) { } + HashsetCachedEntry(const C& key, SPInt next) + : NextInChain(next), Value(key) { } + + bool IsEmpty() const { return NextInChain == -2; } + bool IsEndOfChain() const { return NextInChain == -1; } + + // Cached hash value access - can be optimized bu storing hash locally. + // Mask value only needs to be used if SetCachedHash is not implemented. + UPInt GetCachedHash(UPInt maskValue) const { OVR_UNUSED(maskValue); return HashValue; } + void SetCachedHash(UPInt hashValue) { HashValue = hashValue; } + + void Clear() + { + Value.~C(); + NextInChain = -2; + } + // Free is only used from dtor of hash; Clear is used during regular operations: + // assignment, hash reallocations, value reassignments, so on. + void Free() { Clear(); } +}; + + +//----------------------------------------------------------------------------------- +// *** HashSet implementation - relies on either cached or regular entries. +// +// Use: Entry = HashsetCachedEntry<C, HashF> if hashes are expensive to +// compute and thus need caching in entries. +// Entry = HashsetEntry<C, HashF> if hashes are already externally cached. +// +template<class C, class HashF = FixedSizeHash<C>, + class AltHashF = HashF, + class Allocator = ContainerAllocator<C>, + class Entry = HashsetCachedEntry<C, HashF> > +class HashSetBase +{ + enum { HashMinSize = 8 }; + +public: + OVR_MEMORY_REDEFINE_NEW(HashSetBase) + + typedef HashSetBase<C, HashF, AltHashF, Allocator, Entry> SelfType; + + HashSetBase() : pTable(NULL) { } + HashSetBase(int sizeHint) : pTable(NULL) { SetCapacity(this, sizeHint); } + HashSetBase(const SelfType& src) : pTable(NULL) { Assign(this, src); } + + ~HashSetBase() + { + if (pTable) + { + // Delete the entries. + for (UPInt i = 0, n = pTable->SizeMask; i <= n; i++) + { + Entry* e = &E(i); + if (!e->IsEmpty()) + e->Free(); + } + + Allocator::Free(pTable); + pTable = NULL; + } + } + + + void Assign(const SelfType& src) + { + Clear(); + if (src.IsEmpty() == false) + { + SetCapacity(src.GetSize()); + + for (ConstIterator it = src.Begin(); it != src.End(); ++it) + { + Add(*it); + } + } + } + + + // Remove all entries from the HashSet table. + void Clear() + { + if (pTable) + { + // Delete the entries. + for (UPInt i = 0, n = pTable->SizeMask; i <= n; i++) + { + Entry* e = &E(i); + if (!e->IsEmpty()) + e->Clear(); + } + + Allocator::Free(pTable); + pTable = NULL; + } + } + + // Returns true if the HashSet is empty. + bool IsEmpty() const + { + return pTable == NULL || pTable->EntryCount == 0; + } + + + // Set a new or existing value under the key, to the value. + // Pass a different class of 'key' so that assignment reference object + // can be passed instead of the actual object. + template<class CRef> + void Set(const CRef& key) + { + UPInt hashValue = HashF()(key); + SPInt index = (SPInt)-1; + + if (pTable != NULL) + index = findIndexCore(key, hashValue & pTable->SizeMask); + + if (index >= 0) + { + E(index).Value = key; + } + else + { + // Entry under key doesn't exist. + add(key, hashValue); + } + } + + template<class CRef> + inline void Add(const CRef& key) + { + UPInt hashValue = HashF()(key); + add(key, hashValue); + } + + // Remove by alternative key. + template<class K> + void RemoveAlt(const K& key) + { + if (pTable == NULL) + return; + + UPInt hashValue = AltHashF()(key); + SPInt index = hashValue & pTable->SizeMask; + + Entry* e = &E(index); + + // If empty node or occupied by collider, we have nothing to remove. + if (e->IsEmpty() || (e->GetCachedHash(pTable->SizeMask) != (UPInt)index)) + return; + + // Save index + SPInt naturalIndex = index; + SPInt prevIndex = -1; + + while ((e->GetCachedHash(pTable->SizeMask) != (UPInt)naturalIndex) || !(e->Value == key)) + { + // Keep looking through the chain. + prevIndex = index; + index = e->NextInChain; + if (index == -1) + return; // End of chain, item not found + e = &E(index); + } + + // Found it - our item is at index + if (naturalIndex == index) + { + // If we have a follower, move it to us + if (!e->IsEndOfChain()) + { + Entry* enext = &E(e->NextInChain); + e->Clear(); + new (e) Entry(*enext); + // Point us to the follower's cell that will be cleared + e = enext; + } + } + else + { + // We are not at natural index, so deal with the prev items next index + E(prevIndex).NextInChain = e->NextInChain; + } + + // Clear us, of the follower cell that was moved. + e->Clear(); + pTable->EntryCount --; + // Should we check the size to condense hash? ... + } + + // Remove by main key. + template<class CRef> + void Remove(const CRef& key) + { + RemoveAlt(key); + } + + // Retrieve the pointer to a value under the given key. + // - If there's no value under the key, then return NULL. + // - If there is a value, return the pointer. + template<class K> + C* Get(const K& key) + { + SPInt index = findIndex(key); + if (index >= 0) + return &E(index).Value; + return 0; + } + + template<class K> + const C* Get(const K& key) const + { + SPInt index = findIndex(key); + if (index >= 0) + return &E(index).Value; + return 0; + } + + // Alternative key versions of Get. Used by Hash. + template<class K> + const C* GetAlt(const K& key) const + { + SPInt index = findIndexAlt(key); + if (index >= 0) + return &E(index).Value; + return 0; + } + + template<class K> + C* GetAlt(const K& key) + { + SPInt index = findIndexAlt(key); + if (index >= 0) + return &E(index).Value; + return 0; + } + + template<class K> + bool GetAlt(const K& key, C* pval) const + { + SPInt index = findIndexAlt(key); + if (index >= 0) + { + if (pval) + *pval = E(index).Value; + return true; + } + return false; + } + + + UPInt GetSize() const + { + return pTable == NULL ? 0 : (UPInt)pTable->EntryCount; + } + + + // Resize the HashSet table to fit one more Entry. Often this + // doesn't involve any action. + void CheckExpand() + { + if (pTable == NULL) + { + // Initial creation of table. Make a minimum-sized table. + setRawCapacity(HashMinSize); + } + else if (pTable->EntryCount * 5 > (pTable->SizeMask + 1) * 4) + { + // pTable is more than 5/4 ths full. Expand. + setRawCapacity((pTable->SizeMask + 1) * 2); + } + } + + // Hint the bucket count to >= n. + void Resize(UPInt n) + { + // Not really sure what this means in relation to + // STLport's hash_map... they say they "increase the + // bucket count to at least n" -- but does that mean + // their real capacity after Resize(n) is more like + // n*2 (since they do linked-list chaining within + // buckets?). + SetCapacity(n); + } + + // Size the HashSet so that it can comfortably contain the given + // number of elements. If the HashSet already contains more + // elements than newSize, then this may be a no-op. + void SetCapacity(UPInt newSize) + { + UPInt newRawSize = (newSize * 5) / 4; + if (newRawSize <= GetSize()) + return; + setRawCapacity(newRawSize); + } + + // Disable inappropriate 'operator ->' warning on MSVC6. +#ifdef OVR_CC_MSVC +#if (OVR_CC_MSVC < 1300) +# pragma warning(disable : 4284) +#endif +#endif + + // Iterator API, like STL. + struct ConstIterator + { + const C& operator * () const + { + OVR_ASSERT(Index >= 0 && Index <= (SPInt)pHash->pTable->SizeMask); + return pHash->E(Index).Value; + } + + const C* operator -> () const + { + OVR_ASSERT(Index >= 0 && Index <= (SPInt)pHash->pTable->SizeMask); + return &pHash->E(Index).Value; + } + + void operator ++ () + { + // Find next non-empty Entry. + if (Index <= (SPInt)pHash->pTable->SizeMask) + { + Index++; + while ((UPInt)Index <= pHash->pTable->SizeMask && + pHash->E(Index).IsEmpty()) + { + Index++; + } + } + } + + bool operator == (const ConstIterator& it) const + { + if (IsEnd() && it.IsEnd()) + { + return true; + } + else + { + return (pHash == it.pHash) && (Index == it.Index); + } + } + + bool operator != (const ConstIterator& it) const + { + return ! (*this == it); + } + + + bool IsEnd() const + { + return (pHash == NULL) || + (pHash->pTable == NULL) || + (Index > (SPInt)pHash->pTable->SizeMask); + } + + ConstIterator() + : pHash(NULL), Index(0) + { } + + public: + // Constructor was intentionally made public to allow create + // iterator with arbitrary index. + ConstIterator(const SelfType* h, SPInt index) + : pHash(h), Index(index) + { } + + const SelfType* GetContainer() const + { + return pHash; + } + SPInt GetIndex() const + { + return Index; + } + + protected: + friend class HashSetBase<C, HashF, AltHashF, Allocator, Entry>; + + const SelfType* pHash; + SPInt Index; + }; + + friend struct ConstIterator; + + + // Non-const Iterator; Get most of it from ConstIterator. + struct Iterator : public ConstIterator + { + // Allow non-const access to entries. + C& operator*() const + { + OVR_ASSERT(ConstIterator::Index >= 0 && ConstIterator::Index <= (SPInt)ConstIterator::pHash->pTable->SizeMask); + return const_cast<SelfType*>(ConstIterator::pHash)->E(ConstIterator::Index).Value; + } + + C* operator->() const + { + return &(operator*()); + } + + Iterator() + : ConstIterator(NULL, 0) + { } + + // Removes current element from Hash + void Remove() + { + RemoveAlt(operator*()); + } + + template <class K> + void RemoveAlt(const K& key) + { + SelfType* phash = const_cast<SelfType*>(ConstIterator::pHash); + //Entry* ee = &phash->E(ConstIterator::Index); + //const C& key = ee->Value; + + UPInt hashValue = AltHashF()(key); + SPInt index = hashValue & phash->pTable->SizeMask; + + Entry* e = &phash->E(index); + + // If empty node or occupied by collider, we have nothing to remove. + if (e->IsEmpty() || (e->GetCachedHash(phash->pTable->SizeMask) != (UPInt)index)) + return; + + // Save index + SPInt naturalIndex = index; + SPInt prevIndex = -1; + + while ((e->GetCachedHash(phash->pTable->SizeMask) != (UPInt)naturalIndex) || !(e->Value == key)) + { + // Keep looking through the chain. + prevIndex = index; + index = e->NextInChain; + if (index == -1) + return; // End of chain, item not found + e = &phash->E(index); + } + + if (index == (SPInt)ConstIterator::Index) + { + // Found it - our item is at index + if (naturalIndex == index) + { + // If we have a follower, move it to us + if (!e->IsEndOfChain()) + { + Entry* enext = &phash->E(e->NextInChain); + e->Clear(); + new (e) Entry(*enext); + // Point us to the follower's cell that will be cleared + e = enext; + --ConstIterator::Index; + } + } + else + { + // We are not at natural index, so deal with the prev items next index + phash->E(prevIndex).NextInChain = e->NextInChain; + } + + // Clear us, of the follower cell that was moved. + e->Clear(); + phash->pTable->EntryCount --; + } + else + OVR_ASSERT(0); //? + } + + private: + friend class HashSetBase<C, HashF, AltHashF, Allocator, Entry>; + + Iterator(SelfType* h, SPInt i0) + : ConstIterator(h, i0) + { } + }; + + friend struct Iterator; + + Iterator Begin() + { + if (pTable == 0) + return Iterator(NULL, 0); + + // Scan till we hit the First valid Entry. + UPInt i0 = 0; + while (i0 <= pTable->SizeMask && E(i0).IsEmpty()) + { + i0++; + } + return Iterator(this, i0); + } + Iterator End() { return Iterator(NULL, 0); } + + ConstIterator Begin() const { return const_cast<SelfType*>(this)->Begin(); } + ConstIterator End() const { return const_cast<SelfType*>(this)->End(); } + + template<class K> + Iterator Find(const K& key) + { + SPInt index = findIndex(key); + if (index >= 0) + return Iterator(this, index); + return Iterator(NULL, 0); + } + + template<class K> + Iterator FindAlt(const K& key) + { + SPInt index = findIndexAlt(key); + if (index >= 0) + return Iterator(this, index); + return Iterator(NULL, 0); + } + + template<class K> + ConstIterator Find(const K& key) const { return const_cast<SelfType*>(this)->Find(key); } + + template<class K> + ConstIterator FindAlt(const K& key) const { return const_cast<SelfType*>(this)->FindAlt(key); } + +private: + // Find the index of the matching Entry. If no match, then return -1. + template<class K> + SPInt findIndex(const K& key) const + { + if (pTable == NULL) + return -1; + UPInt hashValue = HashF()(key) & pTable->SizeMask; + return findIndexCore(key, hashValue); + } + + template<class K> + SPInt findIndexAlt(const K& key) const + { + if (pTable == NULL) + return -1; + UPInt hashValue = AltHashF()(key) & pTable->SizeMask; + return findIndexCore(key, hashValue); + } + + // Find the index of the matching Entry. If no match, then return -1. + template<class K> + SPInt findIndexCore(const K& key, UPInt hashValue) const + { + // Table must exist. + OVR_ASSERT(pTable != 0); + // Hash key must be 'and-ed' by the caller. + OVR_ASSERT((hashValue & ~pTable->SizeMask) == 0); + + UPInt index = hashValue; + const Entry* e = &E(index); + + // If empty or occupied by a collider, not found. + if (e->IsEmpty() || (e->GetCachedHash(pTable->SizeMask) != index)) + return -1; + + while(1) + { + OVR_ASSERT(e->GetCachedHash(pTable->SizeMask) == hashValue); + + if (e->GetCachedHash(pTable->SizeMask) == hashValue && e->Value == key) + { + // Found it. + return index; + } + // Values can not be equal at this point. + // That would mean that the hash key for the same value differs. + OVR_ASSERT(!(e->Value == key)); + + // Keep looking through the chain. + index = e->NextInChain; + if (index == (UPInt)-1) + break; // end of chain + + e = &E(index); + OVR_ASSERT(!e->IsEmpty()); + } + return -1; + } + + + // Add a new value to the HashSet table, under the specified key. + template<class CRef> + void add(const CRef& key, UPInt hashValue) + { + CheckExpand(); + hashValue &= pTable->SizeMask; + + pTable->EntryCount++; + + SPInt index = hashValue; + Entry* naturalEntry = &(E(index)); + + if (naturalEntry->IsEmpty()) + { + // Put the new Entry in. + new (naturalEntry) Entry(key, -1); + } + else + { + // Find a blank spot. + SPInt blankIndex = index; + do { + blankIndex = (blankIndex + 1) & pTable->SizeMask; + } while(!E(blankIndex).IsEmpty()); + + Entry* blankEntry = &E(blankIndex); + + if (naturalEntry->GetCachedHash(pTable->SizeMask) == (UPInt)index) + { + // Collision. Link into this chain. + + // Move existing list head. + new (blankEntry) Entry(*naturalEntry); // placement new, copy ctor + + // Put the new info in the natural Entry. + naturalEntry->Value = key; + naturalEntry->NextInChain = blankIndex; + } + else + { + // Existing Entry does not naturally + // belong in this slot. Existing + // Entry must be moved. + + // Find natural location of collided element (i.e. root of chain) + SPInt collidedIndex = naturalEntry->GetCachedHash(pTable->SizeMask); + OVR_ASSERT(collidedIndex >= 0 && collidedIndex <= (SPInt)pTable->SizeMask); + for (;;) + { + Entry* e = &E(collidedIndex); + if (e->NextInChain == index) + { + // Here's where we need to splice. + new (blankEntry) Entry(*naturalEntry); + e->NextInChain = blankIndex; + break; + } + collidedIndex = e->NextInChain; + OVR_ASSERT(collidedIndex >= 0 && collidedIndex <= (SPInt)pTable->SizeMask); + } + + // Put the new data in the natural Entry. + naturalEntry->Value = key; + naturalEntry->NextInChain = -1; + } + } + + // Record hash value: has effect only if cached node is used. + naturalEntry->SetCachedHash(hashValue); + } + + // Index access helpers. + Entry& E(UPInt index) + { + // Must have pTable and access needs to be within bounds. + OVR_ASSERT(index <= pTable->SizeMask); + return *(((Entry*) (pTable + 1)) + index); + } + const Entry& E(UPInt index) const + { + OVR_ASSERT(index <= pTable->SizeMask); + return *(((Entry*) (pTable + 1)) + index); + } + + + // Resize the HashSet table to the given size (Rehash the + // contents of the current table). The arg is the number of + // HashSet table entries, not the number of elements we should + // actually contain (which will be less than this). + void setRawCapacity(UPInt newSize) + { + if (newSize == 0) + { + // Special case. + Clear(); + return; + } + + // Minimum size; don't incur rehashing cost when expanding + // very small tables. Not that we perform this check before + // 'log2f' call to avoid fp exception with newSize == 1. + if (newSize < HashMinSize) + newSize = HashMinSize; + else + { + // Force newSize to be a power of two. + int bits = Alg::UpperBit(newSize-1) + 1; // Chop( Log2f((float)(newSize-1)) + 1); + OVR_ASSERT((UPInt(1) << bits) >= newSize); + newSize = UPInt(1) << bits; + } + + SelfType newHash; + newHash.pTable = (TableType*) + Allocator::Alloc( + sizeof(TableType) + sizeof(Entry) * newSize); + // Need to do something on alloc failure! + OVR_ASSERT(newHash.pTable); + + newHash.pTable->EntryCount = 0; + newHash.pTable->SizeMask = newSize - 1; + UPInt i, n; + + // Mark all entries as empty. + for (i = 0; i < newSize; i++) + newHash.E(i).NextInChain = -2; + + // Copy stuff to newHash + if (pTable) + { + for (i = 0, n = pTable->SizeMask; i <= n; i++) + { + Entry* e = &E(i); + if (e->IsEmpty() == false) + { + // Insert old Entry into new HashSet. + newHash.Add(e->Value); + // placement delete of old element + e->Clear(); + } + } + + // Delete our old data buffer. + Allocator::Free(pTable); + } + + // Steal newHash's data. + pTable = newHash.pTable; + newHash.pTable = NULL; + } + + struct TableType + { + UPInt EntryCount; + UPInt SizeMask; + // Entry array follows this structure + // in memory. + }; + TableType* pTable; +}; + + + +//----------------------------------------------------------------------------------- +template<class C, class HashF = FixedSizeHash<C>, + class AltHashF = HashF, + class Allocator = ContainerAllocator<C>, + class Entry = HashsetCachedEntry<C, HashF> > +class HashSet : public HashSetBase<C, HashF, AltHashF, Allocator, Entry> +{ +public: + typedef HashSetBase<C, HashF, AltHashF, Allocator, Entry> BaseType; + typedef HashSet<C, HashF, AltHashF, Allocator, Entry> SelfType; + + HashSet() { } + HashSet(int sizeHint) : BaseType(sizeHint) { } + HashSet(const SelfType& src) : BaseType(src) { } + ~HashSet() { } + + void operator = (const SelfType& src) { BaseType::Assign(src); } + + // Set a new or existing value under the key, to the value. + // Pass a different class of 'key' so that assignment reference object + // can be passed instead of the actual object. + template<class CRef> + void Set(const CRef& key) + { + BaseType::Set(key); + } + + template<class CRef> + inline void Add(const CRef& key) + { + BaseType::Add(key); + } + + // Hint the bucket count to >= n. + void Resize(UPInt n) + { + BaseType::SetCapacity(n); + } + + // Size the HashSet so that it can comfortably contain the given + // number of elements. If the HashSet already contains more + // elements than newSize, then this may be a no-op. + void SetCapacity(UPInt newSize) + { + BaseType::SetCapacity(newSize); + } + +}; + +// HashSet with uncached hash code; declared for convenience. +template<class C, class HashF = FixedSizeHash<C>, + class AltHashF = HashF, + class Allocator = ContainerAllocator<C> > +class HashSetUncached : public HashSet<C, HashF, AltHashF, Allocator, HashsetEntry<C, HashF> > +{ +public: + + typedef HashSetUncached<C, HashF, AltHashF, Allocator> SelfType; + typedef HashSet<C, HashF, AltHashF, Allocator, HashsetEntry<C, HashF> > BaseType; + + // Delegated constructors. + HashSetUncached() { } + HashSetUncached(int sizeHint) : BaseType(sizeHint) { } + HashSetUncached(const SelfType& src) : BaseType(src) { } + ~HashSetUncached() { } + + void operator = (const SelfType& src) + { + BaseType::operator = (src); + } +}; + + +//----------------------------------------------------------------------------------- +// ***** Hash hash table implementation + +// Node for Hash - necessary so that Hash can delegate its implementation +// to HashSet. +template<class C, class U, class HashF> +struct HashNode +{ + typedef HashNode<C, U, HashF> SelfType; + typedef C FirstType; + typedef U SecondType; + + C First; + U Second; + + // NodeRef is used to allow passing of elements into HashSet + // without using a temporary object. + struct NodeRef + { + const C* pFirst; + const U* pSecond; + + NodeRef(const C& f, const U& s) : pFirst(&f), pSecond(&s) { } + NodeRef(const NodeRef& src) : pFirst(src.pFirst), pSecond(src.pSecond) { } + + // Enable computation of ghash_node_hashf. + inline UPInt GetHash() const { return HashF()(*pFirst); } + // Necessary conversion to allow HashNode::operator == to work. + operator const C& () const { return *pFirst; } + }; + + // Note: No default constructor is necessary. + HashNode(const HashNode& src) : First(src.First), Second(src.Second) { } + HashNode(const NodeRef& src) : First(*src.pFirst), Second(*src.pSecond) { } + void operator = (const NodeRef& src) { First = *src.pFirst; Second = *src.pSecond; } + + template<class K> + bool operator == (const K& src) const { return (First == src); } + + template<class K> + static UPInt CalcHash(const K& data) { return HashF()(data); } + inline UPInt GetHash() const { return HashF()(First); } + + // Hash functors used with this node. A separate functor is used for alternative + // key lookup so that it does not need to access the '.First' element. + struct NodeHashF + { + template<class K> + UPInt operator()(const K& data) const { return data.GetHash(); } + }; + struct NodeAltHashF + { + template<class K> + UPInt operator()(const K& data) const { return HashNode<C,U,HashF>::CalcHash(data); } + }; +}; + + + +// **** Extra hashset_entry types to allow NodeRef construction. + +// The big difference between the below types and the ones used in hash_set is that +// these allow initializing the node with 'typename C::NodeRef& keyRef', which +// is critical to avoid temporary node allocation on stack when using placement new. + +// Compact hash table Entry type that re-computes hash keys during hash traversal. +// Good to use if the hash function is cheap or the hash value is already cached in C. +template<class C, class HashF> +class HashsetNodeEntry +{ +public: + // Internal chaining for collisions. + SPInt NextInChain; + C Value; + + HashsetNodeEntry() + : NextInChain(-2) { } + HashsetNodeEntry(const HashsetNodeEntry& e) + : NextInChain(e.NextInChain), Value(e.Value) { } + HashsetNodeEntry(const C& key, SPInt next) + : NextInChain(next), Value(key) { } + HashsetNodeEntry(const typename C::NodeRef& keyRef, SPInt next) + : NextInChain(next), Value(keyRef) { } + + bool IsEmpty() const { return NextInChain == -2; } + bool IsEndOfChain() const { return NextInChain == -1; } + UPInt GetCachedHash(UPInt maskValue) const { return HashF()(Value) & maskValue; } + void SetCachedHash(UPInt hashValue) { OVR_UNUSED(hashValue); } + + void Clear() + { + Value.~C(); // placement delete + NextInChain = -2; + } + // Free is only used from dtor of hash; Clear is used during regular operations: + // assignment, hash reallocations, value reassignments, so on. + void Free() { Clear(); } +}; + +// Hash table Entry type that caches the Entry hash value for nodes, so that it +// does not need to be re-computed during access. +template<class C, class HashF> +class HashsetCachedNodeEntry +{ +public: + // Internal chaining for collisions. + SPInt NextInChain; + UPInt HashValue; + C Value; + + HashsetCachedNodeEntry() + : NextInChain(-2) { } + HashsetCachedNodeEntry(const HashsetCachedNodeEntry& e) + : NextInChain(e.NextInChain), HashValue(e.HashValue), Value(e.Value) { } + HashsetCachedNodeEntry(const C& key, SPInt next) + : NextInChain(next), Value(key) { } + HashsetCachedNodeEntry(const typename C::NodeRef& keyRef, SPInt next) + : NextInChain(next), Value(keyRef) { } + + bool IsEmpty() const { return NextInChain == -2; } + bool IsEndOfChain() const { return NextInChain == -1; } + UPInt GetCachedHash(UPInt maskValue) const { OVR_UNUSED(maskValue); return HashValue; } + void SetCachedHash(UPInt hashValue) { HashValue = hashValue; } + + void Clear() + { + Value.~C(); + NextInChain = -2; + } + // Free is only used from dtor of hash; Clear is used during regular operations: + // assignment, hash reallocations, value reassignments, so on. + void Free() { Clear(); } +}; + + +//----------------------------------------------------------------------------------- +template<class C, class U, + class HashF = FixedSizeHash<C>, + class Allocator = ContainerAllocator<C>, + class HashNode = OVR::HashNode<C,U,HashF>, + class Entry = HashsetCachedNodeEntry<HashNode, typename HashNode::NodeHashF>, + class Container = HashSet<HashNode, typename HashNode::NodeHashF, + typename HashNode::NodeAltHashF, Allocator, + Entry> > +class Hash +{ +public: + OVR_MEMORY_REDEFINE_NEW(Hash) + + // Types used for hash_set. + typedef U ValueType; + typedef Hash<C, U, HashF, Allocator, HashNode, Entry, Container> SelfType; + + // Actual hash table itself, implemented as hash_set. + Container mHash; + +public: + Hash() { } + Hash(int sizeHint) : mHash(sizeHint) { } + Hash(const SelfType& src) : mHash(src.mHash) { } + ~Hash() { } + + void operator = (const SelfType& src) { mHash = src.mHash; } + + // Remove all entries from the Hash table. + inline void Clear() { mHash.Clear(); } + // Returns true if the Hash is empty. + inline bool IsEmpty() const { return mHash.IsEmpty(); } + + // Access (set). + inline void Set(const C& key, const U& value) + { + typename HashNode::NodeRef e(key, value); + mHash.Set(e); + } + inline void Add(const C& key, const U& value) + { + typename HashNode::NodeRef e(key, value); + mHash.Add(e); + } + + // Removes an element by clearing its Entry. + inline void Remove(const C& key) + { + mHash.RemoveAlt(key); + } + template<class K> + inline void RemoveAlt(const K& key) + { + mHash.RemoveAlt(key); + } + + // Retrieve the value under the given key. + // - If there's no value under the key, then return false and leave *pvalue alone. + // - If there is a value, return true, and Set *Pvalue to the Entry's value. + // - If value == NULL, return true or false according to the presence of the key. + bool Get(const C& key, U* pvalue) const + { + const HashNode* p = mHash.GetAlt(key); + if (p) + { + if (pvalue) + *pvalue = p->Second; + return true; + } + return false; + } + + template<class K> + bool GetAlt(const K& key, U* pvalue) const + { + const HashNode* p = mHash.GetAlt(key); + if (p) + { + if (pvalue) + *pvalue = p->Second; + return true; + } + return false; + } + + // Retrieve the pointer to a value under the given key. + // - If there's no value under the key, then return NULL. + // - If there is a value, return the pointer. + inline U* Get(const C& key) + { + HashNode* p = mHash.GetAlt(key); + return p ? &p->Second : 0; + } + inline const U* Get(const C& key) const + { + const HashNode* p = mHash.GetAlt(key); + return p ? &p->Second : 0; + } + + template<class K> + inline U* GetAlt(const K& key) + { + HashNode* p = mHash.GetAlt(key); + return p ? &p->Second : 0; + } + template<class K> + inline const U* GetAlt(const K& key) const + { + const HashNode* p = mHash.GetAlt(key); + return p ? &p->Second : 0; + } + + // Sizing methods - delegate to Hash. + inline UPInt GetSize() const { return mHash.GetSize(); } + inline void Resize(UPInt n) { mHash.Resize(n); } + inline void SetCapacity(UPInt newSize) { mHash.SetCapacity(newSize); } + + // Iterator API, like STL. + typedef typename Container::ConstIterator ConstIterator; + typedef typename Container::Iterator Iterator; + + inline Iterator Begin() { return mHash.Begin(); } + inline Iterator End() { return mHash.End(); } + inline ConstIterator Begin() const { return mHash.Begin(); } + inline ConstIterator End() const { return mHash.End(); } + + Iterator Find(const C& key) { return mHash.FindAlt(key); } + ConstIterator Find(const C& key) const { return mHash.FindAlt(key); } + + template<class K> + Iterator FindAlt(const K& key) { return mHash.FindAlt(key); } + template<class K> + ConstIterator FindAlt(const K& key) const { return mHash.FindAlt(key); } +}; + + + +// Hash with uncached hash code; declared for convenience. +template<class C, class U, class HashF = FixedSizeHash<C>, class Allocator = ContainerAllocator<C> > +class HashUncached + : public Hash<C, U, HashF, Allocator, HashNode<C,U,HashF>, + HashsetNodeEntry<HashNode<C,U,HashF>, typename HashNode<C,U,HashF>::NodeHashF> > +{ +public: + typedef HashUncached<C, U, HashF, Allocator> SelfType; + typedef Hash<C, U, HashF, Allocator, HashNode<C,U,HashF>, + HashsetNodeEntry<HashNode<C,U,HashF>, + typename HashNode<C,U,HashF>::NodeHashF> > BaseType; + + // Delegated constructors. + HashUncached() { } + HashUncached(int sizeHint) : BaseType(sizeHint) { } + HashUncached(const SelfType& src) : BaseType(src) { } + ~HashUncached() { } + void operator = (const SelfType& src) { BaseType::operator = (src); } +}; + + + +// And identity hash in which keys serve as hash value. Can be uncached, +// since hash computation is assumed cheap. +template<class C, class U, class Allocator = ContainerAllocator<C>, class HashF = IdentityHash<C> > +class HashIdentity + : public HashUncached<C, U, HashF, Allocator> +{ +public: + typedef HashIdentity<C, U, Allocator, HashF> SelfType; + typedef HashUncached<C, U, HashF, Allocator> BaseType; + + // Delegated constructors. + HashIdentity() { } + HashIdentity(int sizeHint) : BaseType(sizeHint) { } + HashIdentity(const SelfType& src) : BaseType(src) { } + ~HashIdentity() { } + void operator = (const SelfType& src) { BaseType::operator = (src); } +}; + + +} // OVR + + +#ifdef OVR_DEFINE_NEW +#define new OVR_DEFINE_NEW +#endif + +#endif diff --git a/LibOVR/Src/Kernel/OVR_KeyCodes.h b/LibOVR/Src/Kernel/OVR_KeyCodes.h new file mode 100644 index 0000000..b5c5930 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_KeyCodes.h @@ -0,0 +1,251 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_KeyCodes.h +Content : Common keyboard constants +Created : September 19, 2012 + +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. + +************************************************************************************/ + +#ifndef OVR_KeyCodes_h +#define OVR_KeyCodes_h + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** KeyCode + +// KeyCode enumeration defines platform-independent keyboard key constants. +// Note that Key_A through Key_Z are mapped to capital ascii constants. + +enum KeyCode +{ + // Key_None indicates that no key was specified. + Key_None = 0, + + // A through Z and numbers 0 through 9. + Key_A = 65, + Key_B, + Key_C, + Key_D, + Key_E, + Key_F, + Key_G, + Key_H, + Key_I, + Key_J, + Key_K, + Key_L, + Key_M, + Key_N, + Key_O, + Key_P, + Key_Q, + Key_R, + Key_S, + Key_T, + Key_U, + Key_V, + Key_W, + Key_X, + Key_Y, + Key_Z, + Key_Num0 = 48, + Key_Num1, + Key_Num2, + Key_Num3, + Key_Num4, + Key_Num5, + Key_Num6, + Key_Num7, + Key_Num8, + Key_Num9, + + // Numeric keypad. + Key_KP_0 = 0xa0, + Key_KP_1, + Key_KP_2, + Key_KP_3, + Key_KP_4, + Key_KP_5, + Key_KP_6, + Key_KP_7, + Key_KP_8, + Key_KP_9, + Key_KP_Multiply, + Key_KP_Add, + Key_KP_Enter, + Key_KP_Subtract, + Key_KP_Decimal, + Key_KP_Divide, + + // Function keys. + Key_F1 = 0xb0, + Key_F2, + Key_F3, + Key_F4, + Key_F5, + Key_F6, + Key_F7, + Key_F8, + Key_F9, + Key_F10, + Key_F11, + Key_F12, + Key_F13, + Key_F14, + Key_F15, + + // Other keys. + Key_Backspace = 8, + Key_Tab, + Key_Clear = 12, + Key_Return, + Key_Shift = 16, + Key_Control, + Key_Alt, + Key_Pause, + Key_CapsLock = 20, // Toggle + Key_Escape = 27, + Key_Space = 32, + Key_Quote = 39, + Key_PageUp = 0xc0, + Key_PageDown, + Key_End, + Key_Home, + Key_Left, + Key_Up, + Key_Right, + Key_Down, + Key_Insert, + Key_Delete, + Key_Help, + + Key_Comma = 44, + Key_Minus, + Key_Slash = 47, + Key_Period, + Key_NumLock = 144, // Toggle + Key_ScrollLock = 145, // Toggle + + Key_Semicolon = 59, + Key_Equal = 61, + Key_Backtick = 96, // ` and tilda~ when shifted (US keyboard) + Key_BracketLeft = 91, + Key_Backslash, + Key_BracketRight, + + Key_OEM_AX = 0xE1, // 'AX' key on Japanese AX keyboard + Key_OEM_102 = 0xE2, // "<>" or "\|" on RT 102-key keyboard. + Key_ICO_HELP = 0xE3, // Help key on ICO + Key_ICO_00 = 0xE4, // 00 key on ICO + + Key_Meta, + + // Total number of keys. + Key_CodeCount +}; + + +//----------------------------------------------------------------------------------- + +class KeyModifiers +{ +public: + enum + { + Key_ShiftPressed = 0x01, + Key_CtrlPressed = 0x02, + Key_AltPressed = 0x04, + Key_MetaPressed = 0x08, + Key_CapsToggled = 0x10, + Key_NumToggled = 0x20, + Key_ScrollToggled = 0x40, + + Initialized_Bit = 0x80, + Initialized_Mask = 0xFF + }; + unsigned char States; + + KeyModifiers() : States(0) { } + KeyModifiers(unsigned char st) : States((unsigned char)(st | Initialized_Bit)) { } + + void Reset() { States = 0; } + + bool IsShiftPressed() const { return (States & Key_ShiftPressed) != 0; } + bool IsCtrlPressed() const { return (States & Key_CtrlPressed) != 0; } + bool IsAltPressed() const { return (States & Key_AltPressed) != 0; } + bool IsMetaPressed() const { return (States & Key_MetaPressed) != 0; } + bool IsCapsToggled() const { return (States & Key_CapsToggled) != 0; } + bool IsNumToggled() const { return (States & Key_NumToggled) != 0; } + bool IsScrollToggled() const{ return (States & Key_ScrollToggled) != 0; } + + void SetShiftPressed(bool v = true) { (v) ? States |= Key_ShiftPressed : States &= ~Key_ShiftPressed; } + void SetCtrlPressed(bool v = true) { (v) ? States |= Key_CtrlPressed : States &= ~Key_CtrlPressed; } + void SetAltPressed(bool v = true) { (v) ? States |= Key_AltPressed : States &= ~Key_AltPressed; } + void SetMetaPressed(bool v = true) { (v) ? States |= Key_MetaPressed : States &= ~Key_MetaPressed; } + void SetCapsToggled(bool v = true) { (v) ? States |= Key_CapsToggled : States &= ~Key_CapsToggled; } + void SetNumToggled(bool v = true) { (v) ? States |= Key_NumToggled : States &= ~Key_NumToggled; } + void SetScrollToggled(bool v = true) { (v) ? States |= Key_ScrollToggled: States &= ~Key_ScrollToggled; } + + bool IsInitialized() const { return (States & Initialized_Mask) != 0; } +}; + + +//----------------------------------------------------------------------------------- + +/* +enum PadKeyCode +{ + Pad_None, // Indicates absence of key code. + Pad_Back, + Pad_Start, + Pad_A, + Pad_B, + Pad_X, + Pad_Y, + Pad_R1, // RightShoulder; + Pad_L1, // LeftShoulder; + Pad_R2, // RightTrigger; + Pad_L2, // LeftTrigger; + Pad_Up, + Pad_Down, + Pad_Right, + Pad_Left, + Pad_Plus, + Pad_Minus, + Pad_1, + Pad_2, + Pad_H, + Pad_C, + Pad_Z, + Pad_O, + Pad_T, + Pad_S, + Pad_Select, + Pad_Home, + Pad_RT, // RightThumb; + Pad_LT // LeftThumb; +}; +*/ + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_List.h b/LibOVR/Src/Kernel/OVR_List.h new file mode 100644 index 0000000..6b49f37 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_List.h @@ -0,0 +1,336 @@ +/************************************************************************************ + +PublicHeader: OVR +Filename : OVR_List.h +Content : Template implementation for doubly-connected linked List +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_List_h +#define OVR_List_h + +#include "OVR_Types.h" + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** ListNode +// +// Base class for the elements of the intrusive linked list. +// To store elements in the List do: +// +// struct MyData : ListNode<MyData> +// { +// . . . +// }; + +template<class T> +struct ListNode +{ + union { + T* pPrev; + void* pVoidPrev; + }; + union { + T* pNext; + void* pVoidNext; + }; + + void RemoveNode() + { + pPrev->pNext = pNext; + pNext->pPrev = pPrev; + } + + // Removes us from the list and inserts pnew there instead. + void ReplaceNodeWith(T* pnew) + { + pPrev->pNext = pnew; + pNext->pPrev = pnew; + pnew->pPrev = pPrev; + pnew->pNext = pNext; + } + + // Inserts the argument linked list node after us in the list. + void InsertNodeAfter(T* p) + { + p->pPrev = pNext->pPrev; // this + p->pNext = pNext; + pNext->pPrev = p; + pNext = p; + } + // Inserts the argument linked list node before us in the list. + void InsertNodeBefore(T* p) + { + p->pNext = pNext->pPrev; // this + p->pPrev = pPrev; + pPrev->pNext = p; + pPrev = p; + } + + void Alloc_MoveTo(ListNode<T>* pdest) + { + pdest->pNext = pNext; + pdest->pPrev = pPrev; + pPrev->pNext = (T*)pdest; + pNext->pPrev = (T*)pdest; + } +}; + + +//------------------------------------------------------------------------ +// ***** List +// +// Doubly linked intrusive list. +// The data type must be derived from ListNode. +// +// Adding: PushFront(), PushBack(). +// Removing: Remove() - the element must be in the list! +// Moving: BringToFront(), SendToBack() - the element must be in the list! +// +// Iterating: +// MyData* data = MyList.GetFirst(); +// while (!MyList.IsNull(data)) +// { +// . . . +// data = MyList.GetNext(data); +// } +// +// Removing: +// MyData* data = MyList.GetFirst(); +// while (!MyList.IsNull(data)) +// { +// MyData* next = MyList.GetNext(data); +// if (ToBeRemoved(data)) +// MyList.Remove(data); +// data = next; +// } +// + +// List<> represents a doubly-linked list of T, where each T must derive +// from ListNode<B>. B specifies the base class that was directly +// derived from ListNode, and is only necessary if there is an intermediate +// inheritance chain. + +template<class T, class B = T> class List +{ +public: + typedef T ValueType; + + List() + { + Root.pNext = Root.pPrev = (ValueType*)&Root; + } + + void Clear() + { + Root.pNext = Root.pPrev = (ValueType*)&Root; + } + + const ValueType* GetFirst() const { return (const ValueType*)Root.pNext; } + const ValueType* GetLast () const { return (const ValueType*)Root.pPrev; } + ValueType* GetFirst() { return (ValueType*)Root.pNext; } + ValueType* GetLast () { return (ValueType*)Root.pPrev; } + + // Determine if list is empty (i.e.) points to itself. + // Go through void* access to avoid issues with strict-aliasing optimizing out the + // access after RemoveNode(), etc. + bool IsEmpty() const { return Root.pVoidNext == (const T*)(const B*)&Root; } + bool IsFirst(const ValueType* p) const { return p == Root.pNext; } + bool IsLast (const ValueType* p) const { return p == Root.pPrev; } + bool IsNull (const ValueType* p) const { return p == (const T*)(const B*)&Root; } + + inline static const ValueType* GetPrev(const ValueType* p) { return (const ValueType*)p->pPrev; } + inline static const ValueType* GetNext(const ValueType* p) { return (const ValueType*)p->pNext; } + inline static ValueType* GetPrev( ValueType* p) { return (ValueType*)p->pPrev; } + inline static ValueType* GetNext( ValueType* p) { return (ValueType*)p->pNext; } + + void PushFront(ValueType* p) + { + p->pNext = Root.pNext; + p->pPrev = (ValueType*)&Root; + Root.pNext->pPrev = p; + Root.pNext = p; + } + + void PushBack(ValueType* p) + { + p->pPrev = Root.pPrev; + p->pNext = (ValueType*)&Root; + Root.pPrev->pNext = p; + Root.pPrev = p; + } + + static void Remove(ValueType* p) + { + p->pPrev->pNext = p->pNext; + p->pNext->pPrev = p->pPrev; + } + + void BringToFront(ValueType* p) + { + Remove(p); + PushFront(p); + } + + void SendToBack(ValueType* p) + { + Remove(p); + PushBack(p); + } + + // Appends the contents of the argument list to the front of this list; + // items are removed from the argument list. + void PushListToFront(List<T>& src) + { + if (!src.IsEmpty()) + { + ValueType* pfirst = src.GetFirst(); + ValueType* plast = src.GetLast(); + src.Clear(); + plast->pNext = Root.pNext; + pfirst->pPrev = (ValueType*)&Root; + Root.pNext->pPrev = plast; + Root.pNext = pfirst; + } + } + + void PushListToBack(List<T>& src) + { + if (!src.IsEmpty()) + { + ValueType* pfirst = src.GetFirst(); + ValueType* plast = src.GetLast(); + src.Clear(); + plast->pNext = (ValueType*)&Root; + pfirst->pPrev = Root.pPrev; + Root.pPrev->pNext = pfirst; + Root.pPrev = plast; + } + } + + // Removes all source list items after (and including) the 'pfirst' node from the + // source list and adds them to out list. + void PushFollowingListItemsToFront(List<T>& src, ValueType *pfirst) + { + if (pfirst != &src.Root) + { + ValueType *plast = src.Root.pPrev; + + // Remove list remainder from source. + pfirst->pPrev->pNext = (ValueType*)&src.Root; + src.Root.pPrev = pfirst->pPrev; + // Add the rest of the items to list. + plast->pNext = Root.pNext; + pfirst->pPrev = (ValueType*)&Root; + Root.pNext->pPrev = plast; + Root.pNext = pfirst; + } + } + + // Removes all source list items up to but NOT including the 'pend' node from the + // source list and adds them to out list. + void PushPrecedingListItemsToFront(List<T>& src, ValueType *ptail) + { + if (src.GetFirst() != ptail) + { + ValueType *pfirst = src.Root.pNext; + ValueType *plast = ptail->pPrev; + + // Remove list remainder from source. + ptail->pPrev = (ValueType*)&src.Root; + src.Root.pNext = ptail; + + // Add the rest of the items to list. + plast->pNext = Root.pNext; + pfirst->pPrev = (ValueType*)&Root; + Root.pNext->pPrev = plast; + Root.pNext = pfirst; + } + } + + + // Removes a range of source list items starting at 'pfirst' and up to, but not including 'pend', + // and adds them to out list. Note that source items MUST already be in the list. + void PushListItemsToFront(ValueType *pfirst, ValueType *pend) + { + if (pfirst != pend) + { + ValueType *plast = pend->pPrev; + + // Remove list remainder from source. + pfirst->pPrev->pNext = pend; + pend->pPrev = pfirst->pPrev; + // Add the rest of the items to list. + plast->pNext = Root.pNext; + pfirst->pPrev = (ValueType*)&Root; + Root.pNext->pPrev = plast; + Root.pNext = pfirst; + } + } + + + void Alloc_MoveTo(List<T>* pdest) + { + if (IsEmpty()) + pdest->Clear(); + else + { + pdest->Root.pNext = Root.pNext; + pdest->Root.pPrev = Root.pPrev; + + Root.pNext->pPrev = (ValueType*)&pdest->Root; + Root.pPrev->pNext = (ValueType*)&pdest->Root; + } + } + + +private: + // Copying is prohibited + List(const List<T>&); + const List<T>& operator = (const List<T>&); + + ListNode<B> Root; +}; + + +//------------------------------------------------------------------------ +// ***** FreeListElements +// +// Remove all elements in the list and free them in the allocator + +template<class List, class Allocator> +void FreeListElements(List& list, Allocator& allocator) +{ + typename List::ValueType* self = list.GetFirst(); + while(!list.IsNull(self)) + { + typename List::ValueType* next = list.GetNext(self); + allocator.Free(self); + self = next; + } + list.Clear(); +} + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Lockless.cpp b/LibOVR/Src/Kernel/OVR_Lockless.cpp new file mode 100644 index 0000000..0f25805 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Lockless.cpp @@ -0,0 +1,231 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Lockless.cpp +Content : Test logic for lock-less classes +Created : December 27, 2013 +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 "OVR_Lockless.h" + +#ifdef OVR_LOCKLESS_TEST + +#include "OVR_Threads.h" +#include "OVR_Timer.h" +#include "OVR_Log.h" + + +namespace OVR { namespace LocklessTest { + + +const int TestIterations = 10000000; + +// Use volatile dummys to force compiler to do spinning. +volatile int Dummy1; +int Unused1[32]; +volatile int Dummy2; +int Unused2[32]; +volatile int Dummy3; +int Unused3[32]; + + +// Data block out of 20 consecutive integers, should be internally consistent. +struct TestData +{ + enum { ItemCount = 20 }; + + int Data[ItemCount]; + + + void Set(int val) + { + for (int i=0; i<ItemCount; i++) + { + Data[i] = val*100 + i; + } + } + + int ReadAndCheckConsistency(int prevValue) const + { + int val = Data[0]; + + for (int i=1; i<ItemCount; i++) + { + + if (Data[i] != (val + i)) + { + // Only complain once per same-value entry + if (prevValue != val / 100) + { + LogText("LocklessTest Fail - corruption at %d inside block %d\n", + i, val/100); + // OVR_ASSERT(Data[i] == val + i); + } + break; + } + } + + return val / 100; + } +}; + + + +volatile bool FirstItemWritten = false; +LocklessUpdater<TestData> TestDataUpdater; + +// Use this lock to verify that testing algorithm is otherwise correct... +Lock TestLock; + + +//------------------------------------------------------------------------------------- + +// Consumer thread reads values from TestDataUpdater and +// ensures that each one is internally consistent. + +class Consumer : public Thread +{ + virtual int Run() + { + LogText("LocklessTest::Consumer::Run started.\n"); + + + while (!FirstItemWritten) + { + // spin until producer wrote first value... + } + + TestData d; + int oldValue = 0; + int newValue; + + do + { + { + //Lock::Locker scope(&TestLock); + d = TestDataUpdater.GetState(); + } + + newValue = d.ReadAndCheckConsistency(oldValue); + + // Values should increase or stay the same! + if (newValue < oldValue) + { + LogText("LocklessTest Fail - %d after %d; delta = %d\n", + newValue, oldValue, newValue - oldValue); + // OVR_ASSERT(0); + } + + + if (oldValue != newValue) + { + oldValue = newValue; + + if (oldValue % (TestIterations/30) == 0) + { + LogText("LocklessTest::Consumer - %5.2f%% done\n", + 100.0f * (float)oldValue/(float)TestIterations); + } + } + + // Spin a while + for (int j = 0; j< 300; j++) + { + Dummy3 = j; + } + + + } while (oldValue < (TestIterations * 99 / 100)); + + LogText("LocklessTest::Consumer::Run exiting.\n"); + return 0; + } + +}; + + +//------------------------------------------------------------------------------------- + +class Producer : public Thread +{ + + virtual int Run() + { + LogText("LocklessTest::Producer::Run started.\n"); + + for (int testVal = 0; testVal < TestIterations; testVal++) + { + TestData d; + d.Set(testVal); + + { + //Lock::Locker scope(&TestLock); + TestDataUpdater.SetState(d); + } + + FirstItemWritten = true; + + // Spin a bit + for(int j = 0; j < 1000; j++) + { + Dummy2 = j; + } + + if (testVal % (TestIterations/30) == 0) + { + LogText("LocklessTest::Producer - %5.2f%% done\n", + 100.0f * (float)testVal/(float)TestIterations); + } + } + + LogText("LocklessTest::Producer::Run exiting.\n"); + return 0; + } +}; + + +} // namespace LocklessTest + + + +void StartLocklessTest() +{ + // These threads will release themselves once done + Ptr<LocklessTest::Producer> producerThread = *new LocklessTest::Producer; + Ptr<LocklessTest::Consumer> consumerThread = *new LocklessTest::Consumer; + + producerThread->Start(); + consumerThread->Start(); + + /* + while (!producerThread->IsFinished() && consumerThread->IsFinished()) + { + Thread::MSleep(500); + } */ + + // TBD: Cleanup +} + + +} // namespace OVR + +#endif // OVR_LOCKLESS_TEST diff --git a/LibOVR/Src/Kernel/OVR_Lockless.h b/LibOVR/Src/Kernel/OVR_Lockless.h new file mode 100644 index 0000000..a12f824 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Lockless.h @@ -0,0 +1,107 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Lockless.h +Content : Lock-less classes for producer/consumer communication +Created : November 9, 2013 +Authors : John Carmack + +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. + +*************************************************************************************/ + +#ifndef OVR_Lockless_h +#define OVR_Lockless_h + +#include "OVR_Atomic.h" + +// Define this to compile-in Lockless test logic +//#define OVR_LOCKLESS_TEST + +namespace OVR { + + +// ***** LocklessUpdater + +// For single producer cases where you only care about the most recent update, not +// necessarily getting every one that happens (vsync timing, SensorFusion updates). +// +// This is multiple consumer safe, but is currently only used with a single consumer. + +template<class T> +class LocklessUpdater +{ +public: + LocklessUpdater() : UpdateBegin( 0 ), UpdateEnd( 0 ) {} + + T GetState() const + { + // Copy the state out, then retry with the alternate slot + // if we determine that our copy may have been partially + // stepped on by a new update. + T state; + int begin, end, final; + + for(;;) + { + // We are adding 0, only using these as atomic memory barriers, so it + // is ok to cast off the const, allowing GetState() to remain const. + end = UpdateEnd.ExchangeAdd_Sync(0); + state = Slots[ end & 1 ]; + begin = UpdateBegin.ExchangeAdd_Sync(0); + if ( begin == end ) { + break; + } + + // The producer is potentially blocked while only having partially + // written the update, so copy out the other slot. + state = Slots[ (begin & 1) ^ 1 ]; + final = UpdateBegin.ExchangeAdd_NoSync(0); + if ( final == begin ) { + break; + } + + // The producer completed the last update and started a new one before + // we got it copied out, so try fetching the current buffer again. + } + return state; + } + + void SetState( T state ) + { + const int slot = UpdateBegin.ExchangeAdd_Sync(1) & 1; + // Write to (slot ^ 1) because ExchangeAdd returns 'previous' value before add. + Slots[slot ^ 1] = state; + UpdateEnd.ExchangeAdd_Sync(1); + } + + mutable AtomicInt<int> UpdateBegin; + mutable AtomicInt<int> UpdateEnd; + T Slots[2]; +}; + + +#ifdef OVR_LOCKLESS_TEST +void StartLocklessTest(); +#endif + + +} // namespace OVR + +#endif // OVR_Lockless_h + diff --git a/LibOVR/Src/Kernel/OVR_Log.cpp b/LibOVR/Src/Kernel/OVR_Log.cpp new file mode 100644 index 0000000..d5f8a68 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Log.cpp @@ -0,0 +1,184 @@ +/************************************************************************************ + +Filename : OVR_Log.cpp +Content : Logging support +Created : September 19, 2012 +Notes : + +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 "OVR_Log.h" +#include "OVR_Std.h" +#include <stdarg.h> +#include <stdio.h> + +#if defined(OVR_OS_WIN32) +#include <windows.h> +#elif defined(OVR_OS_ANDROID) +#include <android/log.h> +#endif + +namespace OVR { + +// Global Log pointer. +Log* volatile OVR_GlobalLog = 0; + +//----------------------------------------------------------------------------------- +// ***** Log Implementation + +Log::~Log() +{ + // Clear out global log + if (this == OVR_GlobalLog) + { + // TBD: perhaps we should ASSERT if this happens before system shutdown? + OVR_GlobalLog = 0; + } +} + +void Log::LogMessageVarg(LogMessageType messageType, const char* fmt, va_list argList) +{ + if ((messageType & LoggingMask) == 0) + return; +#ifndef OVR_BUILD_DEBUG + if (IsDebugMessage(messageType)) + return; +#endif + + char buffer[MaxLogBufferMessageSize]; + FormatLog(buffer, MaxLogBufferMessageSize, messageType, fmt, argList); + DefaultLogOutput(buffer, IsDebugMessage(messageType)); +} + +void OVR::Log::LogMessage(LogMessageType messageType, const char* pfmt, ...) +{ + va_list argList; + va_start(argList, pfmt); + LogMessageVarg(messageType, pfmt, argList); + va_end(argList); +} + + +void Log::FormatLog(char* buffer, unsigned bufferSize, LogMessageType messageType, + const char* fmt, va_list argList) +{ + bool addNewline = true; + + switch(messageType) + { + case Log_Error: OVR_strcpy(buffer, bufferSize, "Error: "); break; + case Log_Debug: OVR_strcpy(buffer, bufferSize, "Debug: "); break; + case Log_Assert: OVR_strcpy(buffer, bufferSize, "Assert: "); break; + case Log_Text: buffer[0] = 0; addNewline = false; break; + case Log_DebugText: buffer[0] = 0; addNewline = false; break; + default: + buffer[0] = 0; + addNewline = false; + break; + } + + UPInt prefixLength = OVR_strlen(buffer); + char *buffer2 = buffer + prefixLength; + OVR_vsprintf(buffer2, bufferSize - prefixLength, fmt, argList); + + if (addNewline) + OVR_strcat(buffer, bufferSize, "\n"); +} + + +void Log::DefaultLogOutput(const char* formattedText, bool debug) +{ + +#if defined(OVR_OS_WIN32) + // Under Win32, output regular messages to console if it exists; debug window otherwise. + static DWORD dummyMode; + static bool hasConsole = (GetStdHandle(STD_OUTPUT_HANDLE) != INVALID_HANDLE_VALUE) && + (GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dummyMode)); + + if (!hasConsole || debug) + { + ::OutputDebugStringA(formattedText); + } + else + { + fputs(formattedText, stdout); + } + +#elif defined(OVR_OS_ANDROID) + __android_log_write(ANDROID_LOG_INFO, "OVR", formattedText); + +#else + fputs(formattedText, stdout); + +#endif + + // Just in case. + OVR_UNUSED2(formattedText, debug); +} + + +//static +void Log::SetGlobalLog(Log *log) +{ + OVR_GlobalLog = log; +} +//static +Log* Log::GetGlobalLog() +{ +// No global log by default? +// if (!OVR_GlobalLog) +// OVR_GlobalLog = GetDefaultLog(); + return OVR_GlobalLog; +} + +//static +Log* Log::GetDefaultLog() +{ + // Create default log pointer statically so that it can be used + // even during startup. + static Log defaultLog; + return &defaultLog; +} + + +//----------------------------------------------------------------------------------- +// ***** Global Logging functions + +#define OVR_LOG_FUNCTION_IMPL(Name) \ + void Log##Name(const char* fmt, ...) \ + { \ + if (OVR_GlobalLog) \ + { \ + va_list argList; va_start(argList, fmt); \ + OVR_GlobalLog->LogMessageVarg(Log_##Name, fmt, argList); \ + va_end(argList); \ + } \ + } + +OVR_LOG_FUNCTION_IMPL(Text) +OVR_LOG_FUNCTION_IMPL(Error) + +#ifdef OVR_BUILD_DEBUG +OVR_LOG_FUNCTION_IMPL(DebugText) +OVR_LOG_FUNCTION_IMPL(Debug) +OVR_LOG_FUNCTION_IMPL(Assert) +#endif + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_Log.h b/LibOVR/Src/Kernel/OVR_Log.h new file mode 100644 index 0000000..4d9acc1 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Log.h @@ -0,0 +1,204 @@ +/************************************************************************************ + +PublicHeader: OVR +Filename : OVR_Log.h +Content : Logging support +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Log_h +#define OVR_Log_h + +#include "OVR_Types.h" +#include <stdarg.h> + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Logging Constants + +// LogMaskConstants defined bit mask constants that describe what log messages +// should be displayed. +enum LogMaskConstants +{ + LogMask_Regular = 0x100, + LogMask_Debug = 0x200, + LogMask_None = 0, + LogMask_All = LogMask_Regular|LogMask_Debug +}; + + +// LogMessageType describes the type of the log message, controls when it is +// displayed and what prefix/suffix is given to it. Messages are subdivided into +// regular and debug logging types. Debug logging is only generated in debug builds. +// +// Log_Text - General output text displayed without prefix or new-line. +// Used in OVR libraries for general log flow messages +// such as "Device Initialized". +// +// Log_Error - Error message output with "Error: %s\n", intended for +// application/sample-level use only, in cases where an expected +// operation failed. OVR libraries should not use this internally, +// reporting status codes instead. +// +// Log_DebugText - Message without prefix or new lines; output in Debug build only. +// +// Log_Debug - Debug-build only message, formatted with "Debug: %s\n". +// Intended to comment on incorrect API usage that doesn't lead +// to crashes but can be avoided with proper use. +// There is no Debug Error on purpose, since real errors should +// be handled by API user. +// +// Log_Assert - Debug-build only message, formatted with "Assert: %s\n". +// Intended for severe unrecoverable conditions in library +// source code. Generated though OVR_ASSERT_MSG(c, "Text"). + +enum LogMessageType +{ + // General Logging + Log_Text = LogMask_Regular | 0, + Log_Error = LogMask_Regular | 1, // "Error: %s\n". + + // Debug-only messages (not generated in release build) + Log_DebugText = LogMask_Debug | 0, + Log_Debug = LogMask_Debug | 1, // "Debug: %s\n". + Log_Assert = LogMask_Debug | 2, // "Assert: %s\n". +}; + + +// LOG_VAARG_ATTRIBUTE macro, enforces printf-style fromatting for message types +#ifdef __GNUC__ +# define OVR_LOG_VAARG_ATTRIBUTE(a,b) __attribute__((format (printf, a, b))) +#else +# define OVR_LOG_VAARG_ATTRIBUTE(a,b) +#endif + + +//----------------------------------------------------------------------------------- +// ***** Log + +// Log defines a base class interface that can be implemented to catch both +// debug and runtime messages. +// Debug logging can be overridden by calling Log::SetGlobalLog. + +class Log +{ + friend class System; +public: + Log(unsigned logMask = LogMask_Debug) : LoggingMask(logMask) { } + virtual ~Log(); + + // Log formating buffer size used by default LogMessageVarg. Longer strings are truncated. + enum { MaxLogBufferMessageSize = 4096 }; + + unsigned GetLoggingMask() const { return LoggingMask; } + void SetLoggingMask(unsigned logMask) { LoggingMask = logMask; } + + // This virtual function receives all the messages, + // developers should override this function in order to do custom logging + virtual void LogMessageVarg(LogMessageType messageType, const char* fmt, va_list argList); + + // Call the logging function with specific message type, with no type filtering. + void LogMessage(LogMessageType messageType, + const char* fmt, ...) OVR_LOG_VAARG_ATTRIBUTE(3,4); + + + // Helper used by LogMessageVarg to format the log message, writing the resulting + // string into buffer. It formats text based on fmt and appends prefix/new line + // based on LogMessageType. + static void FormatLog(char* buffer, unsigned bufferSize, LogMessageType messageType, + const char* fmt, va_list argList); + + // Default log output implementation used by by LogMessageVarg. + // Debug flag may be used to re-direct output on some platforms, but doesn't + // necessarily disable it in release builds; that is the job of the called. + static void DefaultLogOutput(const char* textBuffer, bool debug); + + // Determines if the specified message type is for debugging only. + static bool IsDebugMessage(LogMessageType messageType) + { + return (messageType & LogMask_Debug) != 0; + } + + // *** Global APIs + + // Global Log registration APIs. + // - Global log is used for OVR_DEBUG messages. Set global log to null (0) + // to disable all logging. + static void SetGlobalLog(Log *log); + static Log* GetGlobalLog(); + + // Returns default log singleton instance. + static Log* GetDefaultLog(); + + // Applies logMask to the default log and returns a pointer to it. + // By default, only Debug logging is enabled, so to avoid SDK generating console + // messages in user app (those are always disabled in release build, + // even if the flag is set). This function is useful in System constructor. + static Log* ConfigureDefaultLog(unsigned logMask = LogMask_Debug) + { + Log* log = GetDefaultLog(); + log->SetLoggingMask(logMask); + return log; + } + +private: + // Logging mask described by LogMaskConstants. + unsigned LoggingMask; +}; + + +//----------------------------------------------------------------------------------- +// ***** Global Logging Functions and Debug Macros + +// These functions will output text to global log with semantics described by +// their LogMessageType. +void LogText(const char* fmt, ...) OVR_LOG_VAARG_ATTRIBUTE(1,2); +void LogError(const char* fmt, ...) OVR_LOG_VAARG_ATTRIBUTE(1,2); + +#ifdef OVR_BUILD_DEBUG + + // Debug build only logging. + void LogDebugText(const char* fmt, ...) OVR_LOG_VAARG_ATTRIBUTE(1,2); + void LogDebug(const char* fmt, ...) OVR_LOG_VAARG_ATTRIBUTE(1,2); + void LogAssert(const char* fmt, ...) OVR_LOG_VAARG_ATTRIBUTE(1,2); + + // Macro to do debug logging, printf-style. + // An extra set of set of parenthesis must be used around arguments, + // as in: OVR_LOG_DEBUG(("Value %d", 2)). + #define OVR_DEBUG_LOG(args) do { OVR::LogDebug args; } while(0) + #define OVR_DEBUG_LOG_TEXT(args) do { OVR::LogDebugText args; } while(0) + + #define OVR_ASSERT_LOG(c, args) do { if (!(c)) { OVR::LogAssert args; OVR_DEBUG_BREAK; } } while(0) + +#else + + // If not in debug build, macros do nothing. + #define OVR_DEBUG_LOG(args) ((void)0) + #define OVR_DEBUG_LOG_TEXT(args) ((void)0) + #define OVR_ASSERT_LOG(c, args) ((void)0) + +#endif + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Math.cpp b/LibOVR/Src/Kernel/OVR_Math.cpp new file mode 100644 index 0000000..396d3ff --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Math.cpp @@ -0,0 +1,91 @@ +/************************************************************************************ + +Filename : OVR_Math.h +Content : Implementation of 3D primitives such as vectors, matrices. +Created : September 4, 2012 +Authors : Andrew Reisse, Michael Antonov, Anna Yershova + +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 "OVR_Math.h" +#include "OVR_Log.h" + +#include <float.h> + + +namespace OVR { + + +//------------------------------------------------------------------------------------- +// ***** Math + + +// Single-precision Math constants class. +const float Math<float>::Pi = 3.1415926f; +const float Math<float>::TwoPi = 3.1415926f * 2; +const float Math<float>::PiOver2 = 3.1415926f / 2.0f; +const float Math<float>::PiOver4 = 3.1415926f / 4.0f; +const float Math<float>::E = 2.7182818f; + +const float Math<float>::MaxValue = FLT_MAX; +const float Math<float>::MinPositiveValue = FLT_MIN; + +const float Math<float>::RadToDegreeFactor = 360.0f / Math<float>::TwoPi; +const float Math<float>::DegreeToRadFactor = Math<float>::TwoPi / 360.0f; + +const float Math<float>::Tolerance = 0.00001f; +const float Math<float>::SingularityRadius = 0.0000001f; // Use for Gimbal lock numerical problems + +// Double-precision Math constants class. +const double Math<double>::Pi = 3.14159265358979; +const double Math<double>::TwoPi = 3.14159265358979 * 2; +const double Math<double>::PiOver2 = 3.14159265358979 / 2.0; +const double Math<double>::PiOver4 = 3.14159265358979 / 4.0; +const double Math<double>::E = 2.71828182845905; + +const double Math<double>::MaxValue = DBL_MAX; +const double Math<double>::MinPositiveValue = DBL_MIN; + +const double Math<double>::RadToDegreeFactor = 360.0 / Math<double>::TwoPi; +const double Math<double>::DegreeToRadFactor = Math<double>::TwoPi / 360.0; + +const double Math<double>::Tolerance = 0.00001; +const double Math<double>::SingularityRadius = 0.000000000001; // Use for Gimbal lock numerical problems + + + +//------------------------------------------------------------------------------------- +// ***** Matrix4 + +template<> +const Matrix4<float> Matrix4<float>::IdentityValue = Matrix4<float>(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + +template<> +const Matrix4<double> Matrix4<double>::IdentityValue = Matrix4<double>(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + + + +} // Namespace OVR diff --git a/LibOVR/Src/Kernel/OVR_Math.h b/LibOVR/Src/Kernel/OVR_Math.h new file mode 100644 index 0000000..4aa42b0 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Math.h @@ -0,0 +1,2526 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Math.h +Content : Implementation of 3D primitives such as vectors, matrices. +Created : September 4, 2012 +Authors : Andrew Reisse, Michael Antonov, Steve LaValle, + Anna Yershova, Max Katsev, Dov Katz + +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. + +*************************************************************************************/ + +#ifndef OVR_Math_h +#define OVR_Math_h + +#include <assert.h> +#include <stdlib.h> +#include <math.h> + +#include "OVR_Types.h" +#include "OVR_RefCount.h" +#include "OVR_Std.h" +#include "OVR_Alg.h" + + +namespace OVR { + +//------------------------------------------------------------------------------------- +// ***** Constants for 3D world/axis definitions. + +// Definitions of axes for coordinate and rotation conversions. +enum Axis +{ + Axis_X = 0, Axis_Y = 1, Axis_Z = 2 +}; + +// RotateDirection describes the rotation direction around an axis, interpreted as follows: +// CW - Clockwise while looking "down" from positive axis towards the origin. +// CCW - Counter-clockwise while looking from the positive axis towards the origin, +// which is in the negative axis direction. +// CCW is the default for the RHS coordinate system. Oculus standard RHS coordinate +// system defines Y up, X right, and Z back (pointing out from the screen). In this +// system Rotate_CCW around Z will specifies counter-clockwise rotation in XY plane. +enum RotateDirection +{ + Rotate_CCW = 1, + Rotate_CW = -1 +}; + +// Constants for right handed and left handed coordinate systems +enum HandedSystem +{ + Handed_R = 1, Handed_L = -1 +}; + +// AxisDirection describes which way the coordinate axis points. Used by WorldAxes. +enum AxisDirection +{ + Axis_Up = 2, + Axis_Down = -2, + Axis_Right = 1, + Axis_Left = -1, + Axis_In = 3, + Axis_Out = -3 +}; + +struct WorldAxes +{ + AxisDirection XAxis, YAxis, ZAxis; + + WorldAxes(AxisDirection x, AxisDirection y, AxisDirection z) + : XAxis(x), YAxis(y), ZAxis(z) + { OVR_ASSERT(abs(x) != abs(y) && abs(y) != abs(z) && abs(z) != abs(x));} +}; + +} // namespace OVR + + +//------------------------------------------------------------------------------------// +// ***** C Compatibility Types + +// These declarations are used to support conversion between C types used in +// LibOVR C interfaces and their C++ versions. As an example, they allow passing +// Vector3f into a function that expects ovrVector3f. + +typedef struct ovrQuatf_ ovrQuatf; +typedef struct ovrQuatd_ ovrQuatd; +typedef struct ovrSizei_ ovrSizei; +typedef struct ovrSizef_ ovrSizef; +typedef struct ovrRecti_ ovrRecti; +typedef struct ovrVector2i_ ovrVector2i; +typedef struct ovrVector2f_ ovrVector2f; +typedef struct ovrVector3f_ ovrVector3f; +typedef struct ovrVector3d_ ovrVector3d; +typedef struct ovrMatrix3d_ ovrMatrix3d; +typedef struct ovrMatrix4f_ ovrMatrix4f; +typedef struct ovrPosef_ ovrPosef; +typedef struct ovrPosed_ ovrPosed; +typedef struct ovrPoseStatef_ ovrPoseStatef; +typedef struct ovrPoseStated_ ovrPoseStated; + +namespace OVR { + +// Forward-declare our templates. +template<class T> class Quat; +template<class T> class Size; +template<class T> class Rect; +template<class T> class Vector2; +template<class T> class Vector3; +template<class T> class Matrix3; +template<class T> class Matrix4; +template<class T> class Transform; +template<class T> class PoseState; + +// CompatibleTypes::Type is used to lookup a compatible C-version of a C++ class. +template<class C> +struct CompatibleTypes +{ + // Declaration here seems necessary for MSVC; specializations are + // used instead. + typedef float Type; +}; + +// Specializations providing CompatibleTypes::Type value. +template<> struct CompatibleTypes<Quat<float> > { typedef ovrQuatf Type; }; +template<> struct CompatibleTypes<Quat<double> > { typedef ovrQuatd Type; }; +template<> struct CompatibleTypes<Matrix3<double> > { typedef ovrMatrix3d Type; }; +template<> struct CompatibleTypes<Matrix4<float> > { typedef ovrMatrix4f Type; }; +template<> struct CompatibleTypes<Size<int> > { typedef ovrSizei Type; }; +template<> struct CompatibleTypes<Size<float> > { typedef ovrSizef Type; }; +template<> struct CompatibleTypes<Rect<int> > { typedef ovrRecti Type; }; +template<> struct CompatibleTypes<Vector2<int> > { typedef ovrVector2i Type; }; +template<> struct CompatibleTypes<Vector2<float> > { typedef ovrVector2f Type; }; +template<> struct CompatibleTypes<Vector3<float> > { typedef ovrVector3f Type; }; +template<> struct CompatibleTypes<Vector3<double> > { typedef ovrVector3d Type; }; + +template<> struct CompatibleTypes<Transform<float> > { typedef ovrPosef Type; }; +template<> struct CompatibleTypes<PoseState<float> > { typedef ovrPoseStatef Type; }; + +template<> struct CompatibleTypes<Transform<double> > { typedef ovrPosed Type; }; +template<> struct CompatibleTypes<PoseState<double> > { typedef ovrPoseStated Type; }; + +//------------------------------------------------------------------------------------// +// ***** Math +// +// Math class contains constants and functions. This class is a template specialized +// per type, with Math<float> and Math<double> being distinct. +template<class Type> +class Math +{ +public: + // By default, support explicit conversion to float. This allows Vector2<int> to + // compile, for example. + typedef float OtherFloatType; +}; + +// Single-precision Math constants class. +template<> +class Math<float> +{ +public: + static const float Pi; + static const float TwoPi; + static const float PiOver2; + static const float PiOver4; + static const float E; + + static const float MaxValue; // Largest positive float Value + static const float MinPositiveValue; // Smallest possible positive value + + static const float RadToDegreeFactor; + static const float DegreeToRadFactor; + + static const float Tolerance; // 0.00001f; + static const float SingularityRadius; // 0.0000001f for Gimbal lock numerical problems + + // Used to support direct conversions in template classes. + typedef double OtherFloatType; +}; + +// Double-precision Math constants class. +template<> +class Math<double> +{ +public: + static const double Pi; + static const double TwoPi; + static const double PiOver2; + static const double PiOver4; + static const double E; + + static const double MaxValue; // Largest positive double Value + static const double MinPositiveValue; // Smallest possible positive value + + static const double RadToDegreeFactor; + static const double DegreeToRadFactor; + + static const double Tolerance; // 0.00001; + static const double SingularityRadius; // 0.000000000001 for Gimbal lock numerical problems + + typedef float OtherFloatType; +}; + + +typedef Math<float> Mathf; +typedef Math<double> Mathd; + +// Conversion functions between degrees and radians +template<class T> +T RadToDegree(T rads) { return rads * Math<T>::RadToDegreeFactor; } +template<class T> +T DegreeToRad(T rads) { return rads * Math<T>::DegreeToRadFactor; } + +// Numerically stable acos function +template<class T> +T Acos(T val) { + if (val > T(1)) return T(0); + else if (val < T(-1)) return Math<T>::Pi; + else return acos(val); +}; + +// Numerically stable asin function +template<class T> +T Asin(T val) { + if (val > T(1)) return Math<T>::PiOver2; + else if (val < T(-1)) return Math<T>::PiOver2 * T(3); + else return asin(val); +}; + +#ifdef OVR_CC_MSVC +inline int isnan(double x) { return _isnan(x); }; +#endif + +template<class T> +class Quat; + + +//------------------------------------------------------------------------------------- +// ***** Vector2<> + +// Vector2f (Vector2d) represents a 2-dimensional vector or point in space, +// consisting of coordinates x and y + +template<class T> +class Vector2 +{ +public: + T x, y; + + Vector2() : x(0), y(0) { } + Vector2(T x_, T y_) : x(x_), y(y_) { } + explicit Vector2(T s) : x(s), y(s) { } + explicit Vector2(const Vector2<typename Math<T>::OtherFloatType> &src) + : x((T)src.x), y((T)src.y) { } + + + // C-interop support. + typedef typename CompatibleTypes<Vector2<T> >::Type CompatibleType; + + Vector2(const CompatibleType& s) : x(s.x), y(s.y) { } + + operator const CompatibleType& () const + { + OVR_COMPILER_ASSERT(sizeof(Vector2<T>) == sizeof(CompatibleType)); + return reinterpret_cast<const CompatibleType&>(*this); + } + + + bool operator== (const Vector2& b) const { return x == b.x && y == b.y; } + bool operator!= (const Vector2& b) const { return x != b.x || y != b.y; } + + Vector2 operator+ (const Vector2& b) const { return Vector2(x + b.x, y + b.y); } + Vector2& operator+= (const Vector2& b) { x += b.x; y += b.y; return *this; } + Vector2 operator- (const Vector2& b) const { return Vector2(x - b.x, y - b.y); } + Vector2& operator-= (const Vector2& b) { x -= b.x; y -= b.y; return *this; } + Vector2 operator- () const { return Vector2(-x, -y); } + + // Scalar multiplication/division scales vector. + Vector2 operator* (T s) const { return Vector2(x*s, y*s); } + Vector2& operator*= (T s) { x *= s; y *= s; return *this; } + + Vector2 operator/ (T s) const { T rcp = T(1)/s; + return Vector2(x*rcp, y*rcp); } + Vector2& operator/= (T s) { T rcp = T(1)/s; + x *= rcp; y *= rcp; + return *this; } + + static Vector2 Min(const Vector2& a, const Vector2& b) { return Vector2((a.x < b.x) ? a.x : b.x, + (a.y < b.y) ? a.y : b.y); } + static Vector2 Max(const Vector2& a, const Vector2& b) { return Vector2((a.x > b.x) ? a.x : b.x, + (a.y > b.y) ? a.y : b.y); } + + // Compare two vectors for equality with tolerance. Returns true if vectors match withing tolerance. + bool Compare(const Vector2&b, T tolerance = Mathf::Tolerance) + { + return (fabs(b.x-x) < tolerance) && (fabs(b.y-y) < tolerance); + } + + // Entrywise product of two vectors + Vector2 EntrywiseMultiply(const Vector2& b) const { return Vector2(x * b.x, y * b.y);} + + + // Multiply and divide operators do entry-wise math. Used Dot() for dot product. + Vector2 operator* (const Vector2& b) const { return Vector2(x * b.x, y * b.y); } + Vector2 operator/ (const Vector2& b) const { return Vector2(x / b.x, y / b.y); } + + // Dot product + // Used to calculate angle q between two vectors among other things, + // as (A dot B) = |a||b|cos(q). + T Dot(const Vector2& b) const { return x*b.x + y*b.y; } + + // Returns the angle from this vector to b, in radians. + T Angle(const Vector2& b) const + { + T div = LengthSq()*b.LengthSq(); + OVR_ASSERT(div != T(0)); + T result = Acos((this->Dot(b))/sqrt(div)); + return result; + } + + // Return Length of the vector squared. + T LengthSq() const { return (x * x + y * y); } + + // Return vector length. + T Length() const { return sqrt(LengthSq()); } + + // Returns squared distance between two points represented by vectors. + T DistanceSq(Vector2& b) const { return (*this - b).LengthSq(); } + + // Returns distance between two points represented by vectors. + T Distance(Vector2& b) const { return (*this - b).Length(); } + + // Determine if this a unit vector. + bool IsNormalized() const { return fabs(LengthSq() - T(1)) < Math<T>::Tolerance; } + + // Normalize, convention vector length to 1. + void Normalize() + { + T l = Length(); + OVR_ASSERT(l != T(0)); + *this /= l; + } + // Returns normalized (unit) version of the vector without modifying itself. + Vector2 Normalized() const + { + T l = Length(); + OVR_ASSERT(l != T(0)); + return *this / l; + } + + // Linearly interpolates from this vector to another. + // Factor should be between 0.0 and 1.0, with 0 giving full value to this. + Vector2 Lerp(const Vector2& b, T f) const { return *this*(T(1) - f) + b*f; } + + // Projects this vector onto the argument; in other words, + // A.Project(B) returns projection of vector A onto B. + Vector2 ProjectTo(const Vector2& b) const + { + T l2 = b.LengthSq(); + OVR_ASSERT(l2 != T(0)); + return b * ( Dot(b) / l2 ); + } +}; + + +typedef Vector2<float> Vector2f; +typedef Vector2<double> Vector2d; +typedef Vector2<int> Vector2i; + +//------------------------------------------------------------------------------------- +// ***** Vector3<> - 3D vector of {x, y, z} + +// +// Vector3f (Vector3d) represents a 3-dimensional vector or point in space, +// consisting of coordinates x, y and z. + +template<class T> +class Vector3 +{ +public: + T x, y, z; + + Vector3() : x(0), y(0), z(0) { } + Vector3(T x_, T y_, T z_ = 0) : x(x_), y(y_), z(z_) { } + explicit Vector3(T s) : x(s), y(s), z(s) { } + explicit Vector3(const Vector3<typename Math<T>::OtherFloatType> &src) + : x((T)src.x), y((T)src.y), z((T)src.z) { } + + + // C-interop support. + typedef typename CompatibleTypes<Vector3<T> >::Type CompatibleType; + + Vector3(const CompatibleType& s) : x(s.x), y(s.y), z(s.z) { } + + operator const CompatibleType& () const + { + OVR_COMPILER_ASSERT(sizeof(Vector3<T>) == sizeof(CompatibleType)); + return reinterpret_cast<const CompatibleType&>(*this); + } + + bool operator== (const Vector3& b) const { return x == b.x && y == b.y && z == b.z; } + bool operator!= (const Vector3& b) const { return x != b.x || y != b.y || z != b.z; } + + Vector3 operator+ (const Vector3& b) const { return Vector3(x + b.x, y + b.y, z + b.z); } + Vector3& operator+= (const Vector3& b) { x += b.x; y += b.y; z += b.z; return *this; } + Vector3 operator- (const Vector3& b) const { return Vector3(x - b.x, y - b.y, z - b.z); } + Vector3& operator-= (const Vector3& b) { x -= b.x; y -= b.y; z -= b.z; return *this; } + Vector3 operator- () const { return Vector3(-x, -y, -z); } + + // Scalar multiplication/division scales vector. + Vector3 operator* (T s) const { return Vector3(x*s, y*s, z*s); } + Vector3& operator*= (T s) { x *= s; y *= s; z *= s; return *this; } + + Vector3 operator/ (T s) const { T rcp = T(1)/s; + return Vector3(x*rcp, y*rcp, z*rcp); } + Vector3& operator/= (T s) { T rcp = T(1)/s; + x *= rcp; y *= rcp; z *= rcp; + return *this; } + + static Vector3 Min(const Vector3& a, const Vector3& b) + { + return Vector3((a.x < b.x) ? a.x : b.x, + (a.y < b.y) ? a.y : b.y, + (a.z < b.z) ? a.z : b.z); + } + static Vector3 Max(const Vector3& a, const Vector3& b) + { + return Vector3((a.x > b.x) ? a.x : b.x, + (a.y > b.y) ? a.y : b.y, + (a.z > b.z) ? a.z : b.z); + } + + // Compare two vectors for equality with tolerance. Returns true if vectors match withing tolerance. + bool Compare(const Vector3&b, T tolerance = Mathf::Tolerance) + { + return (fabs(b.x-x) < tolerance) && + (fabs(b.y-y) < tolerance) && + (fabs(b.z-z) < tolerance); + } + + T& operator[] (int idx) + { + OVR_ASSERT(0 <= idx && idx < 3); + return *(&x + idx); + } + + const T& operator[] (int idx) const + { + OVR_ASSERT(0 <= idx && idx < 3); + return *(&x + idx); + } + + // Entrywise product of two vectors + Vector3 EntrywiseMultiply(const Vector3& b) const { return Vector3(x * b.x, + y * b.y, + z * b.z);} + + // Multiply and divide operators do entry-wise math + Vector3 operator* (const Vector3& b) const { return Vector3(x * b.x, + y * b.y, + z * b.z); } + + Vector3 operator/ (const Vector3& b) const { return Vector3(x / b.x, + y / b.y, + z / b.z); } + + + // Dot product + // Used to calculate angle q between two vectors among other things, + // as (A dot B) = |a||b|cos(q). + T Dot(const Vector3& b) const { return x*b.x + y*b.y + z*b.z; } + + // Compute cross product, which generates a normal vector. + // Direction vector can be determined by right-hand rule: Pointing index finder in + // direction a and middle finger in direction b, thumb will point in a.Cross(b). + Vector3 Cross(const Vector3& b) const { return Vector3(y*b.z - z*b.y, + z*b.x - x*b.z, + x*b.y - y*b.x); } + + // Returns the angle from this vector to b, in radians. + T Angle(const Vector3& b) const + { + T div = LengthSq()*b.LengthSq(); + OVR_ASSERT(div != T(0)); + T result = Acos((this->Dot(b))/sqrt(div)); + return result; + } + + // Return Length of the vector squared. + T LengthSq() const { return (x * x + y * y + z * z); } + + // Return vector length. + T Length() const { return sqrt(LengthSq()); } + + // Returns squared distance between two points represented by vectors. + T DistanceSq(Vector3 const& b) const { return (*this - b).LengthSq(); } + + // Returns distance between two points represented by vectors. + T Distance(Vector3 const& b) const { return (*this - b).Length(); } + + // Determine if this a unit vector. + bool IsNormalized() const { return fabs(LengthSq() - T(1)) < Math<T>::Tolerance; } + + // Normalize, convention vector length to 1. + void Normalize() + { + T l = Length(); + OVR_ASSERT(l != T(0)); + *this /= l; + } + + // Returns normalized (unit) version of the vector without modifying itself. + Vector3 Normalized() const + { + T l = Length(); + OVR_ASSERT(l != T(0)); + return *this / l; + } + + // Linearly interpolates from this vector to another. + // Factor should be between 0.0 and 1.0, with 0 giving full value to this. + Vector3 Lerp(const Vector3& b, T f) const { return *this*(T(1) - f) + b*f; } + + // Projects this vector onto the argument; in other words, + // A.Project(B) returns projection of vector A onto B. + Vector3 ProjectTo(const Vector3& b) const + { + T l2 = b.LengthSq(); + OVR_ASSERT(l2 != T(0)); + return b * ( Dot(b) / l2 ); + } + + // Projects this vector onto a plane defined by a normal vector + Vector3 ProjectToPlane(const Vector3& normal) const { return *this - this->ProjectTo(normal); } +}; + + +typedef Vector3<float> Vector3f; +typedef Vector3<double> Vector3d; +typedef Vector3<SInt32> Vector3i; + + +// JDC: this was defined in Render_Device.h, I moved it here, but it +// needs to be fleshed out like the other Vector types. +// +// A vector with a dummy w component for alignment in uniform buffers (and for float colors). +// The w component is not used in any calculations. + +struct Vector4f : public Vector3f +{ + float w; + + Vector4f() : w(1) {} + Vector4f(const Vector3f& v) : Vector3f(v), w(1) {} + Vector4f(float r, float g, float b, float a) : Vector3f(r,g,b), w(a) {} +}; + + + +//------------------------------------------------------------------------------------- +// ***** Size + +// Size class represents 2D size with Width, Height components. +// Used to describe distentions of render targets, etc. + +template<class T> +class Size +{ +public: + T w, h; + + Size() : w(0), h(0) { } + Size(T w_, T h_) : w(w_), h(h_) { } + explicit Size(T s) : w(s), h(s) { } + explicit Size(const Size<typename Math<T>::OtherFloatType> &src) + : w((T)src.w), h((T)src.h) { } + + // C-interop support. + typedef typename CompatibleTypes<Size<T> >::Type CompatibleType; + + Size(const CompatibleType& s) : w(s.w), h(s.h) { } + + operator const CompatibleType& () const + { + OVR_COMPILER_ASSERT(sizeof(Size<T>) == sizeof(CompatibleType)); + return reinterpret_cast<const CompatibleType&>(*this); + } + + bool operator== (const Size& b) const { return w == b.w && h == b.h; } + bool operator!= (const Size& b) const { return w != b.w || h != b.h; } + + Size operator+ (const Size& b) const { return Size(w + b.w, h + b.h); } + Size& operator+= (const Size& b) { w += b.w; h += b.h; return *this; } + Size operator- (const Size& b) const { return Size(w - b.w, h - b.h); } + Size& operator-= (const Size& b) { w -= b.w; h -= b.h; return *this; } + Size operator- () const { return Size(-w, -h); } + Size operator* (const Size& b) const { return Size(w * b.w, h * b.h); } + Size& operator*= (const Size& b) { w *= b.w; h *= b.h; return *this; } + Size operator/ (const Size& b) const { return Size(w / b.w, h / b.h); } + Size& operator/= (const Size& b) { w /= b.w; h /= b.h; return *this; } + + // Scalar multiplication/division scales both components. + Size operator* (T s) const { return Size(w*s, h*s); } + Size& operator*= (T s) { w *= s; h *= s; return *this; } + Size operator/ (T s) const { return Size(w/s, h/s); } + Size& operator/= (T s) { w /= s; h /= s; return *this; } + + static Size Min(const Size& a, const Size& b) { return Size((a.w < b.w) ? a.w : b.w, + (a.h < b.h) ? a.h : b.h); } + static Size Max(const Size& a, const Size& b) { return Size((a.w > b.w) ? a.w : b.w, + (a.h > b.h) ? a.h : b.h); } + + + T Area() const { return w * h; } + + inline Vector2<T> ToVector() const { return Vector2<T>(w, h); } +}; + + +typedef Size<int> Sizei; +typedef Size<unsigned> Sizeu; +typedef Size<float> Sizef; +typedef Size<double> Sized; + + + +//----------------------------------------------------------------------------------- +// ***** Rect + +// Rect describes a rectangular area for rendering, that includes position and size. +template<class T> +class Rect +{ +public: + T x, y; + T w, h; + + Rect() { } + Rect(T x1, T y1, T w1, T h1) : x(x1), y(y1), w(w1), h(h1) { } + Rect(const Vector2<T>& pos, const Size<T>& sz) : x(pos.x), y(pos.y), w(sz.w), h(sz.h) { } + Rect(const Size<T>& sz) : x(0), y(0), w(sz.w), h(sz.h) { } + + // C-interop support. + typedef typename CompatibleTypes<Rect<T> >::Type CompatibleType; + + Rect(const CompatibleType& s) : x(s.Pos.x), y(s.Pos.y), w(s.Size.w), h(s.Size.h) { } + + operator const CompatibleType& () const + { + OVR_COMPILER_ASSERT(sizeof(Rect<T>) == sizeof(CompatibleType)); + return reinterpret_cast<const CompatibleType&>(*this); + } + + Vector2<T> GetPos() const { return Vector2<T>(x, y); } + Size<T> GetSize() const { return Size<T>(w, h); } + void SetPos(const Vector2<T>& pos) { x = pos.x; y = pos.y; } + void SetSize(const Size<T>& sz) { w = sz.w; h = sz.h; } + + bool operator == (const Rect& vp) const + { return (x == vp.x) && (y == vp.y) && (w == vp.w) && (h == vp.h); } + bool operator != (const Rect& vp) const + { return !operator == (vp); } +}; + +typedef Rect<int> Recti; + + +//-------------------------------------------------------------------------------------// +// ***** Quat +// +// Quatf represents a quaternion class used for rotations. +// +// Quaternion multiplications are done in right-to-left order, to match the +// behavior of matrices. + + +template<class T> +class Quat +{ +public: + // w + Xi + Yj + Zk + T x, y, z, w; + + Quat() : x(0), y(0), z(0), w(1) { } + Quat(T x_, T y_, T z_, T w_) : x(x_), y(y_), z(z_), w(w_) { } + explicit Quat(const Quat<typename Math<T>::OtherFloatType> &src) + : x((T)src.x), y((T)src.y), z((T)src.z), w((T)src.w) { } + + // C-interop support. + Quat(const typename CompatibleTypes<Quat<T> >::Type& s) : x(s.x), y(s.y), z(s.z), w(s.w) { } + + operator typename CompatibleTypes<Quat<T> >::Type () const + { + typename CompatibleTypes<Quat<T> >::Type result; + result.x = x; + result.y = y; + result.z = z; + result.w = w; + return result; + } + + // Constructs quaternion for rotation around the axis by an angle. + Quat(const Vector3<T>& axis, T angle) + { + // Make sure we don't divide by zero. + if (axis.LengthSq() == 0) + { + // Assert if the axis is zero, but the angle isn't + OVR_ASSERT(angle == 0); + x = 0; y = 0; z = 0; w = 1; + return; + } + + Vector3<T> unitAxis = axis.Normalized(); + T sinHalfAngle = sin(angle * T(0.5)); + + w = cos(angle * T(0.5)); + x = unitAxis.x * sinHalfAngle; + y = unitAxis.y * sinHalfAngle; + z = unitAxis.z * sinHalfAngle; + } + + // Constructs quaternion for rotation around one of the coordinate axis by an angle. + Quat(Axis A, T angle, RotateDirection d = Rotate_CCW, HandedSystem s = Handed_R) + { + T sinHalfAngle = s * d *sin(angle * T(0.5)); + T v[3]; + v[0] = v[1] = v[2] = T(0); + v[A] = sinHalfAngle; + + w = cos(angle * T(0.5)); + x = v[0]; + y = v[1]; + z = v[2]; + } + + // Compute axis and angle from quaternion + void GetAxisAngle(Vector3<T>* axis, T* angle) const + { + if ( x*x + y*y + z*z > Math<T>::Tolerance * Math<T>::Tolerance ) { + *axis = Vector3<T>(x, y, z).Normalized(); + *angle = 2 * Acos(w); + if (*angle > Math<T>::Pi) // Reduce the magnitude of the angle, if necessary + { + *angle = Math<T>::TwoPi - *angle; + *axis = *axis * (-1); + } + } + else + { + *axis = Vector3<T>(1, 0, 0); + *angle= 0; + } + } + + // Constructs the quaternion from a rotation matrix + explicit Quat(const Matrix4<T>& m) + { + T trace = m.M[0][0] + m.M[1][1] + m.M[2][2]; + + // In almost all cases, the first part is executed. + // However, if the trace is not positive, the other + // cases arise. + if (trace > T(0)) + { + T s = sqrt(trace + T(1)) * T(2); // s=4*qw + w = T(0.25) * s; + x = (m.M[2][1] - m.M[1][2]) / s; + y = (m.M[0][2] - m.M[2][0]) / s; + z = (m.M[1][0] - m.M[0][1]) / s; + } + else if ((m.M[0][0] > m.M[1][1])&&(m.M[0][0] > m.M[2][2])) + { + T s = sqrt(T(1) + m.M[0][0] - m.M[1][1] - m.M[2][2]) * T(2); + w = (m.M[2][1] - m.M[1][2]) / s; + x = T(0.25) * s; + y = (m.M[0][1] + m.M[1][0]) / s; + z = (m.M[2][0] + m.M[0][2]) / s; + } + else if (m.M[1][1] > m.M[2][2]) + { + T s = sqrt(T(1) + m.M[1][1] - m.M[0][0] - m.M[2][2]) * T(2); // S=4*qy + w = (m.M[0][2] - m.M[2][0]) / s; + x = (m.M[0][1] + m.M[1][0]) / s; + y = T(0.25) * s; + z = (m.M[1][2] + m.M[2][1]) / s; + } + else + { + T s = sqrt(T(1) + m.M[2][2] - m.M[0][0] - m.M[1][1]) * T(2); // S=4*qz + w = (m.M[1][0] - m.M[0][1]) / s; + x = (m.M[0][2] + m.M[2][0]) / s; + y = (m.M[1][2] + m.M[2][1]) / s; + z = T(0.25) * s; + } + } + + // Constructs the quaternion from a rotation matrix + explicit Quat(const Matrix3<T>& m) + { + T trace = m.M[0][0] + m.M[1][1] + m.M[2][2]; + + // In almost all cases, the first part is executed. + // However, if the trace is not positive, the other + // cases arise. + if (trace > T(0)) + { + T s = sqrt(trace + T(1)) * T(2); // s=4*qw + w = T(0.25) * s; + x = (m.M[2][1] - m.M[1][2]) / s; + y = (m.M[0][2] - m.M[2][0]) / s; + z = (m.M[1][0] - m.M[0][1]) / s; + } + else if ((m.M[0][0] > m.M[1][1])&&(m.M[0][0] > m.M[2][2])) + { + T s = sqrt(T(1) + m.M[0][0] - m.M[1][1] - m.M[2][2]) * T(2); + w = (m.M[2][1] - m.M[1][2]) / s; + x = T(0.25) * s; + y = (m.M[0][1] + m.M[1][0]) / s; + z = (m.M[2][0] + m.M[0][2]) / s; + } + else if (m.M[1][1] > m.M[2][2]) + { + T s = sqrt(T(1) + m.M[1][1] - m.M[0][0] - m.M[2][2]) * T(2); // S=4*qy + w = (m.M[0][2] - m.M[2][0]) / s; + x = (m.M[0][1] + m.M[1][0]) / s; + y = T(0.25) * s; + z = (m.M[1][2] + m.M[2][1]) / s; + } + else + { + T s = sqrt(T(1) + m.M[2][2] - m.M[0][0] - m.M[1][1]) * T(2); // S=4*qz + w = (m.M[1][0] - m.M[0][1]) / s; + x = (m.M[0][2] + m.M[2][0]) / s; + y = (m.M[1][2] + m.M[2][1]) / s; + z = T(0.25) * s; + } + } + + bool operator== (const Quat& b) const { return x == b.x && y == b.y && z == b.z && w == b.w; } + bool operator!= (const Quat& b) const { return x != b.x || y != b.y || z != b.z || w != b.w; } + + Quat operator+ (const Quat& b) const { return Quat(x + b.x, y + b.y, z + b.z, w + b.w); } + Quat& operator+= (const Quat& b) { w += b.w; x += b.x; y += b.y; z += b.z; return *this; } + Quat operator- (const Quat& b) const { return Quat(x - b.x, y - b.y, z - b.z, w - b.w); } + Quat& operator-= (const Quat& b) { w -= b.w; x -= b.x; y -= b.y; z -= b.z; return *this; } + + Quat operator* (T s) const { return Quat(x * s, y * s, z * s, w * s); } + Quat& operator*= (T s) { w *= s; x *= s; y *= s; z *= s; return *this; } + Quat operator/ (T s) const { T rcp = T(1)/s; return Quat(x * rcp, y * rcp, z * rcp, w *rcp); } + Quat& operator/= (T s) { T rcp = T(1)/s; w *= rcp; x *= rcp; y *= rcp; z *= rcp; return *this; } + + + // Get Imaginary part vector + Vector3<T> Imag() const { return Vector3<T>(x,y,z); } + + // Get quaternion length. + T Length() const { return sqrt(LengthSq()); } + + // Get quaternion length squared. + T LengthSq() const { return (x * x + y * y + z * z + w * w); } + + // Simple Euclidean distance in R^4 (not SLERP distance, but at least respects Haar measure) + T Distance(const Quat& q) const + { + T d1 = (*this - q).Length(); + T d2 = (*this + q).Length(); // Antipodal point check + return (d1 < d2) ? d1 : d2; + } + + T DistanceSq(const Quat& q) const + { + T d1 = (*this - q).LengthSq(); + T d2 = (*this + q).LengthSq(); // Antipodal point check + return (d1 < d2) ? d1 : d2; + } + + T Dot(const Quat& q) const + { + return x * q.x + y * q.y + z * q.z + w * q.w; + } + + // Angle between two quaternions in radians + T Angle(const Quat& q) const + { + return 2 * Acos(Alg::Abs(Dot(q))); + } + + // Normalize + bool IsNormalized() const { return fabs(LengthSq() - T(1)) < Math<T>::Tolerance; } + + void Normalize() + { + T l = Length(); + OVR_ASSERT(l != T(0)); + *this /= l; + } + + Quat Normalized() const + { + T l = Length(); + OVR_ASSERT(l != T(0)); + return *this / l; + } + + // Returns conjugate of the quaternion. Produces inverse rotation if quaternion is normalized. + Quat Conj() const { return Quat(-x, -y, -z, w); } + + // Quaternion multiplication. Combines quaternion rotations, performing the one on the + // right hand side first. + Quat operator* (const Quat& b) const { return Quat(w * b.x + x * b.w + y * b.z - z * b.y, + w * b.y - x * b.z + y * b.w + z * b.x, + w * b.z + x * b.y - y * b.x + z * b.w, + w * b.w - x * b.x - y * b.y - z * b.z); } + + // + // this^p normalized; same as rotating by this p times. + Quat PowNormalized(T p) const + { + Vector3<T> v; + T a; + GetAxisAngle(&v, &a); + return Quat(v, a * p); + } + + // Normalized linear interpolation of quaternions + Quat Nlerp(const Quat& other, T a) + { + T sign = (Dot(other) >= 0) ? 1 : -1; + return (*this * sign * a + other * (1-a)).Normalized(); + } + + // Rotate transforms vector in a manner that matches Matrix rotations (counter-clockwise, + // assuming negative direction of the axis). Standard formula: q(t) * V * q(t)^-1. + Vector3<T> Rotate(const Vector3<T>& v) const + { + return ((*this * Quat<T>(v.x, v.y, v.z, T(0))) * Inverted()).Imag(); + } + + // Inversed quaternion rotates in the opposite direction. + Quat Inverted() const + { + return Quat(-x, -y, -z, w); + } + + // Sets this quaternion to the one rotates in the opposite direction. + void Invert() + { + *this = Quat(-x, -y, -z, w); + } + + // GetEulerAngles extracts Euler angles from the quaternion, in the specified order of + // axis rotations and the specified coordinate system. Right-handed coordinate system + // is the default, with CCW rotations while looking in the negative axis direction. + // Here a,b,c, are the Yaw/Pitch/Roll angles to be returned. + // rotation a around axis A1 + // is followed by rotation b around axis A2 + // is followed by rotation c around axis A3 + // rotations are CCW or CW (D) in LH or RH coordinate system (S) + template <Axis A1, Axis A2, Axis A3, RotateDirection D, HandedSystem S> + void GetEulerAngles(T *a, T *b, T *c) const + { + OVR_COMPILER_ASSERT((A1 != A2) && (A2 != A3) && (A1 != A3)); + + T Q[3] = { x, y, z }; //Quaternion components x,y,z + + T ww = w*w; + T Q11 = Q[A1]*Q[A1]; + T Q22 = Q[A2]*Q[A2]; + T Q33 = Q[A3]*Q[A3]; + + T psign = T(-1); + // Determine whether even permutation + if (((A1 + 1) % 3 == A2) && ((A2 + 1) % 3 == A3)) + psign = T(1); + + T s2 = psign * T(2) * (psign*w*Q[A2] + Q[A1]*Q[A3]); + + if (s2 < T(-1) + Math<T>::SingularityRadius) + { // South pole singularity + *a = T(0); + *b = -S*D*Math<T>::PiOver2; + *c = S*D*atan2(T(2)*(psign*Q[A1]*Q[A2] + w*Q[A3]), + ww + Q22 - Q11 - Q33 ); + } + else if (s2 > T(1) - Math<T>::SingularityRadius) + { // North pole singularity + *a = T(0); + *b = S*D*Math<T>::PiOver2; + *c = S*D*atan2(T(2)*(psign*Q[A1]*Q[A2] + w*Q[A3]), + ww + Q22 - Q11 - Q33); + } + else + { + *a = -S*D*atan2(T(-2)*(w*Q[A1] - psign*Q[A2]*Q[A3]), + ww + Q33 - Q11 - Q22); + *b = S*D*asin(s2); + *c = S*D*atan2(T(2)*(w*Q[A3] - psign*Q[A1]*Q[A2]), + ww + Q11 - Q22 - Q33); + } + return; + } + + template <Axis A1, Axis A2, Axis A3, RotateDirection D> + void GetEulerAngles(T *a, T *b, T *c) const + { GetEulerAngles<A1, A2, A3, D, Handed_R>(a, b, c); } + + template <Axis A1, Axis A2, Axis A3> + void GetEulerAngles(T *a, T *b, T *c) const + { GetEulerAngles<A1, A2, A3, Rotate_CCW, Handed_R>(a, b, c); } + + + // GetEulerAnglesABA extracts Euler angles from the quaternion, in the specified order of + // axis rotations and the specified coordinate system. Right-handed coordinate system + // is the default, with CCW rotations while looking in the negative axis direction. + // Here a,b,c, are the Yaw/Pitch/Roll angles to be returned. + // rotation a around axis A1 + // is followed by rotation b around axis A2 + // is followed by rotation c around axis A1 + // Rotations are CCW or CW (D) in LH or RH coordinate system (S) + template <Axis A1, Axis A2, RotateDirection D, HandedSystem S> + void GetEulerAnglesABA(T *a, T *b, T *c) const + { + OVR_COMPILER_ASSERT(A1 != A2); + + T Q[3] = {x, y, z}; // Quaternion components + + // Determine the missing axis that was not supplied + int m = 3 - A1 - A2; + + T ww = w*w; + T Q11 = Q[A1]*Q[A1]; + T Q22 = Q[A2]*Q[A2]; + T Qmm = Q[m]*Q[m]; + + T psign = T(-1); + if ((A1 + 1) % 3 == A2) // Determine whether even permutation + { + psign = T(1); + } + + T c2 = ww + Q11 - Q22 - Qmm; + if (c2 < T(-1) + Math<T>::SingularityRadius) + { // South pole singularity + *a = T(0); + *b = S*D*Math<T>::Pi; + *c = S*D*atan2( T(2)*(w*Q[A1] - psign*Q[A2]*Q[m]), + ww + Q22 - Q11 - Qmm); + } + else if (c2 > T(1) - Math<T>::SingularityRadius) + { // North pole singularity + *a = T(0); + *b = T(0); + *c = S*D*atan2( T(2)*(w*Q[A1] - psign*Q[A2]*Q[m]), + ww + Q22 - Q11 - Qmm); + } + else + { + *a = S*D*atan2( psign*w*Q[m] + Q[A1]*Q[A2], + w*Q[A2] -psign*Q[A1]*Q[m]); + *b = S*D*acos(c2); + *c = S*D*atan2( -psign*w*Q[m] + Q[A1]*Q[A2], + w*Q[A2] + psign*Q[A1]*Q[m]); + } + return; + } +}; + +typedef Quat<float> Quatf; +typedef Quat<double> Quatd; + +//------------------------------------------------------------------------------------- +// ***** Pose + +// Position and orientation combined. + +template<class T> +class Transform +{ +public: + + typedef typename CompatibleTypes<Transform<T> >::Type CompatibleType; + + Transform() { } + Transform(const Quat<T>& orientation, const Vector3<T>& pos) + : Rotation(orientation), Translation(pos) { } + Transform(const Transform& s) + : Rotation(s.Rotation), Translation(s.Translation) { } + Transform(const CompatibleType& s) + : Rotation(s.Orientation), Translation(s.Position) { } + explicit Transform(const Transform<typename Math<T>::OtherFloatType> &s) + : Rotation(s.Rotation), Translation(s.Translation) { } + + operator typename CompatibleTypes<Transform<T> >::Type () const + { + typename CompatibleTypes<Transform<T> >::Type result; + result.Orientation = Rotation; + result.Position = Translation; + return result; + } + + Quat<T> Rotation; + Vector3<T> Translation; + + Vector3<T> Rotate(const Vector3<T>& v) const + { + return Rotation.Rotate(v); + } + + Vector3<T> Translate(const Vector3<T>& v) const + { + return v + Translation; + } + + Vector3<T> Apply(const Vector3<T>& v) const + { + return Translate(Rotate(v)); + } + + Transform operator*(const Transform& other) const + { + return Transform(Rotation * other.Rotation, Apply(other.Translation)); + } + + PoseState<T> operator*(const PoseState<T>& poseState) const + { + PoseState<T> result; + result.Pose = (*this) * poseState.Pose; + result.LinearVelocity = this->Rotate(poseState.LinearVelocity); + result.LinearAcceleration = this->Rotate(poseState.LinearAcceleration); + result.AngularVelocity = this->Rotate(poseState.AngularVelocity); + result.AngularAcceleration = this->Rotate(poseState.AngularAcceleration); + return result; + } + + Transform Inverted() const + { + Quat<T> inv = Rotation.Inverted(); + return Transform(inv, inv.Rotate(-Translation)); + } +}; + +typedef Transform<float> Transformf; +typedef Transform<double> Transformd; + + +//------------------------------------------------------------------------------------- +// ***** Matrix4 +// +// Matrix4 is a 4x4 matrix used for 3d transformations and projections. +// Translation stored in the last column. +// The matrix is stored in row-major order in memory, meaning that values +// of the first row are stored before the next one. +// +// The arrangement of the matrix is chosen to be in Right-Handed +// coordinate system and counterclockwise rotations when looking down +// the axis +// +// Transformation Order: +// - Transformations are applied from right to left, so the expression +// M1 * M2 * M3 * V means that the vector V is transformed by M3 first, +// followed by M2 and M1. +// +// Coordinate system: Right Handed +// +// Rotations: Counterclockwise when looking down the axis. All angles are in radians. +// +// | sx 01 02 tx | // First column (sx, 10, 20): Axis X basis vector. +// | 10 sy 12 ty | // Second column (01, sy, 21): Axis Y basis vector. +// | 20 21 sz tz | // Third columnt (02, 12, sz): Axis Z basis vector. +// | 30 31 32 33 | +// +// The basis vectors are first three columns. + +template<class T> +class Matrix4 +{ + static const Matrix4 IdentityValue; + +public: + T M[4][4]; + + enum NoInitType { NoInit }; + + // Construct with no memory initialization. + Matrix4(NoInitType) { } + + // By default, we construct identity matrix. + Matrix4() + { + SetIdentity(); + } + + Matrix4(T m11, T m12, T m13, T m14, + T m21, T m22, T m23, T m24, + T m31, T m32, T m33, T m34, + T m41, T m42, T m43, T m44) + { + M[0][0] = m11; M[0][1] = m12; M[0][2] = m13; M[0][3] = m14; + M[1][0] = m21; M[1][1] = m22; M[1][2] = m23; M[1][3] = m24; + M[2][0] = m31; M[2][1] = m32; M[2][2] = m33; M[2][3] = m34; + M[3][0] = m41; M[3][1] = m42; M[3][2] = m43; M[3][3] = m44; + } + + Matrix4(T m11, T m12, T m13, + T m21, T m22, T m23, + T m31, T m32, T m33) + { + M[0][0] = m11; M[0][1] = m12; M[0][2] = m13; M[0][3] = 0; + M[1][0] = m21; M[1][1] = m22; M[1][2] = m23; M[1][3] = 0; + M[2][0] = m31; M[2][1] = m32; M[2][2] = m33; M[2][3] = 0; + M[3][0] = 0; M[3][1] = 0; M[3][2] = 0; M[3][3] = 1; + } + + explicit Matrix4(const Quat<T>& q) + { + T ww = q.w*q.w; + T xx = q.x*q.x; + T yy = q.y*q.y; + T zz = q.z*q.z; + + M[0][0] = ww + xx - yy - zz; M[0][1] = 2 * (q.x*q.y - q.w*q.z); M[0][2] = 2 * (q.x*q.z + q.w*q.y); M[0][3] = 0; + M[1][0] = 2 * (q.x*q.y + q.w*q.z); M[1][1] = ww - xx + yy - zz; M[1][2] = 2 * (q.y*q.z - q.w*q.x); M[1][3] = 0; + M[2][0] = 2 * (q.x*q.z - q.w*q.y); M[2][1] = 2 * (q.y*q.z + q.w*q.x); M[2][2] = ww - xx - yy + zz; M[2][3] = 0; + M[3][0] = 0; M[3][1] = 0; M[3][2] = 0; M[3][3] = 1; + } + + explicit Matrix4(const Transform<T>& p) + { + Matrix4 result(p.Rotation); + result.SetTranslation(p.Translation); + *this = result; + } + + // C-interop support + explicit Matrix4(const Matrix4<typename Math<T>::OtherFloatType> &src) + { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + M[i][j] = (T)src.M[i][j]; + } + + // C-interop support. + Matrix4(const typename CompatibleTypes<Matrix4<T> >::Type& s) + { + OVR_COMPILER_ASSERT(sizeof(s) == sizeof(Matrix4)); + memcpy(M, s.M, sizeof(M)); + } + + operator typename CompatibleTypes<Matrix4<T> >::Type () const + { + typename CompatibleTypes<Matrix4<T> >::Type result; + OVR_COMPILER_ASSERT(sizeof(result) == sizeof(Matrix4)); + memcpy(result.M, M, sizeof(M)); + return result; + } + + void ToString(char* dest, UPInt destsize) const + { + UPInt pos = 0; + for (int r=0; r<4; r++) + for (int c=0; c<4; c++) + pos += OVR_sprintf(dest+pos, destsize-pos, "%g ", M[r][c]); + } + + static Matrix4 FromString(const char* src) + { + Matrix4 result; + for (int r=0; r<4; r++) + for (int c=0; c<4; c++) + { + result.M[r][c] = (T)atof(src); + while (src && *src != ' ') + src++; + while (src && *src == ' ') + src++; + } + return result; + } + + static const Matrix4& Identity() { return IdentityValue; } + + void SetIdentity() + { + M[0][0] = M[1][1] = M[2][2] = M[3][3] = 1; + M[0][1] = M[1][0] = M[2][3] = M[3][1] = 0; + M[0][2] = M[1][2] = M[2][0] = M[3][2] = 0; + M[0][3] = M[1][3] = M[2][1] = M[3][0] = 0; + } + + bool operator== (const Matrix4& b) const + { + bool isEqual = true; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + isEqual &= (M[i][j] == b.M[i][j]); + + return isEqual; + } + + Matrix4 operator+ (const Matrix4& b) const + { + Matrix4 result(*this); + result += b; + return result; + } + + Matrix4& operator+= (const Matrix4& b) + { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + M[i][j] += b.M[i][j]; + return *this; + } + + Matrix4 operator- (const Matrix4& b) const + { + Matrix4 result(*this); + result -= b; + return result; + } + + Matrix4& operator-= (const Matrix4& b) + { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + M[i][j] -= b.M[i][j]; + return *this; + } + + // Multiplies two matrices into destination with minimum copying. + static Matrix4& Multiply(Matrix4* d, const Matrix4& a, const Matrix4& b) + { + OVR_ASSERT((d != &a) && (d != &b)); + int i = 0; + do { + d->M[i][0] = a.M[i][0] * b.M[0][0] + a.M[i][1] * b.M[1][0] + a.M[i][2] * b.M[2][0] + a.M[i][3] * b.M[3][0]; + d->M[i][1] = a.M[i][0] * b.M[0][1] + a.M[i][1] * b.M[1][1] + a.M[i][2] * b.M[2][1] + a.M[i][3] * b.M[3][1]; + d->M[i][2] = a.M[i][0] * b.M[0][2] + a.M[i][1] * b.M[1][2] + a.M[i][2] * b.M[2][2] + a.M[i][3] * b.M[3][2]; + d->M[i][3] = a.M[i][0] * b.M[0][3] + a.M[i][1] * b.M[1][3] + a.M[i][2] * b.M[2][3] + a.M[i][3] * b.M[3][3]; + } while((++i) < 4); + + return *d; + } + + Matrix4 operator* (const Matrix4& b) const + { + Matrix4 result(Matrix4::NoInit); + Multiply(&result, *this, b); + return result; + } + + Matrix4& operator*= (const Matrix4& b) + { + return Multiply(this, Matrix4(*this), b); + } + + Matrix4 operator* (T s) const + { + Matrix4 result(*this); + result *= s; + return result; + } + + Matrix4& operator*= (T s) + { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + M[i][j] *= s; + return *this; + } + + + Matrix4 operator/ (T s) const + { + Matrix4 result(*this); + result /= s; + return result; + } + + Matrix4& operator/= (T s) + { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + M[i][j] /= s; + return *this; + } + + Vector3<T> Transform(const Vector3<T>& v) const + { + return Vector3<T>(M[0][0] * v.x + M[0][1] * v.y + M[0][2] * v.z + M[0][3], + M[1][0] * v.x + M[1][1] * v.y + M[1][2] * v.z + M[1][3], + M[2][0] * v.x + M[2][1] * v.y + M[2][2] * v.z + M[2][3]); + } + + Matrix4 Transposed() const + { + return Matrix4(M[0][0], M[1][0], M[2][0], M[3][0], + M[0][1], M[1][1], M[2][1], M[3][1], + M[0][2], M[1][2], M[2][2], M[3][2], + M[0][3], M[1][3], M[2][3], M[3][3]); + } + + void Transpose() + { + *this = Transposed(); + } + + + T SubDet (const UPInt* rows, const UPInt* cols) const + { + return M[rows[0]][cols[0]] * (M[rows[1]][cols[1]] * M[rows[2]][cols[2]] - M[rows[1]][cols[2]] * M[rows[2]][cols[1]]) + - M[rows[0]][cols[1]] * (M[rows[1]][cols[0]] * M[rows[2]][cols[2]] - M[rows[1]][cols[2]] * M[rows[2]][cols[0]]) + + M[rows[0]][cols[2]] * (M[rows[1]][cols[0]] * M[rows[2]][cols[1]] - M[rows[1]][cols[1]] * M[rows[2]][cols[0]]); + } + + T Cofactor(UPInt I, UPInt J) const + { + const UPInt indices[4][3] = {{1,2,3},{0,2,3},{0,1,3},{0,1,2}}; + return ((I+J)&1) ? -SubDet(indices[I],indices[J]) : SubDet(indices[I],indices[J]); + } + + T Determinant() const + { + return M[0][0] * Cofactor(0,0) + M[0][1] * Cofactor(0,1) + M[0][2] * Cofactor(0,2) + M[0][3] * Cofactor(0,3); + } + + Matrix4 Adjugated() const + { + return Matrix4(Cofactor(0,0), Cofactor(1,0), Cofactor(2,0), Cofactor(3,0), + Cofactor(0,1), Cofactor(1,1), Cofactor(2,1), Cofactor(3,1), + Cofactor(0,2), Cofactor(1,2), Cofactor(2,2), Cofactor(3,2), + Cofactor(0,3), Cofactor(1,3), Cofactor(2,3), Cofactor(3,3)); + } + + Matrix4 Inverted() const + { + T det = Determinant(); + assert(det != 0); + return Adjugated() * (1.0f/det); + } + + void Invert() + { + *this = Inverted(); + } + + // This is more efficient than general inverse, but ONLY works + // correctly if it is a homogeneous transform matrix (rot + trans) + Matrix4 InvertedHomogeneousTransform() const + { + // Make the inverse rotation matrix + Matrix4 rinv = this->Transposed(); + rinv.M[3][0] = rinv.M[3][1] = rinv.M[3][2] = 0.0f; + // Make the inverse translation matrix + Vector3<T> tvinv(-M[0][3],-M[1][3],-M[2][3]); + Matrix4 tinv = Matrix4::Translation(tvinv); + return rinv * tinv; // "untranslate", then "unrotate" + } + + // This is more efficient than general inverse, but ONLY works + // correctly if it is a homogeneous transform matrix (rot + trans) + void InvertHomogeneousTransform() + { + *this = InvertedHomogeneousTransform(); + } + + // Matrix to Euler Angles conversion + // a,b,c, are the YawPitchRoll angles to be returned + // rotation a around axis A1 + // is followed by rotation b around axis A2 + // is followed by rotation c around axis A3 + // rotations are CCW or CW (D) in LH or RH coordinate system (S) + template <Axis A1, Axis A2, Axis A3, RotateDirection D, HandedSystem S> + void ToEulerAngles(T *a, T *b, T *c) + { + OVR_COMPILER_ASSERT((A1 != A2) && (A2 != A3) && (A1 != A3)); + + T psign = -1; + if (((A1 + 1) % 3 == A2) && ((A2 + 1) % 3 == A3)) // Determine whether even permutation + psign = 1; + + T pm = psign*M[A1][A3]; + if (pm < -1.0f + Math<T>::SingularityRadius) + { // South pole singularity + *a = 0; + *b = -S*D*Math<T>::PiOver2; + *c = S*D*atan2( psign*M[A2][A1], M[A2][A2] ); + } + else if (pm > 1.0f - Math<T>::SingularityRadius) + { // North pole singularity + *a = 0; + *b = S*D*Math<T>::PiOver2; + *c = S*D*atan2( psign*M[A2][A1], M[A2][A2] ); + } + else + { // Normal case (nonsingular) + *a = S*D*atan2( -psign*M[A2][A3], M[A3][A3] ); + *b = S*D*asin(pm); + *c = S*D*atan2( -psign*M[A1][A2], M[A1][A1] ); + } + + return; + } + + // Matrix to Euler Angles conversion + // a,b,c, are the YawPitchRoll angles to be returned + // rotation a around axis A1 + // is followed by rotation b around axis A2 + // is followed by rotation c around axis A1 + // rotations are CCW or CW (D) in LH or RH coordinate system (S) + template <Axis A1, Axis A2, RotateDirection D, HandedSystem S> + void ToEulerAnglesABA(T *a, T *b, T *c) + { + OVR_COMPILER_ASSERT(A1 != A2); + + // Determine the axis that was not supplied + int m = 3 - A1 - A2; + + T psign = -1; + if ((A1 + 1) % 3 == A2) // Determine whether even permutation + psign = 1.0f; + + T c2 = M[A1][A1]; + if (c2 < -1 + Math<T>::SingularityRadius) + { // South pole singularity + *a = 0; + *b = S*D*Math<T>::Pi; + *c = S*D*atan2( -psign*M[A2][m],M[A2][A2]); + } + else if (c2 > 1.0f - Math<T>::SingularityRadius) + { // North pole singularity + *a = 0; + *b = 0; + *c = S*D*atan2( -psign*M[A2][m],M[A2][A2]); + } + else + { // Normal case (nonsingular) + *a = S*D*atan2( M[A2][A1],-psign*M[m][A1]); + *b = S*D*acos(c2); + *c = S*D*atan2( M[A1][A2],psign*M[A1][m]); + } + return; + } + + // Creates a matrix that converts the vertices from one coordinate system + // to another. + static Matrix4 AxisConversion(const WorldAxes& to, const WorldAxes& from) + { + // Holds axis values from the 'to' structure + int toArray[3] = { to.XAxis, to.YAxis, to.ZAxis }; + + // The inverse of the toArray + int inv[4]; + inv[0] = inv[abs(to.XAxis)] = 0; + inv[abs(to.YAxis)] = 1; + inv[abs(to.ZAxis)] = 2; + + Matrix4 m(0, 0, 0, + 0, 0, 0, + 0, 0, 0); + + // Only three values in the matrix need to be changed to 1 or -1. + m.M[inv[abs(from.XAxis)]][0] = T(from.XAxis/toArray[inv[abs(from.XAxis)]]); + m.M[inv[abs(from.YAxis)]][1] = T(from.YAxis/toArray[inv[abs(from.YAxis)]]); + m.M[inv[abs(from.ZAxis)]][2] = T(from.ZAxis/toArray[inv[abs(from.ZAxis)]]); + return m; + } + + + // Creates a matrix for translation by vector + static Matrix4 Translation(const Vector3<T>& v) + { + Matrix4 t; + t.M[0][3] = v.x; + t.M[1][3] = v.y; + t.M[2][3] = v.z; + return t; + } + + // Creates a matrix for translation by vector + static Matrix4 Translation(T x, T y, T z = 0.0f) + { + Matrix4 t; + t.M[0][3] = x; + t.M[1][3] = y; + t.M[2][3] = z; + return t; + } + + // Sets the translation part + void SetTranslation(const Vector3<T>& v) + { + M[0][3] = v.x; + M[1][3] = v.y; + M[2][3] = v.z; + } + + Vector3<T> GetTranslation() const + { + return Vector3<T>( M[0][3], M[1][3], M[2][3] ); + } + + // Creates a matrix for scaling by vector + static Matrix4 Scaling(const Vector3<T>& v) + { + Matrix4 t; + t.M[0][0] = v.x; + t.M[1][1] = v.y; + t.M[2][2] = v.z; + return t; + } + + // Creates a matrix for scaling by vector + static Matrix4 Scaling(T x, T y, T z) + { + Matrix4 t; + t.M[0][0] = x; + t.M[1][1] = y; + t.M[2][2] = z; + return t; + } + + // Creates a matrix for scaling by constant + static Matrix4 Scaling(T s) + { + Matrix4 t; + t.M[0][0] = s; + t.M[1][1] = s; + t.M[2][2] = s; + return t; + } + + // Simple L1 distance in R^12 + T Distance(const Matrix4& m2) const + { + T d = fabs(M[0][0] - m2.M[0][0]) + fabs(M[0][1] - m2.M[0][1]); + d += fabs(M[0][2] - m2.M[0][2]) + fabs(M[0][3] - m2.M[0][3]); + d += fabs(M[1][0] - m2.M[1][0]) + fabs(M[1][1] - m2.M[1][1]); + d += fabs(M[1][2] - m2.M[1][2]) + fabs(M[1][3] - m2.M[1][3]); + d += fabs(M[2][0] - m2.M[2][0]) + fabs(M[2][1] - m2.M[2][1]); + d += fabs(M[2][2] - m2.M[2][2]) + fabs(M[2][3] - m2.M[2][3]); + d += fabs(M[3][0] - m2.M[3][0]) + fabs(M[3][1] - m2.M[3][1]); + d += fabs(M[3][2] - m2.M[3][2]) + fabs(M[3][3] - m2.M[3][3]); + return d; + } + + // Creates a rotation matrix rotating around the X axis by 'angle' radians. + // Just for quick testing. Not for final API. Need to remove case. + static Matrix4 RotationAxis(Axis A, T angle, RotateDirection d, HandedSystem s) + { + T sina = s * d *sin(angle); + T cosa = cos(angle); + + switch(A) + { + case Axis_X: + return Matrix4(1, 0, 0, + 0, cosa, -sina, + 0, sina, cosa); + case Axis_Y: + return Matrix4(cosa, 0, sina, + 0, 1, 0, + -sina, 0, cosa); + case Axis_Z: + return Matrix4(cosa, -sina, 0, + sina, cosa, 0, + 0, 0, 1); + } + } + + + // Creates a rotation matrix rotating around the X axis by 'angle' radians. + // Rotation direction is depends on the coordinate system: + // RHS (Oculus default): Positive angle values rotate Counter-clockwise (CCW), + // while looking in the negative axis direction. This is the + // same as looking down from positive axis values towards origin. + // LHS: Positive angle values rotate clock-wise (CW), while looking in the + // negative axis direction. + static Matrix4 RotationX(T angle) + { + T sina = sin(angle); + T cosa = cos(angle); + return Matrix4(1, 0, 0, + 0, cosa, -sina, + 0, sina, cosa); + } + + // Creates a rotation matrix rotating around the Y axis by 'angle' radians. + // Rotation direction is depends on the coordinate system: + // RHS (Oculus default): Positive angle values rotate Counter-clockwise (CCW), + // while looking in the negative axis direction. This is the + // same as looking down from positive axis values towards origin. + // LHS: Positive angle values rotate clock-wise (CW), while looking in the + // negative axis direction. + static Matrix4 RotationY(T angle) + { + T sina = sin(angle); + T cosa = cos(angle); + return Matrix4(cosa, 0, sina, + 0, 1, 0, + -sina, 0, cosa); + } + + // Creates a rotation matrix rotating around the Z axis by 'angle' radians. + // Rotation direction is depends on the coordinate system: + // RHS (Oculus default): Positive angle values rotate Counter-clockwise (CCW), + // while looking in the negative axis direction. This is the + // same as looking down from positive axis values towards origin. + // LHS: Positive angle values rotate clock-wise (CW), while looking in the + // negative axis direction. + static Matrix4 RotationZ(T angle) + { + T sina = sin(angle); + T cosa = cos(angle); + return Matrix4(cosa, -sina, 0, + sina, cosa, 0, + 0, 0, 1); + } + + // LookAtRH creates a View transformation matrix for right-handed coordinate system. + // The resulting matrix points camera from 'eye' towards 'at' direction, with 'up' + // specifying the up vector. The resulting matrix should be used with PerspectiveRH + // projection. + static Matrix4 LookAtRH(const Vector3<T>& eye, const Vector3<T>& at, const Vector3<T>& up) + { + Vector3<T> z = (eye - at).Normalized(); // Forward + Vector3<T> x = up.Cross(z).Normalized(); // Right + Vector3<T> y = z.Cross(x); + + Matrix4 m(x.x, x.y, x.z, -(x.Dot(eye)), + y.x, y.y, y.z, -(y.Dot(eye)), + z.x, z.y, z.z, -(z.Dot(eye)), + 0, 0, 0, 1 ); + return m; + } + + // LookAtLH creates a View transformation matrix for left-handed coordinate system. + // The resulting matrix points camera from 'eye' towards 'at' direction, with 'up' + // specifying the up vector. + static Matrix4 LookAtLH(const Vector3<T>& eye, const Vector3<T>& at, const Vector3<T>& up) + { + Vector3<T> z = (at - eye).Normalized(); // Forward + Vector3<T> x = up.Cross(z).Normalized(); // Right + Vector3<T> y = z.Cross(x); + + Matrix4 m(x.x, x.y, x.z, -(x.Dot(eye)), + y.x, y.y, y.z, -(y.Dot(eye)), + z.x, z.y, z.z, -(z.Dot(eye)), + 0, 0, 0, 1 ); + return m; + } + + // PerspectiveRH creates a right-handed perspective projection matrix that can be + // used with the Oculus sample renderer. + // yfov - Specifies vertical field of view in radians. + // aspect - Screen aspect ration, which is usually width/height for square pixels. + // Note that xfov = yfov * aspect. + // znear - Absolute value of near Z clipping clipping range. + // zfar - Absolute value of far Z clipping clipping range (larger then near). + // Even though RHS usually looks in the direction of negative Z, positive values + // are expected for znear and zfar. + static Matrix4 PerspectiveRH(T yfov, T aspect, T znear, T zfar) + { + Matrix4 m; + T tanHalfFov = tan(yfov * 0.5f); + + m.M[0][0] = 1 / (aspect * tanHalfFov); + m.M[1][1] = 1 / tanHalfFov; + m.M[2][2] = zfar / (zfar - znear); + m.M[3][2] = 1; + m.M[2][3] = (zfar * znear) / (znear - zfar); + m.M[3][3] = 0; + + // Note: Post-projection matrix result assumes Left-Handed coordinate system, + // with Y up, X right and Z forward. This supports positive z-buffer values. + return m; + } + + // PerspectiveRH creates a left-handed perspective projection matrix that can be + // used with the Oculus sample renderer. + // yfov - Specifies vertical field of view in radians. + // aspect - Screen aspect ration, which is usually width/height for square pixels. + // Note that xfov = yfov * aspect. + // znear - Absolute value of near Z clipping clipping range. + // zfar - Absolute value of far Z clipping clipping range (larger then near). + static Matrix4 PerspectiveLH(T yfov, T aspect, T znear, T zfar) + { + Matrix4 m; + T tanHalfFov = tan(yfov * 0.5f); + + m.M[0][0] = 1.0 / (aspect * tanHalfFov); + m.M[1][1] = 1.0 / tanHalfFov; + m.M[2][2] = zfar / (znear - zfar); + // m.M[2][2] = zfar / (zfar - znear); + m.M[3][2] = -1.0; + m.M[2][3] = (zfar * znear) / (znear - zfar); + m.M[3][3] = 0.0; + + // Note: Post-projection matrix result assumes Left-Handed coordinate system, + // with Y up, X right and Z forward. This supports positive z-buffer values. + // This is the case even for RHS cooridnate input. + return m; + } + + static Matrix4 Ortho2D(T w, T h) + { + Matrix4 m; + m.M[0][0] = 2.0/w; + m.M[1][1] = -2.0/h; + m.M[0][3] = -1.0; + m.M[1][3] = 1.0; + m.M[2][2] = 0; + return m; + } +}; + +typedef Matrix4<float> Matrix4f; +typedef Matrix4<double> Matrix4d; + +//------------------------------------------------------------------------------------- +// ***** Matrix3 +// +// Matrix3 is a 3x3 matrix used for representing a rotation matrix. +// The matrix is stored in row-major order in memory, meaning that values +// of the first row are stored before the next one. +// +// The arrangement of the matrix is chosen to be in Right-Handed +// coordinate system and counterclockwise rotations when looking down +// the axis +// +// Transformation Order: +// - Transformations are applied from right to left, so the expression +// M1 * M2 * M3 * V means that the vector V is transformed by M3 first, +// followed by M2 and M1. +// +// Coordinate system: Right Handed +// +// Rotations: Counterclockwise when looking down the axis. All angles are in radians. + +template<typename T> +class SymMat3; + +template<class T> +class Matrix3 +{ + static const Matrix3 IdentityValue; + +public: + T M[3][3]; + + enum NoInitType { NoInit }; + + // Construct with no memory initialization. + Matrix3(NoInitType) { } + + // By default, we construct identity matrix. + Matrix3() + { + SetIdentity(); + } + + Matrix3(T m11, T m12, T m13, + T m21, T m22, T m23, + T m31, T m32, T m33) + { + M[0][0] = m11; M[0][1] = m12; M[0][2] = m13; + M[1][0] = m21; M[1][1] = m22; M[1][2] = m23; + M[2][0] = m31; M[2][1] = m32; M[2][2] = m33; + } + + /* + explicit Matrix3(const Quat<T>& q) + { + T ww = q.w*q.w; + T xx = q.x*q.x; + T yy = q.y*q.y; + T zz = q.z*q.z; + + M[0][0] = ww + xx - yy - zz; M[0][1] = 2 * (q.x*q.y - q.w*q.z); M[0][2] = 2 * (q.x*q.z + q.w*q.y); + M[1][0] = 2 * (q.x*q.y + q.w*q.z); M[1][1] = ww - xx + yy - zz; M[1][2] = 2 * (q.y*q.z - q.w*q.x); + M[2][0] = 2 * (q.x*q.z - q.w*q.y); M[2][1] = 2 * (q.y*q.z + q.w*q.x); M[2][2] = ww - xx - yy + zz; + } + */ + + explicit Matrix3(const Quat<T>& q) + { + const T tx = q.x+q.x, ty = q.y+q.y, tz = q.z+q.z; + const T twx = q.w*tx, twy = q.w*ty, twz = q.w*tz; + const T txx = q.x*tx, txy = q.x*ty, txz = q.x*tz; + const T tyy = q.y*ty, tyz = q.y*tz, tzz = q.z*tz; + M[0][0] = T(1) - (tyy + tzz); M[0][1] = txy - twz; M[0][2] = txz + twy; + M[1][0] = txy + twz; M[1][1] = T(1) - (txx + tzz); M[1][2] = tyz - twx; + M[2][0] = txz - twy; M[2][1] = tyz + twx; M[2][2] = T(1) - (txx + tyy); + } + + inline explicit Matrix3(T s) + { + M[0][0] = M[1][1] = M[2][2] = s; + M[0][1] = M[0][2] = M[1][0] = M[1][2] = M[2][0] = M[2][1] = 0; + } + + explicit Matrix3(const Transform<T>& p) + { + Matrix3 result(p.Rotation); + result.SetTranslation(p.Translation); + *this = result; + } + + // C-interop support + explicit Matrix3(const Matrix4<typename Math<T>::OtherFloatType> &src) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] = (T)src.M[i][j]; + } + + // C-interop support. + Matrix3(const typename CompatibleTypes<Matrix3<T> >::Type& s) + { + OVR_COMPILER_ASSERT(sizeof(s) == sizeof(Matrix3)); + memcpy(M, s.M, sizeof(M)); + } + + operator typename CompatibleTypes<Matrix3<T> >::Type () const + { + typename CompatibleTypes<Matrix3<T> >::Type result; + OVR_COMPILER_ASSERT(sizeof(result) == sizeof(Matrix3)); + memcpy(result.M, M, sizeof(M)); + return result; + } + + void ToString(char* dest, UPInt destsize) const + { + UPInt pos = 0; + for (int r=0; r<3; r++) + for (int c=0; c<3; c++) + pos += OVR_sprintf(dest+pos, destsize-pos, "%g ", M[r][c]); + } + + static Matrix3 FromString(const char* src) + { + Matrix3 result; + for (int r=0; r<3; r++) + for (int c=0; c<3; c++) + { + result.M[r][c] = (T)atof(src); + while (src && *src != ' ') + src++; + while (src && *src == ' ') + src++; + } + return result; + } + + static const Matrix3& Identity() { return IdentityValue; } + + void SetIdentity() + { + M[0][0] = M[1][1] = M[2][2] = 1; + M[0][1] = M[1][0] = M[2][0] = 0; + M[0][2] = M[1][2] = M[2][1] = 0; + } + + bool operator== (const Matrix3& b) const + { + bool isEqual = true; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + isEqual &= (M[i][j] == b.M[i][j]); + + return isEqual; + } + + Matrix3 operator+ (const Matrix3& b) const + { + Matrix4<T> result(*this); + result += b; + return result; + } + + Matrix3& operator+= (const Matrix3& b) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] += b.M[i][j]; + return *this; + } + + void operator= (const Matrix3& b) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] = b.M[i][j]; + return; + } + + void operator= (const SymMat3<T>& b) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] = 0; + + M[0][0] = b.v[0]; + M[0][1] = b.v[1]; + M[0][2] = b.v[2]; + M[1][1] = b.v[3]; + M[1][2] = b.v[4]; + M[2][2] = b.v[5]; + + return; + } + + Matrix3 operator- (const Matrix3& b) const + { + Matrix3 result(*this); + result -= b; + return result; + } + + Matrix3& operator-= (const Matrix3& b) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] -= b.M[i][j]; + return *this; + } + + // Multiplies two matrices into destination with minimum copying. + static Matrix3& Multiply(Matrix3* d, const Matrix3& a, const Matrix3& b) + { + OVR_ASSERT((d != &a) && (d != &b)); + int i = 0; + do { + d->M[i][0] = a.M[i][0] * b.M[0][0] + a.M[i][1] * b.M[1][0] + a.M[i][2] * b.M[2][0]; + d->M[i][1] = a.M[i][0] * b.M[0][1] + a.M[i][1] * b.M[1][1] + a.M[i][2] * b.M[2][1]; + d->M[i][2] = a.M[i][0] * b.M[0][2] + a.M[i][1] * b.M[1][2] + a.M[i][2] * b.M[2][2]; + } while((++i) < 3); + + return *d; + } + + Matrix3 operator* (const Matrix3& b) const + { + Matrix3 result(Matrix3::NoInit); + Multiply(&result, *this, b); + return result; + } + + Matrix3& operator*= (const Matrix3& b) + { + return Multiply(this, Matrix3(*this), b); + } + + Matrix3 operator* (T s) const + { + Matrix3 result(*this); + result *= s; + return result; + } + + Matrix3& operator*= (T s) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] *= s; + return *this; + } + + Vector3<T> operator* (const Vector3<T> &b) const + { + Vector3<T> result; + result.x = M[0][0]*b.x + M[0][1]*b.y + M[0][2]*b.z; + result.y = M[1][0]*b.x + M[1][1]*b.y + M[1][2]*b.z; + result.z = M[2][0]*b.x + M[2][1]*b.y + M[2][2]*b.z; + + return result; + } + + Matrix3 operator/ (T s) const + { + Matrix3 result(*this); + result /= s; + return result; + } + + Matrix3& operator/= (T s) + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + M[i][j] /= s; + return *this; + } + + Vector3<T> Transform(const Vector3<T>& v) const + { + return Vector3<T>(M[0][0] * v.x + M[0][1] * v.y + M[0][2] * v.z, + M[1][0] * v.x + M[1][1] * v.y + M[1][2] * v.z, + M[2][0] * v.x + M[2][1] * v.y + M[2][2] * v.z); + } + + Matrix3 Transposed() const + { + return Matrix3(M[0][0], M[1][0], M[2][0], + M[0][1], M[1][1], M[2][1], + M[0][2], M[1][2], M[2][2]); + } + + void Transpose() + { + *this = Transposed(); + } + + + T SubDet (const UPInt* rows, const UPInt* cols) const + { + return M[rows[0]][cols[0]] * (M[rows[1]][cols[1]] * M[rows[2]][cols[2]] - M[rows[1]][cols[2]] * M[rows[2]][cols[1]]) + - M[rows[0]][cols[1]] * (M[rows[1]][cols[0]] * M[rows[2]][cols[2]] - M[rows[1]][cols[2]] * M[rows[2]][cols[0]]) + + M[rows[0]][cols[2]] * (M[rows[1]][cols[0]] * M[rows[2]][cols[1]] - M[rows[1]][cols[1]] * M[rows[2]][cols[0]]); + } + + // M += a*b.t() + inline void Rank1Add(const Vector3<T> &a, const Vector3<T> &b) + { + M[0][0] += a.x*b.x; M[0][1] += a.x*b.y; M[0][2] += a.x*b.z; + M[1][0] += a.y*b.x; M[1][1] += a.y*b.y; M[1][2] += a.y*b.z; + M[2][0] += a.z*b.x; M[2][1] += a.z*b.y; M[2][2] += a.z*b.z; + } + + // M -= a*b.t() + inline void Rank1Sub(const Vector3<T> &a, const Vector3<T> &b) + { + M[0][0] -= a.x*b.x; M[0][1] -= a.x*b.y; M[0][2] -= a.x*b.z; + M[1][0] -= a.y*b.x; M[1][1] -= a.y*b.y; M[1][2] -= a.y*b.z; + M[2][0] -= a.z*b.x; M[2][1] -= a.z*b.y; M[2][2] -= a.z*b.z; + } + + inline Vector3<T> Col(int c) const + { + return Vector3<T>(M[0][c], M[1][c], M[2][c]); + } + + inline Vector3<T> Row(int r) const + { + return Vector3<T>(M[r][0], M[r][1], M[r][2]); + } + + inline T Determinant() const + { + const Matrix3<T>& m = *this; + T d; + + d = m.M[0][0] * (m.M[1][1]*m.M[2][2] - m.M[1][2] * m.M[2][1]); + d -= m.M[0][1] * (m.M[1][0]*m.M[2][2] - m.M[1][2] * m.M[2][0]); + d += m.M[0][2] * (m.M[1][0]*m.M[2][1] - m.M[1][1] * m.M[2][0]); + + return d; + } + + inline Matrix3<T> Inverse() const + { + Matrix3<T> a; + const Matrix3<T>& m = *this; + T d = Determinant(); + + assert(d != 0); + T s = T(1)/d; + + a.M[0][0] = s * (m.M[1][1] * m.M[2][2] - m.M[1][2] * m.M[2][1]); + a.M[1][0] = s * (m.M[1][2] * m.M[2][0] - m.M[1][0] * m.M[2][2]); + a.M[2][0] = s * (m.M[1][0] * m.M[2][1] - m.M[1][1] * m.M[2][0]); + + a.M[0][1] = s * (m.M[0][2] * m.M[2][1] - m.M[0][1] * m.M[2][2]); + a.M[1][1] = s * (m.M[0][0] * m.M[2][2] - m.M[0][2] * m.M[2][0]); + a.M[2][1] = s * (m.M[0][1] * m.M[2][0] - m.M[0][0] * m.M[2][1]); + + a.M[0][2] = s * (m.M[0][1] * m.M[1][2] - m.M[0][2] * m.M[1][1]); + a.M[1][2] = s * (m.M[0][2] * m.M[1][0] - m.M[0][0] * m.M[1][2]); + a.M[2][2] = s * (m.M[0][0] * m.M[1][1] - m.M[0][1] * m.M[1][0]); + + return a; + } + +}; + +typedef Matrix3<float> Matrix3f; +typedef Matrix3<double> Matrix3d; + +//------------------------------------------------------------------------------------- + +template<typename T> +class SymMat3 +{ +private: + typedef SymMat3<T> this_type; + +public: + typedef T Value_t; + // Upper symmetric + T v[6]; // _00 _01 _02 _11 _12 _22 + + inline SymMat3() {} + + inline explicit SymMat3(T s) + { + v[0] = v[3] = v[5] = s; + v[1] = v[2] = v[4] = 0; + } + + inline explicit SymMat3(T a00, T a01, T a02, T a11, T a12, T a22) + { + v[0] = a00; v[1] = a01; v[2] = a02; + v[3] = a11; v[4] = a12; + v[5] = a22; + } + + static inline int Index(unsigned int i, unsigned int j) + { + return (i <= j) ? (3*i - i*(i+1)/2 + j) : (3*j - j*(j+1)/2 + i); + } + + inline T operator()(int i, int j) const { return v[Index(i,j)]; } + + inline T &operator()(int i, int j) { return v[Index(i,j)]; } + + template<typename U> + inline SymMat3<U> CastTo() const + { + return SymMat3<U>(static_cast<U>(v[0]), static_cast<U>(v[1]), static_cast<U>(v[2]), + static_cast<U>(v[3]), static_cast<U>(v[4]), static_cast<U>(v[5])); + } + + inline this_type& operator+=(const this_type& b) + { + v[0]+=b.v[0]; + v[1]+=b.v[1]; + v[2]+=b.v[2]; + v[3]+=b.v[3]; + v[4]+=b.v[4]; + v[5]+=b.v[5]; + return *this; + } + + inline this_type& operator-=(const this_type& b) + { + v[0]-=b.v[0]; + v[1]-=b.v[1]; + v[2]-=b.v[2]; + v[3]-=b.v[3]; + v[4]-=b.v[4]; + v[5]-=b.v[5]; + + return *this; + } + + inline this_type& operator*=(T s) + { + v[0]*=s; + v[1]*=s; + v[2]*=s; + v[3]*=s; + v[4]*=s; + v[5]*=s; + + return *this; + } + + inline SymMat3 operator*(T s) const + { + SymMat3 d; + d.v[0] = v[0]*s; + d.v[1] = v[1]*s; + d.v[2] = v[2]*s; + d.v[3] = v[3]*s; + d.v[4] = v[4]*s; + d.v[5] = v[5]*s; + + return d; + } + + // Multiplies two matrices into destination with minimum copying. + static SymMat3& Multiply(SymMat3* d, const SymMat3& a, const SymMat3& b) + { + // _00 _01 _02 _11 _12 _22 + + d->v[0] = a.v[0] * b.v[0]; + d->v[1] = a.v[0] * b.v[1] + a.v[1] * b.v[3]; + d->v[2] = a.v[0] * b.v[2] + a.v[1] * b.v[4]; + + d->v[3] = a.v[3] * b.v[3]; + d->v[4] = a.v[3] * b.v[4] + a.v[4] * b.v[5]; + + d->v[5] = a.v[5] * b.v[5]; + + return *d; + } + + inline T Determinant() const + { + const this_type& m = *this; + T d; + + d = m(0,0) * (m(1,1)*m(2,2) - m(1,2) * m(2,1)); + d -= m(0,1) * (m(1,0)*m(2,2) - m(1,2) * m(2,0)); + d += m(0,2) * (m(1,0)*m(2,1) - m(1,1) * m(2,0)); + + return d; + } + + inline this_type Inverse() const + { + this_type a; + const this_type& m = *this; + T d = Determinant(); + + assert(d != 0); + T s = T(1)/d; + + a(0,0) = s * (m(1,1) * m(2,2) - m(1,2) * m(2,1)); + + a(0,1) = s * (m(0,2) * m(2,1) - m(0,1) * m(2,2)); + a(1,1) = s * (m(0,0) * m(2,2) - m(0,2) * m(2,0)); + + a(0,2) = s * (m(0,1) * m(1,2) - m(0,2) * m(1,1)); + a(1,2) = s * (m(0,2) * m(1,0) - m(0,0) * m(1,2)); + a(2,2) = s * (m(0,0) * m(1,1) - m(0,1) * m(1,0)); + + return a; + } + + inline T Trace() const { return v[0] + v[3] + v[5]; } + + // M = a*a.t() + inline void Rank1(const Vector3<T> &a) + { + v[0] = a.x*a.x; v[1] = a.x*a.y; v[2] = a.x*a.z; + v[3] = a.y*a.y; v[4] = a.y*a.z; + v[5] = a.z*a.z; + } + + // M += a*a.t() + inline void Rank1Add(const Vector3<T> &a) + { + v[0] += a.x*a.x; v[1] += a.x*a.y; v[2] += a.x*a.z; + v[3] += a.y*a.y; v[4] += a.y*a.z; + v[5] += a.z*a.z; + } + + // M -= a*a.t() + inline void Rank1Sub(const Vector3<T> &a) + { + v[0] -= a.x*a.x; v[1] -= a.x*a.y; v[2] -= a.x*a.z; + v[3] -= a.y*a.y; v[4] -= a.y*a.z; + v[5] -= a.z*a.z; + } +}; + +typedef SymMat3<float> SymMat3f; +typedef SymMat3<double> SymMat3d; + +template<typename T> +inline Matrix3<T> operator*(const SymMat3<T>& a, const SymMat3<T>& b) +{ + #define AJB_ARBC(r,c) (a(r,0)*b(0,c)+a(r,1)*b(1,c)+a(r,2)*b(2,c)) + return Matrix3<T>( + AJB_ARBC(0,0), AJB_ARBC(0,1), AJB_ARBC(0,2), + AJB_ARBC(1,0), AJB_ARBC(1,1), AJB_ARBC(1,2), + AJB_ARBC(2,0), AJB_ARBC(2,1), AJB_ARBC(2,2)); + #undef AJB_ARBC +} + +template<typename T> +inline Matrix3<T> operator*(const Matrix3<T>& a, const SymMat3<T>& b) +{ + #define AJB_ARBC(r,c) (a(r,0)*b(0,c)+a(r,1)*b(1,c)+a(r,2)*b(2,c)) + return Matrix3<T>( + AJB_ARBC(0,0), AJB_ARBC(0,1), AJB_ARBC(0,2), + AJB_ARBC(1,0), AJB_ARBC(1,1), AJB_ARBC(1,2), + AJB_ARBC(2,0), AJB_ARBC(2,1), AJB_ARBC(2,2)); + #undef AJB_ARBC +} + +//------------------------------------------------------------------------------------- +// ***** Angle + +// Cleanly representing the algebra of 2D rotations. +// The operations maintain the angle between -Pi and Pi, the same range as atan2. + +template<class T> +class Angle +{ +public: + enum AngularUnits + { + Radians = 0, + Degrees = 1 + }; + + Angle() : a(0) {} + + // Fix the range to be between -Pi and Pi + Angle(T a_, AngularUnits u = Radians) : a((u == Radians) ? a_ : a_*Math<T>::DegreeToRadFactor) { FixRange(); } + + T Get(AngularUnits u = Radians) const { return (u == Radians) ? a : a*Math<T>::RadToDegreeFactor; } + void Set(const T& x, AngularUnits u = Radians) { a = (u == Radians) ? x : x*Math<T>::DegreeToRadFactor; FixRange(); } + int Sign() const { if (a == 0) return 0; else return (a > 0) ? 1 : -1; } + T Abs() const { return (a > 0) ? a : -a; } + + bool operator== (const Angle& b) const { return a == b.a; } + bool operator!= (const Angle& b) const { return a != b.a; } +// bool operator< (const Angle& b) const { return a < a.b; } +// bool operator> (const Angle& b) const { return a > a.b; } +// bool operator<= (const Angle& b) const { return a <= a.b; } +// bool operator>= (const Angle& b) const { return a >= a.b; } +// bool operator= (const T& x) { a = x; FixRange(); } + + // These operations assume a is already between -Pi and Pi. + Angle& operator+= (const Angle& b) { a = a + b.a; FastFixRange(); return *this; } + Angle& operator+= (const T& x) { a = a + x; FixRange(); return *this; } + Angle operator+ (const Angle& b) const { Angle res = *this; res += b; return res; } + Angle operator+ (const T& x) const { Angle res = *this; res += x; return res; } + Angle& operator-= (const Angle& b) { a = a - b.a; FastFixRange(); return *this; } + Angle& operator-= (const T& x) { a = a - x; FixRange(); return *this; } + Angle operator- (const Angle& b) const { Angle res = *this; res -= b; return res; } + Angle operator- (const T& x) const { Angle res = *this; res -= x; return res; } + + T Distance(const Angle& b) { T c = fabs(a - b.a); return (c <= Math<T>::Pi) ? c : Math<T>::TwoPi - c; } + +private: + + // The stored angle, which should be maintained between -Pi and Pi + T a; + + // Fixes the angle range to [-Pi,Pi], but assumes no more than 2Pi away on either side + inline void FastFixRange() + { + if (a < -Math<T>::Pi) + a += Math<T>::TwoPi; + else if (a > Math<T>::Pi) + a -= Math<T>::TwoPi; + } + + // Fixes the angle range to [-Pi,Pi] for any given range, but slower then the fast method + inline void FixRange() + { + // do nothing if the value is already in the correct range, since fmod call is expensive + if (a >= -Math<T>::Pi && a <= Math<T>::Pi) + return; + a = fmod(a,Math<T>::TwoPi); + if (a < -Math<T>::Pi) + a += Math<T>::TwoPi; + else if (a > Math<T>::Pi) + a -= Math<T>::TwoPi; + } +}; + + +typedef Angle<float> Anglef; +typedef Angle<double> Angled; + + +//------------------------------------------------------------------------------------- +// ***** Plane + +// Consists of a normal vector and distance from the origin where the plane is located. + +template<class T> +class Plane : public RefCountBase<Plane<T> > +{ +public: + Vector3<T> N; + T D; + + Plane() : D(0) {} + + // Normals must already be normalized + Plane(const Vector3<T>& n, T d) : N(n), D(d) {} + Plane(T x, T y, T z, T d) : N(x,y,z), D(d) {} + + // construct from a point on the plane and the normal + Plane(const Vector3<T>& p, const Vector3<T>& n) : N(n), D(-(p * n)) {} + + // Find the point to plane distance. The sign indicates what side of the plane the point is on (0 = point on plane). + T TestSide(const Vector3<T>& p) const + { + return (N.Dot(p)) + D; + } + + Plane<T> Flipped() const + { + return Plane(-N, -D); + } + + void Flip() + { + N = -N; + D = -D; + } + + bool operator==(const Plane<T>& rhs) const + { + return (this->D == rhs.D && this->N == rhs.N); + } +}; + +typedef Plane<float> Planef; + +} // Namespace OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_RefCount.cpp b/LibOVR/Src/Kernel/OVR_RefCount.cpp new file mode 100644 index 0000000..c6301ed --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_RefCount.cpp @@ -0,0 +1,111 @@ +/************************************************************************************ + +Filename : OVR_RefCount.cpp +Content : Reference counting implementation +Created : September 19, 2012 +Notes : + +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 "OVR_RefCount.h" +#include "OVR_Atomic.h" +#include "OVR_Log.h" + +namespace OVR { + +#ifdef OVR_CC_ARM +void* ReturnArg0(void* p) +{ + return p; +} +#endif + +// ***** Reference Count Base implementation + +RefCountImplCore::~RefCountImplCore() +{ + // RefCount can be either 1 or 0 here. + // 0 if Release() was properly called. + // 1 if the object was declared on stack or as an aggregate. + OVR_ASSERT(RefCount <= 1); +} + +#ifdef OVR_BUILD_DEBUG +void RefCountImplCore::reportInvalidDelete(void *pmem) +{ + OVR_DEBUG_LOG( + ("Invalid delete call on ref-counted object at %p. Please use Release()", pmem)); + OVR_ASSERT(0); +} +#endif + +RefCountNTSImplCore::~RefCountNTSImplCore() +{ + // RefCount can be either 1 or 0 here. + // 0 if Release() was properly called. + // 1 if the object was declared on stack or as an aggregate. + OVR_ASSERT(RefCount <= 1); +} + +#ifdef OVR_BUILD_DEBUG +void RefCountNTSImplCore::reportInvalidDelete(void *pmem) +{ + OVR_DEBUG_LOG( + ("Invalid delete call on ref-counted object at %p. Please use Release()", pmem)); + OVR_ASSERT(0); +} +#endif + + +// *** Thread-Safe RefCountImpl + +void RefCountImpl::AddRef() +{ + AtomicOps<int>::ExchangeAdd_NoSync(&RefCount, 1); +} +void RefCountImpl::Release() +{ + if ((AtomicOps<int>::ExchangeAdd_NoSync(&RefCount, -1) - 1) == 0) + delete this; +} + +// *** Thread-Safe RefCountVImpl w/virtual AddRef/Release + +void RefCountVImpl::AddRef() +{ + AtomicOps<int>::ExchangeAdd_NoSync(&RefCount, 1); +} +void RefCountVImpl::Release() +{ + if ((AtomicOps<int>::ExchangeAdd_NoSync(&RefCount, -1) - 1) == 0) + delete this; +} + +// *** NON-Thread-Safe RefCountImpl + +void RefCountNTSImpl::Release() const +{ + RefCount--; + if (RefCount == 0) + delete this; +} + + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_RefCount.h b/LibOVR/Src/Kernel/OVR_RefCount.h new file mode 100644 index 0000000..775e24c --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_RefCount.h @@ -0,0 +1,564 @@ +/************************************************************************************ + +PublicHeader: Kernel +Filename : OVR_RefCount.h +Content : Reference counting implementation headers +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_RefCount_h +#define OVR_RefCount_h + +#include "OVR_Types.h" +#include "OVR_Allocator.h" + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Reference Counting + +// There are three types of reference counting base classes: +// +// RefCountBase - Provides thread-safe reference counting (Default). +// RefCountBaseNTS - Non Thread Safe version of reference counting. + + +// ***** Declared classes + +template<class C> +class RefCountBase; +template<class C> +class RefCountBaseNTS; + +class RefCountImpl; +class RefCountNTSImpl; + + +//----------------------------------------------------------------------------------- +// ***** Implementation For Reference Counting + +// RefCountImplCore holds RefCount value and defines a few utility +// functions shared by all implementations. + +class RefCountImplCore +{ +protected: + volatile int RefCount; + +public: + // RefCountImpl constructor always initializes RefCount to 1 by default. + OVR_FORCE_INLINE RefCountImplCore() : RefCount(1) { } + + // Need virtual destructor + // This: 1. Makes sure the right destructor's called. + // 2. Makes us have VTable, necessary if we are going to have format needed by InitNewMem() + virtual ~RefCountImplCore(); + + // Debug method only. + int GetRefCount() const { return RefCount; } + + // This logic is used to detect invalid 'delete' calls of reference counted + // objects. Direct delete calls are not allowed on them unless they come in + // internally from Release. +#ifdef OVR_BUILD_DEBUG + static void OVR_CDECL reportInvalidDelete(void *pmem); + inline static void checkInvalidDelete(RefCountImplCore *pmem) + { + if (pmem->RefCount != 0) + reportInvalidDelete(pmem); + } +#else + inline static void checkInvalidDelete(RefCountImplCore *) { } +#endif + + // Base class ref-count content should not be copied. + void operator = (const RefCountImplCore &) { } +}; + +class RefCountNTSImplCore +{ +protected: + mutable int RefCount; + +public: + // RefCountImpl constructor always initializes RefCount to 1 by default. + OVR_FORCE_INLINE RefCountNTSImplCore() : RefCount(1) { } + + // Need virtual destructor + // This: 1. Makes sure the right destructor's called. + // 2. Makes us have VTable, necessary if we are going to have format needed by InitNewMem() + virtual ~RefCountNTSImplCore(); + + // Debug method only. + int GetRefCount() const { return RefCount; } + + // This logic is used to detect invalid 'delete' calls of reference counted + // objects. Direct delete calls are not allowed on them unless they come in + // internally from Release. +#ifdef OVR_BUILD_DEBUG + static void OVR_CDECL reportInvalidDelete(void *pmem); + OVR_FORCE_INLINE static void checkInvalidDelete(RefCountNTSImplCore *pmem) + { + if (pmem->RefCount != 0) + reportInvalidDelete(pmem); + } +#else + OVR_FORCE_INLINE static void checkInvalidDelete(RefCountNTSImplCore *) { } +#endif + + // Base class ref-count content should not be copied. + void operator = (const RefCountNTSImplCore &) { } +}; + + + +// RefCountImpl provides Thread-Safe implementation of reference counting, so +// it should be used by default in most places. + +class RefCountImpl : public RefCountImplCore +{ +public: + // Thread-Safe Ref-Count Implementation. + void AddRef(); + void Release(); +}; + +// RefCountVImpl provides Thread-Safe implementation of reference counting, plus, +// virtual AddRef and Release. + +class RefCountVImpl : virtual public RefCountImplCore +{ +public: + // Thread-Safe Ref-Count Implementation. + virtual void AddRef(); + virtual void Release(); +}; + + +// RefCountImplNTS provides Non-Thread-Safe implementation of reference counting, +// which is slightly more efficient since it doesn't use atomics. + +class RefCountNTSImpl : public RefCountNTSImplCore +{ +public: + OVR_FORCE_INLINE void AddRef() const { RefCount++; } + void Release() const; +}; + + + +// RefCountBaseStatImpl<> is a common class that adds new/delete override with Stat tracking +// to the reference counting implementation. Base must be one of the RefCountImpl classes. + +template<class Base> +class RefCountBaseStatImpl : public Base +{ +public: + RefCountBaseStatImpl() { } + + // *** Override New and Delete + + // DOM-IGNORE-BEGIN + // Undef new temporarily if it is being redefined +#ifdef OVR_DEFINE_NEW +#undef new +#endif + +#ifdef OVR_BUILD_DEBUG + // Custom check used to detect incorrect calls of 'delete' on ref-counted objects. + #define OVR_REFCOUNTALLOC_CHECK_DELETE(class_name, p) \ + do {if (p) Base::checkInvalidDelete((class_name*)p); } while(0) +#else + #define OVR_REFCOUNTALLOC_CHECK_DELETE(class_name, p) +#endif + + // Redefine all new & delete operators. + OVR_MEMORY_REDEFINE_NEW_IMPL(Base, OVR_REFCOUNTALLOC_CHECK_DELETE) + +#undef OVR_REFCOUNTALLOC_CHECK_DELETE + +#ifdef OVR_DEFINE_NEW +#define new OVR_DEFINE_NEW +#endif + // OVR_BUILD_DEFINE_NEW + // DOM-IGNORE-END +}; + + +template<class Base> +class RefCountBaseStatVImpl : virtual public Base +{ +public: + RefCountBaseStatVImpl() { } + + // *** Override New and Delete + + // DOM-IGNORE-BEGIN + // Undef new temporarily if it is being redefined +#ifdef OVR_DEFINE_NEW +#undef new +#endif + +#define OVR_REFCOUNTALLOC_CHECK_DELETE(class_name, p) + + // Redefine all new & delete operators. + OVR_MEMORY_REDEFINE_NEW_IMPL(Base, OVR_REFCOUNTALLOC_CHECK_DELETE) + +#undef OVR_REFCOUNTALLOC_CHECK_DELETE + +#ifdef OVR_DEFINE_NEW +#define new OVR_DEFINE_NEW +#endif + // OVR_BUILD_DEFINE_NEW + // DOM-IGNORE-END +}; + + + +//----------------------------------------------------------------------------------- +// *** End user RefCountBase<> classes + + +// RefCountBase is a base class for classes that require thread-safe reference +// counting; it also overrides the new and delete operators to use MemoryHeap. +// +// Reference counted objects start out with RefCount value of 1. Further lifetime +// management is done through the AddRef() and Release() methods, typically +// hidden by Ptr<>. + +template<class C> +class RefCountBase : public RefCountBaseStatImpl<RefCountImpl> +{ +public: + // Constructor. + OVR_FORCE_INLINE RefCountBase() : RefCountBaseStatImpl<RefCountImpl>() { } +}; + +// RefCountBaseV is the same as RefCountBase but with virtual AddRef/Release + +template<class C> +class RefCountBaseV : virtual public RefCountBaseStatVImpl<RefCountVImpl> +{ +public: + // Constructor. + OVR_FORCE_INLINE RefCountBaseV() : RefCountBaseStatVImpl<RefCountVImpl>() { } +}; + + +// RefCountBaseNTS is a base class for classes that require Non-Thread-Safe reference +// counting; it also overrides the new and delete operators to use MemoryHeap. +// This class should only be used if all pointers to it are known to be assigned, +// destroyed and manipulated within one thread. +// +// Reference counted objects start out with RefCount value of 1. Further lifetime +// management is done through the AddRef() and Release() methods, typically +// hidden by Ptr<>. + +template<class C> +class RefCountBaseNTS : public RefCountBaseStatImpl<RefCountNTSImpl> +{ +public: + // Constructor. + OVR_FORCE_INLINE RefCountBaseNTS() : RefCountBaseStatImpl<RefCountNTSImpl>() { } +}; + +//----------------------------------------------------------------------------------- +// ***** Pickable template pointer +enum PickType { PickValue }; + +template <typename T> +class Pickable +{ +public: + Pickable() : pV(NULL) {} + explicit Pickable(T* p) : pV(p) {} + Pickable(T* p, PickType) : pV(p) + { + OVR_ASSERT(pV); + if (pV) + pV->AddRef(); + } + template <typename OT> + Pickable(const Pickable<OT>& other) : pV(other.GetPtr()) {} + +public: + Pickable& operator =(const Pickable& other) + { + OVR_ASSERT(pV == NULL); + pV = other.pV; + // Extra check. + //other.pV = NULL; + return *this; + } + +public: + T* GetPtr() const { return pV; } + T* operator->() const + { + return pV; + } + T& operator*() const + { + OVR_ASSERT(pV); + return *pV; + } + +private: + T* pV; +}; + +template <typename T> +OVR_FORCE_INLINE +Pickable<T> MakePickable(T* p) +{ + return Pickable<T>(p); +} + +//----------------------------------------------------------------------------------- +// ***** Ref-Counted template pointer + +// Automatically AddRefs and Releases interfaces + +void* ReturnArg0(void* p); + +template<class C> +class Ptr +{ +#ifdef OVR_CC_ARM + static C* ReturnArg(void* p) { return (C*)ReturnArg0(p); } +#endif + +protected: + C *pObject; + +public: + + // Constructors + OVR_FORCE_INLINE Ptr() : pObject(0) + { } +#ifdef OVR_CC_ARM + OVR_FORCE_INLINE Ptr(C &robj) : pObject(ReturnArg(&robj)) +#else + OVR_FORCE_INLINE Ptr(C &robj) : pObject(&robj) +#endif + { } + OVR_FORCE_INLINE Ptr(Pickable<C> v) : pObject(v.GetPtr()) + { + // No AddRef() on purpose. + } + OVR_FORCE_INLINE Ptr(Ptr<C>& other, PickType) : pObject(other.pObject) + { + other.pObject = NULL; + // No AddRef() on purpose. + } + OVR_FORCE_INLINE Ptr(C *pobj) + { + if (pobj) pobj->AddRef(); + pObject = pobj; + } + OVR_FORCE_INLINE Ptr(const Ptr<C> &src) + { + if (src.pObject) src.pObject->AddRef(); + pObject = src.pObject; + } + + template<class R> + OVR_FORCE_INLINE Ptr(Ptr<R> &src) + { + if (src) src->AddRef(); + pObject = src; + } + template<class R> + OVR_FORCE_INLINE Ptr(Pickable<R> v) : pObject(v.GetPtr()) + { + // No AddRef() on purpose. + } + + // Destructor + OVR_FORCE_INLINE ~Ptr() + { + if (pObject) pObject->Release(); + } + + // Compares + OVR_FORCE_INLINE bool operator == (const Ptr &other) const { return pObject == other.pObject; } + OVR_FORCE_INLINE bool operator != (const Ptr &other) const { return pObject != other.pObject; } + + OVR_FORCE_INLINE bool operator == (C *pother) const { return pObject == pother; } + OVR_FORCE_INLINE bool operator != (C *pother) const { return pObject != pother; } + + + OVR_FORCE_INLINE bool operator < (const Ptr &other) const { return pObject < other.pObject; } + + // Assignment + template<class R> + OVR_FORCE_INLINE const Ptr<C>& operator = (const Ptr<R> &src) + { + if (src) src->AddRef(); + if (pObject) pObject->Release(); + pObject = src; + return *this; + } + // Specialization + OVR_FORCE_INLINE const Ptr<C>& operator = (const Ptr<C> &src) + { + if (src) src->AddRef(); + if (pObject) pObject->Release(); + pObject = src; + return *this; + } + + OVR_FORCE_INLINE const Ptr<C>& operator = (C *psrc) + { + if (psrc) psrc->AddRef(); + if (pObject) pObject->Release(); + pObject = psrc; + return *this; + } + OVR_FORCE_INLINE const Ptr<C>& operator = (C &src) + { + if (pObject) pObject->Release(); + pObject = &src; + return *this; + } + OVR_FORCE_INLINE Ptr<C>& operator = (Pickable<C> src) + { + return Pick(src); + } + template<class R> + OVR_FORCE_INLINE Ptr<C>& operator = (Pickable<R> src) + { + return Pick(src); + } + + // Set Assignment + template<class R> + OVR_FORCE_INLINE Ptr<C>& SetPtr(const Ptr<R> &src) + { + if (src) src->AddRef(); + if (pObject) pObject->Release(); + pObject = src; + return *this; + } + // Specialization + OVR_FORCE_INLINE Ptr<C>& SetPtr(const Ptr<C> &src) + { + if (src) src->AddRef(); + if (pObject) pObject->Release(); + pObject = src; + return *this; + } + + OVR_FORCE_INLINE Ptr<C>& SetPtr(C *psrc) + { + if (psrc) psrc->AddRef(); + if (pObject) pObject->Release(); + pObject = psrc; + return *this; + } + OVR_FORCE_INLINE Ptr<C>& SetPtr(C &src) + { + if (pObject) pObject->Release(); + pObject = &src; + return *this; + } + OVR_FORCE_INLINE Ptr<C>& SetPtr(Pickable<C> src) + { + return Pick(src); + } + + // Nulls ref-counted pointer without decrement + OVR_FORCE_INLINE void NullWithoutRelease() + { + pObject = 0; + } + + // Clears the pointer to the object + OVR_FORCE_INLINE void Clear() + { + if (pObject) pObject->Release(); + pObject = 0; + } + + // Obtain pointer reference directly, for D3D interfaces + OVR_FORCE_INLINE C*& GetRawRef() { return pObject; } + + // Access Operators + OVR_FORCE_INLINE C* GetPtr() const { return pObject; } + OVR_FORCE_INLINE C& operator * () const { return *pObject; } + OVR_FORCE_INLINE C* operator -> () const { return pObject; } + // Conversion + OVR_FORCE_INLINE operator C* () const { return pObject; } + + // Pickers. + + // Pick a value. + OVR_FORCE_INLINE Ptr<C>& Pick(Ptr<C>& other) + { + if (&other != this) + { + if (pObject) pObject->Release(); + pObject = other.pObject; + other.pObject = 0; + } + + return *this; + } + + OVR_FORCE_INLINE Ptr<C>& Pick(Pickable<C> v) + { + if (v.GetPtr() != pObject) + { + if (pObject) pObject->Release(); + pObject = v.GetPtr(); + } + + return *this; + } + + template<class R> + OVR_FORCE_INLINE Ptr<C>& Pick(Pickable<R> v) + { + if (v.GetPtr() != pObject) + { + if (pObject) pObject->Release(); + pObject = v.GetPtr(); + } + + return *this; + } + + OVR_FORCE_INLINE Ptr<C>& Pick(C* p) + { + if (p != pObject) + { + if (pObject) pObject->Release(); + pObject = p; + } + + return *this; + } +}; + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Std.cpp b/LibOVR/Src/Kernel/OVR_Std.cpp new file mode 100644 index 0000000..6b5be18 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Std.cpp @@ -0,0 +1,1036 @@ +/************************************************************************************ + +Filename : OVR_Std.cpp +Content : Standard C function implementation +Created : September 19, 2012 +Notes : + +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 "OVR_Std.h" +#include "OVR_Alg.h" + +// localeconv() call in OVR_strtod() +#include <locale.h> + +namespace OVR { + +// Source for functions not available on all platforms is included here. + +// Case insensitive compare implemented in platform-specific way. +int OVR_CDECL OVR_stricmp(const char* a, const char* b) +{ +#if defined(OVR_OS_WIN32) + #if defined(OVR_CC_MSVC) && (OVR_CC_MSVC >= 1400) + return ::_stricmp(a, b); + #else + return ::stricmp(a, b); + #endif + +#else + return strcasecmp(a, b); +#endif +} + +int OVR_CDECL OVR_strnicmp(const char* a, const char* b, UPInt count) +{ +#if defined(OVR_OS_WIN32) +#if defined(OVR_CC_MSVC) && (OVR_CC_MSVC >= 1400) + return ::_strnicmp(a, b, count); +#else + return ::strnicmp(a, b, count); +#endif + +#else + return strncasecmp(a, b, count); +#endif +} + +wchar_t* OVR_CDECL OVR_wcscpy(wchar_t* dest, UPInt destsize, const wchar_t* src) +{ +#if defined(OVR_MSVC_SAFESTRING) + wcscpy_s(dest, destsize, src); + return dest; +#elif defined(OVR_OS_WIN32) + OVR_UNUSED(destsize); + wcscpy(dest, src); + return dest; +#else + UPInt l = OVR_wcslen(src) + 1; // incl term null + l = (l < destsize) ? l : destsize; + memcpy(dest, src, l * sizeof(wchar_t)); + return dest; +#endif +} + +wchar_t* OVR_CDECL OVR_wcsncpy(wchar_t* dest, UPInt destsize, const wchar_t* src, UPInt count) +{ +#if defined(OVR_MSVC_SAFESTRING) + wcsncpy_s(dest, destsize, src, count); + return dest; +#else + UPInt srclen = OVR_wcslen(src); + UPInt l = Alg::Min(srclen, count); + l = (l < destsize) ? l : destsize; + memcpy(dest, src, l * sizeof(wchar_t)); + if (count > srclen) + { + UPInt remLen = Alg::Min(destsize - l, (count - srclen)); + memset(&dest[l], 0, sizeof(wchar_t)*remLen); + } + else if (l < destsize) + dest[l] = 0; + return dest; +#endif +} + + +wchar_t* OVR_CDECL OVR_wcscat(wchar_t* dest, UPInt destsize, const wchar_t* src) +{ +#if defined(OVR_MSVC_SAFESTRING) + wcscat_s(dest, destsize, src); + return dest; +#elif defined(OVR_OS_WIN32) + OVR_UNUSED(destsize); + wcscat(dest, src); + return dest; +#else + UPInt dstlen = OVR_wcslen(dest); // do not incl term null + UPInt srclen = OVR_wcslen(src) + 1; // incl term null + UPInt copylen = (dstlen + srclen < destsize) ? srclen : destsize - dstlen; + memcpy(dest + dstlen, src, copylen * sizeof(wchar_t)); + return dest; +#endif +} + +UPInt OVR_CDECL OVR_wcslen(const wchar_t* str) +{ +#if defined(OVR_OS_WIN32) + return wcslen(str); +#else + UPInt i = 0; + while(str[i] != '\0') + ++i; + return i; +#endif +} + +int OVR_CDECL OVR_wcscmp(const wchar_t* a, const wchar_t* b) +{ +#if defined(OVR_OS_WIN32) || defined(OVR_OS_LINUX) + return wcscmp(a, b); +#else + // not supported, use custom implementation + const wchar_t *pa = a, *pb = b; + while (*pa && *pb) + { + wchar_t ca = *pa; + wchar_t cb = *pb; + if (ca < cb) + return -1; + else if (ca > cb) + return 1; + pa++; + pb++; + } + if (*pa) + return 1; + else if (*pb) + return -1; + else + return 0; +#endif +} + +int OVR_CDECL OVR_wcsicmp(const wchar_t* a, const wchar_t* b) +{ +#if defined(OVR_OS_WIN32) +#if defined(OVR_CC_MSVC) && (OVR_CC_MSVC >= 1400) + return ::_wcsicmp(a, b); +#else + return ::wcsicmp(a, b); +#endif +#elif defined(OVR_OS_MAC) || defined(__CYGWIN__) || defined(OVR_OS_ANDROID) || defined(OVR_OS_IPHONE) + // not supported, use custom implementation + const wchar_t *pa = a, *pb = b; + while (*pa && *pb) + { + wchar_t ca = OVR_towlower(*pa); + wchar_t cb = OVR_towlower(*pb); + if (ca < cb) + return -1; + else if (ca > cb) + return 1; + pa++; + pb++; + } + if (*pa) + return 1; + else if (*pb) + return -1; + else + return 0; +#else + return wcscasecmp(a, b); +#endif +} + +// This function is not inline because of dependency on <locale.h> +double OVR_CDECL OVR_strtod(const char* string, char** tailptr) +{ +#if !defined(OVR_OS_ANDROID) + const char s = *localeconv()->decimal_point; + + if (s != '.') + { + char buffer[347 + 1]; + + OVR_strcpy(buffer, sizeof(buffer), string); + + for (char* c = buffer; *c != '\0'; ++c) + { + if (*c == '.') + { + *c = s; + break; + } + } + + return strtod(buffer, tailptr); + } +#endif + + return strtod(string, tailptr); +} + + +#ifndef OVR_NO_WCTYPE + +//// Use this class to generate Unicode bitsets. For example: +//// +//// UnicodeBitSet bitSet; +//// for(unsigned i = 0; i < 65536; ++i) +//// { +//// if (iswalpha(i)) +//// bitSet.Set(i); +//// } +//// bitSet.Dump(); +//// +////--------------------------------------------------------------- +//class UnicodeBitSet +//{ +//public: +// UnicodeBitSet() +// { +// memset(Offsets, 0, sizeof(Offsets)); +// memset(Bits, 0, sizeof(Bits)); +// } +// +// void Set(unsigned bit) { Bits[bit >> 8][(bit >> 4) & 15] |= 1 << (bit & 15); } +// +// void Dump() +// { +// unsigned i, j; +// unsigned offsetCount = 0; +// for(i = 0; i < 256; ++i) +// { +// if (isNull(i)) Offsets[i] = 0; +// else +// if (isFull(i)) Offsets[i] = 1; +// else Offsets[i] = UInt16(offsetCount++ * 16 + 256); +// } +// for(i = 0; i < 16; ++i) +// { +// for(j = 0; j < 16; ++j) +// { +// printf("%5u,", Offsets[i*16+j]); +// } +// printf("\n"); +// } +// for(i = 0; i < 256; ++i) +// { +// if (Offsets[i] > 255) +// { +// for(j = 0; j < 16; j++) +// { +// printf("%5u,", Bits[i][j]); +// } +// printf("\n"); +// } +// } +// } +// +//private: +// bool isNull(unsigned n) const +// { +// const UInt16* p = Bits[n]; +// for(unsigned i = 0; i < 16; ++i) +// if (p[i] != 0) return false; +// return true; +// } +// +// bool isFull(unsigned n) const +// { +// const UInt16* p = Bits[n]; +// for(unsigned i = 0; i < 16; ++i) +// if (p[i] != 0xFFFF) return false; +// return true; +// } +// +// UInt16 Offsets[256]; +// UInt16 Bits[256][16]; +//}; + + +const UInt16 UnicodeAlnumBits[] = { + 256, 1, 272, 288, 304, 320, 336, 352, 0, 368, 384, 400, 416, 432, 448, 464, + 480, 496, 512, 528, 544, 1, 560, 576, 592, 0, 0, 0, 0, 0, 608, 624, + 640, 656, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 672, 688, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 704, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 720, + 1, 1, 1, 1, 736, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 752, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 768, 784, 1, 800, 816, 832, + 0, 0, 0, 1023,65534, 2047,65534, 2047, 0, 0, 0, 524,65535,65407,65535,65407, +65535,65535,65532, 15, 0,65535,65535,65535,65535,65535,16383,63999, 3, 0,16415, 0, + 0, 0, 0, 0, 32, 0, 0, 1024,55104,65535,65531,65535,32767,64767,65535, 15, +65535,65535,65535,65535,65535,65535,65535,65535,61443,65535,65535,65535, 6559,65535,65535, 831, + 0, 0, 0,65534,65535, 639,65534,65535, 255, 0, 0, 0, 0,65535, 2047, 7, + 0, 0,65534, 2047,65534, 63, 1023,65535,65535,65535,65535,65535,65535, 8175, 8702, 8191, + 0,65535, 8191,65535, 0, 0, 0, 0,65535,65535,65535, 1, 0, 0, 0, 0, +65518,65535,65535,58367, 8191,65281,65487, 0,40942,65529,65023,50117, 6559,45184,65487, 3, +34788,65529,65023,50029, 6535,24064,65472, 31,45038,65531,65023,58349, 7103, 1,65473, 0, +40942,65529,65023,58317, 6543,45248,65475, 0,51180,54845,50968,50111, 7623, 128,65408, 0, +57326,65533,65023,50159, 7647, 96,65475, 0,57324,65533,65023,50159, 7647,16480,65475, 0, +57324,65533,65023,50175, 7631, 128,65475, 0,65516,64639,65535,12283,32895,65375, 0, 12, +65534,65535,65535, 2047,32767, 1023, 0, 0, 9622,65264,60590,15359, 8223,13311, 0, 0, + 1, 0, 1023, 0,65279,65535, 2047,65534, 3843,65279,65535, 8191, 0, 0, 0, 0, +65535,65535,63227, 327, 1023, 1023, 0, 0, 0, 0,65535,65535, 63,65535,65535, 127, +65535,65535,65535,65535,65535,33791,65535,65535,65535,65535,65287,65535,65535,65535,65535, 1023, +65407,65535,65535,65535,15743,15743,65535,65535,15743,65535,32767,32573,32573,65407,32767,65535, +32767,32573,65535,65535,65407, 2047,65024, 3, 0, 0,65535,65535,65535,65535,65535, 31, +65534,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, +65535,65535,65535,65535,65535,65535,40959, 127,65534, 2047,65535,65535,65535,65535, 2047, 0, + 0, 0, 0, 0, 0, 0, 0, 0,65535,65535,65535,65535, 511, 0, 1023, 0, + 0, 1023,65535,65535,65527,65535,65535, 255,65535,65535, 1023, 0, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535, 4095,65535,65535,65535,65535,65535, 1023, +65535,16191,65535,65535,16191,43775,65535,16383,65535,65535,65535,24543, 8156, 4047, 8191, 8156, + 0, 0, 0, 0, 0, 0, 0,32768, 0, 0, 0, 0, 0, 0, 0, 0, +64644,15919,48464, 1019, 0, 0,65535,65535, 15, 0, 0, 0, 0, 0, 0, 0, + 192, 0, 1022, 1792,65534,65535,65535,65535,65535, 31,65534,65535,65535,65535,65535, 2047, +65504,65535, 8191,65534,65535,65535,65535,65535,32767, 0,65535, 255, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535, 8191, 0, 0, 0, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 15, 0, 0, 0, 0, 0, +65535,65535,16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 127,41208,65023,24447,65499,65535,65535,65535,65535,65535,65535, 3, 0,65528,65535,65535, +65535,65535,65535,16383, 0,65535,65535,65535,65535,65532,65535,65535, 255, 0, 0, 4095, + 0, 0, 0, 0, 0, 0, 0,65495,65535,65535,65535,65535,65535,65535,65535, 8191, + 0, 1023,65534, 2047,65534, 2047,65472,65534,65535,16383,65535,32767,64764, 7420, 0, 0}; + +const UInt16 UnicodeAlphaBits[] = { + 256, 1, 272, 288, 304, 320, 336, 352, 0, 368, 384, 400, 416, 432, 448, 464, + 480, 496, 512, 528, 544, 1, 560, 576, 592, 0, 0, 0, 0, 0, 608, 624, + 640, 656, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 672, 688, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 704, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 720, + 1, 1, 1, 1, 736, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 752, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 768, 784, 1, 800, 816, 832, + 0, 0, 0, 0,65534, 2047,65534, 2047, 0, 0, 0, 0,65535,65407,65535,65407, +65535,65535,65532, 15, 0,65535,65535,65535,65535,65535,16383,63999, 3, 0,16415, 0, + 0, 0, 0, 0, 32, 0, 0, 1024,55104,65535,65531,65535,32767,64767,65535, 15, +65535,65535,65535,65535,65535,65535,65535,65535,61443,65535,65535,65535, 6559,65535,65535, 831, + 0, 0, 0,65534,65535, 639,65534,65535, 255, 0, 0, 0, 0,65535, 2047, 7, + 0, 0,65534, 2047,65534, 63, 0,65535,65535,65535,65535,65535,65535, 8175, 8702, 7168, + 0,65535, 8191,65535, 0, 0, 0, 0,65535,65535,65535, 1, 0, 0, 0, 0, +65518,65535,65535,58367, 8191,65281, 15, 0,40942,65529,65023,50117, 6559,45184, 15, 3, +34788,65529,65023,50029, 6535,24064, 0, 31,45038,65531,65023,58349, 7103, 1, 1, 0, +40942,65529,65023,58317, 6543,45248, 3, 0,51180,54845,50968,50111, 7623, 128, 0, 0, +57326,65533,65023,50159, 7647, 96, 3, 0,57324,65533,65023,50159, 7647,16480, 3, 0, +57324,65533,65023,50175, 7631, 128, 3, 0,65516,64639,65535,12283,32895,65375, 0, 12, +65534,65535,65535, 2047,32767, 0, 0, 0, 9622,65264,60590,15359, 8223,12288, 0, 0, + 1, 0, 0, 0,65279,65535, 2047,65534, 3843,65279,65535, 8191, 0, 0, 0, 0, +65535,65535,63227, 327, 0, 1023, 0, 0, 0, 0,65535,65535, 63,65535,65535, 127, +65535,65535,65535,65535,65535,33791,65535,65535,65535,65535,65287,65535,65535,65535,65535, 1023, +65407,65535,65535,65535,15743,15743,65535,65535,15743,65535,32767,32573,32573,65407,32767,65535, +32767,32573,65535,65535,65407, 2047, 0, 0, 0, 0,65535,65535,65535,65535,65535, 31, +65534,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, +65535,65535,65535,65535,65535,65535,40959, 127,65534, 2047,65535,65535,65535,65535, 2047, 0, + 0, 0, 0, 0, 0, 0, 0, 0,65535,65535,65535,65535, 511, 0, 0, 0, + 0, 0,65535,65535,65527,65535,65535, 255,65535,65535, 1023, 0, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535, 4095,65535,65535,65535,65535,65535, 1023, +65535,16191,65535,65535,16191,43775,65535,16383,65535,65535,65535,24543, 8156, 4047, 8191, 8156, + 0, 0, 0, 0, 0, 0, 0,32768, 0, 0, 0, 0, 0, 0, 0, 0, +64644,15919,48464, 1019, 0, 0,65535,65535, 15, 0, 0, 0, 0, 0, 0, 0, + 192, 0, 1022, 1792,65534,65535,65535,65535,65535, 31,65534,65535,65535,65535,65535, 2047, +65504,65535, 8191,65534,65535,65535,65535,65535,32767, 0,65535, 255, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535, 8191, 0, 0, 0, 0, 0, 0, 0, +65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 15, 0, 0, 0, 0, 0, +65535,65535,16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 127,41208,65023,24447,65499,65535,65535,65535,65535,65535,65535, 3, 0,65528,65535,65535, +65535,65535,65535,16383, 0,65535,65535,65535,65535,65532,65535,65535, 255, 0, 0, 4095, + 0, 0, 0, 0, 0, 0, 0,65495,65535,65535,65535,65535,65535,65535,65535, 8191, + 0, 0,65534, 2047,65534, 2047,65472,65534,65535,16383,65535,32767,64764, 7420, 0, 0}; + +const UInt16 UnicodeDigitBits[] = { + 256, 0, 0, 0, 0, 0, 272, 0, 0, 288, 304, 320, 336, 352, 368, 384, + 400, 0, 0, 416, 0, 0, 0, 432, 448, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 464, + 0, 0, 0, 1023, 0, 0, 0, 0, 0, 0, 0, 524, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 1023, + 0, 0, 0, 0, 0, 0,65472, 0, 0, 0, 0, 0, 0, 0,65472, 0, + 0, 0, 0, 0, 0, 0,65472, 0, 0, 0, 0, 0, 0, 0,65472, 0, + 0, 0, 0, 0, 0, 0,65472, 0, 0, 0, 0, 0, 0, 0,65408, 0, + 0, 0, 0, 0, 0, 0,65472, 0, 0, 0, 0, 0, 0, 0,65472, 0, + 0, 0, 0, 0, 0, 0,65472, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1023, 0, 0, 0, 0, 0, 0, 0, 1023, 0, 0, + 0, 0, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,65024, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1023, 0, + 0, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +const UInt16 UnicodeSpaceBits[] = { + 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 288, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 304, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +15872, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 4095, 0,33536, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +const UInt16 UnicodeXDigitBits[] = { + 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, + 0, 0, 0, 1023, 126, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1023, 126, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +// Uncomment if necessary +//const UInt16 UnicodeCntrlBits[] = { +// 256, 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 288, 0, 0, 0, 0, 0, 0, 0, +// 304, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 320, 336, +//65535,65535, 0, 0, 0, 0, 0,32768,65535,65535, 0, 0, 0, 0, 0, 0, +//32768, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//30720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//61440, 0,31744, 0, 0, 0,64512, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,32768, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3584}; +// +//const UInt16 UnicodeGraphBits[] = { +// 256, 1, 272, 288, 304, 320, 336, 352, 0, 368, 384, 400, 416, 432, 448, 464, +// 480, 496, 512, 528, 544, 1, 560, 576, 592, 0, 0, 0, 0, 0, 608, 624, +// 640, 656, 0, 672, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 688, 704, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 720, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 736, +// 1, 1, 1, 1, 752, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 768, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 784, 800, 1, 816, 832, 848, +// 0, 0,65534,65535,65535,65535,65535,32767, 0, 0,65534,65535,65535,65535,65535,65535, +//65535,65535,65532, 15, 0,65535,65535,65535,65535,65535,16383,63999, 3, 0,16415, 0, +// 0, 0, 0, 0, 32, 0, 0,17408,55232,65535,65531,65535,32767,64767,65535, 15, +//65535,65535,65535,65535,65535,65535,65535,65535,61443,65535,65535,65535, 6559,65535,65535, 831, +// 0, 0, 0,65534,65535,65151,65534,65535, 1791, 0, 0,16384, 9,65535, 2047, 31, +// 4096,34816,65534, 2047,65534, 63,16383,65535,65535,65535,65535,65535,65535, 8191, 8702, 8191, +//16383,65535, 8191,65535, 0, 0, 0, 0,65535,65535,65535, 1, 0, 0, 0, 0, +//65518,65535,65535,58367, 8191,65281,65535, 1,40942,65529,65023,50117, 6559,45184,65487, 3, +//34788,65529,65023,50029, 6535,24064,65472, 31,45038,65531,65023,58349, 7103, 1,65473, 0, +//40942,65529,65023,58317, 6543,45248,65475, 0,51180,54845,50968,50111, 7623, 128,65408, 0, +//57326,65533,65023,50159, 7647, 96,65475, 0,57324,65533,65023,50159, 7647,16480,65475, 0, +//57324,65533,65023,50175, 7631, 128,65475, 0,65516,64639,65535,12283,32895,65375, 0, 28, +//65534,65535,65535, 2047,65535, 4095, 0, 0, 9622,65264,60590,15359, 8223,13311, 0, 0, +//65521, 7, 1023,15360,65279,65535, 2047,65534, 3875,65279,65535, 8191, 0, 0, 0, 0, +//65535,65535,63227, 327,65535, 1023, 0, 0, 0, 0,65535,65535, 63,65535,65535, 2175, +//65535,65535,65535,65535,65535,33791,65535,65535,65535,65535,65287,65535,65535,65535,65535, 1023, +//65407,65535,65535,65535,15743,15743,65535,65535,15743,65535,32767,32573,32573,65407,32767,65535, +//32767,32573,65535,65535,65407, 2047,65534, 3, 0, 0,65535,65535,65535,65535,65535, 31, +//65534,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, +//65535,65535,65535,65535,65535,65535,65535, 127,65534, 8191,65535,65535,65535,65535,16383, 0, +// 0, 0, 0, 0, 0, 0, 0, 0,65535,65535,65535,65535, 511, 6128, 1023, 0, +// 2047, 1023,65535,65535,65527,65535,65535, 255,65535,65535, 1023, 0, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535, 4095,65535,65535,65535,65535,65535, 1023, +//65535,16191,65535,65535,16191,43775,65535,16383,65535,65535,65535,24543, 8156, 4047, 8191, 8156, +// 0,65535, 255,65535,16239, 0, 0,57344,24576, 0, 0, 0, 0, 0, 0, 0, +//64644,15919,48464, 1019, 0, 0,65535,65535, 15, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 1536, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//65486,65523, 1022, 1793,65534,65535,65535,65535,65535, 31,65534,65535,65535,65535,65535, 4095, +//65504,65535, 8191,65534,65535,65535,65535,65535,32767, 0,65535, 255, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535, 8191, 0, 0, 0, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 15, 0, 0, 0, 0, 0, +//65535,65535,16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 127,41208,65023,24447,65499,65535,65535,65535,65535,65535,65535, 3, 0,65528,65535,65535, +//65535,65535,65535,65535, 0,65535,65535,65535,65535,65532,65535,65535, 255, 0, 0, 4095, +// 0, 0, 0,65535,65055,65527, 3339,65495,65535,65535,65535,65535,65535,65535,65535, 8191, +//63470,36863,65535,49151,65534,12287,65534,65534,65535,16383,65535,32767,64764, 7420, 0, 0}; +// +//const UInt16 UnicodePrintBits[] = { +// 256, 1, 272, 288, 304, 320, 336, 352, 0, 368, 384, 400, 416, 432, 448, 464, +// 480, 496, 512, 528, 544, 1, 560, 576, 592, 0, 0, 0, 0, 0, 608, 624, +// 640, 656, 0, 672, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 688, 704, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 720, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 736, +// 1, 1, 1, 1, 752, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +// 1, 1, 1, 1, 1, 1, 1, 768, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 784, 800, 1, 816, 832, 848, +// 512, 0,65535,65535,65535,65535,65535,32767, 0, 0,65535,65535,65535,65535,65535,65535, +//65535,65535,65532, 15, 0,65535,65535,65535,65535,65535,16383,63999, 3, 0,16415, 0, +// 0, 0, 0, 0, 32, 0, 0,17408,55232,65535,65531,65535,32767,64767,65535, 15, +//65535,65535,65535,65535,65535,65535,65535,65535,61443,65535,65535,65535, 6559,65535,65535, 831, +// 0, 0, 0,65534,65535,65151,65534,65535, 1791, 0, 0,16384, 9,65535, 2047, 31, +// 4096,34816,65534, 2047,65534, 63,16383,65535,65535,65535,65535,65535,65535, 8191, 8702, 8191, +//16383,65535, 8191,65535, 0, 0, 0, 0,65535,65535,65535, 1, 0, 0, 0, 0, +//65518,65535,65535,58367, 8191,65281,65535, 1,40942,65529,65023,50117, 6559,45184,65487, 3, +//34788,65529,65023,50029, 6535,24064,65472, 31,45038,65531,65023,58349, 7103, 1,65473, 0, +//40942,65529,65023,58317, 6543,45248,65475, 0,51180,54845,50968,50111, 7623, 128,65408, 0, +//57326,65533,65023,50159, 7647, 96,65475, 0,57324,65533,65023,50159, 7647,16480,65475, 0, +//57324,65533,65023,50175, 7631, 128,65475, 0,65516,64639,65535,12283,32895,65375, 0, 28, +//65534,65535,65535, 2047,65535, 4095, 0, 0, 9622,65264,60590,15359, 8223,13311, 0, 0, +//65521, 7, 1023,15360,65279,65535, 2047,65534, 3875,65279,65535, 8191, 0, 0, 0, 0, +//65535,65535,63227, 327,65535, 1023, 0, 0, 0, 0,65535,65535, 63,65535,65535, 2175, +//65535,65535,65535,65535,65535,33791,65535,65535,65535,65535,65287,65535,65535,65535,65535, 1023, +//65407,65535,65535,65535,15743,15743,65535,65535,15743,65535,32767,32573,32573,65407,32767,65535, +//32767,32573,65535,65535,65407, 2047,65534, 3, 0, 0,65535,65535,65535,65535,65535, 31, +//65534,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, +//65535,65535,65535,65535,65535,65535,65535, 127,65534, 8191,65535,65535,65535,65535,16383, 0, +// 0, 0, 0, 0, 0, 0, 0, 0,65535,65535,65535,65535, 511, 6128, 1023, 0, +// 2047, 1023,65535,65535,65527,65535,65535, 255,65535,65535, 1023, 0, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535, 4095,65535,65535,65535,65535,65535, 1023, +//65535,16191,65535,65535,16191,43775,65535,16383,65535,65535,65535,24543, 8156, 4047, 8191, 8156, +// 0,65535, 255,65535,16239, 0, 0,57344,24576, 0, 0, 0, 0, 0, 0, 0, +//64644,15919,48464, 1019, 0, 0,65535,65535, 15, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 1536, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//65487,65523, 1022, 1793,65534,65535,65535,65535,65535, 31,65534,65535,65535,65535,65535, 4095, +//65504,65535, 8191,65534,65535,65535,65535,65535,32767, 0,65535, 255, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 63, 0, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535, 8191, 0, 0, 0, 0, 0, 0, 0, +//65535,65535,65535,65535,65535,65535,65535,65535,65535,65535, 15, 0, 0, 0, 0, 0, +//65535,65535,16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 127,41208,65023,24447,65499,65535,65535,65535,65535,65535,65535, 3, 0,65528,65535,65535, +//65535,65535,65535,65535, 0,65535,65535,65535,65535,65532,65535,65535, 255, 0, 0, 4095, +// 0, 0, 0,65535,65055,65527, 3339,65495,65535,65535,65535,65535,65535,65535,65535,40959, +//63470,36863,65535,49151,65534,12287,65534,65534,65535,16383,65535,32767,64764, 7420, 0, 0}; +// +//const UInt16 UnicodePunctBits[] = { +// 256, 0, 0, 272, 0, 288, 304, 320, 0, 336, 0, 0, 0, 352, 368, 384, +// 400, 0, 0, 416, 0, 0, 432, 448, 464, 0, 0, 0, 0, 0, 0, 0, +// 480, 0, 0, 496, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 528, 544, 560, +// 0, 0,65534,64512, 1,63488, 1,30720, 0, 0,65534,65535, 0, 128, 0, 128, +// 0, 0, 0, 0, 0, 0, 0,16384, 128, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0,64512, 0, 0, 1536, 0, 0,16384, 9, 0, 0, 24, +// 4096,34816, 0, 0, 0, 0,15360, 0, 0, 0, 0, 0, 0, 16, 0, 0, +//16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, +// 0, 0, 0, 0,32768, 3072, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//65520, 7, 0,15360, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0,64512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2048, +// 0, 0, 0, 0, 0, 0, 510, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0,24576, 0, 0, 6144, 0, 0, 0, 0,14336, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6128, 0, 0, +// 2047, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0,65535, 255,65535,16239, 0, 0,24576,24576, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 1536, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//65294,65523, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2048, +// 0, 0, 0,49152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0,65535,65055,65527, 3339, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//63470,35840, 1,47104, 0,10240, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +// +//const UInt16 UnicodeLowerBits[] = { +// 256, 272, 288, 304, 320, 336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 352, 368, +// 384, 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 416, 0, 0, 0, 432, +// 0, 0, 0, 0, 0, 0,65534, 2047, 0, 0, 0, 0, 0,32768,65535,65407, +//43690,43690,43690,21930,43861,43690,43690,54442,12585,20004,11562,58961,23392,46421,43690,43565, +//43690,43690,43688, 10, 0,65535,65535,65535,65535,65535,16383, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,61440,65535,32767,43235,43690, 15, +// 0, 0, 0,65535,65535,65535,43690,43690,40962,43690,43690,43690, 4372,43690,43690, 554, +// 0, 0, 0, 0, 0, 0,65534,65535, 255, 0, 0, 0, 0, 0, 0, 0, +//43690,43690,43690,43690,43690,43690,43690,43690,43690, 4074,43690,43690,43690,43690,43690, 682, +// 255, 63, 255, 255, 63, 255, 255,16383,65535,65535,65535,20703, 4316, 207, 255, 4316, +// 0, 0, 0, 0, 0, 0, 0,32768, 0, 0, 0, 0, 0, 0, 0, 0, +//50176, 8,32768, 528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 127, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +// +//const UInt16 UnicodeUpperBits[] = { +// 256, 272, 288, 304, 320, 336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 352, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368, 384, +// 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 416, +// 0, 0, 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0,65535,32639, 0, 0, +//21845,21845,21845,43605,21674,21845,21845,11093,52950,45531,53973, 4526,44464,19114,21845,21974, +//21845,21845,21844, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0,55104,65534, 4091, 0, 0,21532,21845, 0, +//65535,65535,65535, 0, 0, 0,21845,21845,20481,21845,21845,21845, 2187,21845,21845, 277, +// 0, 0, 0,65534,65535, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,65535,65535, 63, 0, 0, 0, +//21845,21845,21845,21845,21845,21845,21845,21845,21845, 21,21845,21845,21845,21845,21845, 341, +//65280,16128,65280,65280,16128,43520,65280, 0,65280,65280,65280, 7936, 7936, 3840, 7936, 7936, +//14468,15911,15696, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + +// MA: March 19, 2010 +// Modified ToUpper and ToLower tables to match values expected by AS3 tests. +// ToLower modifications: +// 304 -> 105 +// 1024 -> 1104 * +// 1037 -> 1117 * +// UoUpper modifications: +// 255 -> 376 +// 305 -> 73 +// 383 -> 83 +// 1104 -> 1024 * +// 1117 -> 1037 * +// Entries marked with a '*' don't make complete sense based on Unicode manual, although +// they match AS3. + + +static const UInt16 UnicodeToUpperBits[] = { + 256, 272, 288, 304, 320, 336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 352, 368, + 0, 384, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 416, + 0, 0, 0, 0, 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0,65535,65407, +43690,43690,43690,21674,43349,43690,43690,54442, 4392, 516, 8490, 8785,21056,46421,43690,43048, // MA: Modified for AS3. +43690, 170, 0, 0, 0, 2776,33545, 36, 3336, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,61440,65534,32767, 0,43688, 0, + 0, 0, 0,65535,65535,65535,43690,43690, 2,43690,43690,43690, 4372,43690,35498, 554, // MA: Modified for AS3. + 0, 0, 0, 0, 0, 0,65534,65535, 127, 0, 0, 0, 0, 0, 0, 0, +43690,43690,43690,43690,43690,43690,43690,43690,43690, 42,43690,43690,43690,43690,43690, 682, + 255, 63, 255, 255, 63, 170, 255,16383, 0, 0, 0, 3, 0, 3, 35, 0, + 0, 0, 0, 0, 0, 0, 0,65535, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,65535, 1023, 0, + 0, 0, 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static const UInt16 UnicodeToLowerBits[] = { + 256, 272, 288, 304, 320, 336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 352, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368, 384, + 0, 400, 0, 0, 416, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 432, + 0, 0, 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0,65535,32639, 0, 0, +21845,21845,21845,43605,21674,21845,21845,11093,52950,45531,53909, 4526,42128,19114,21845,21522,// MA: Modidied for AS3. +21845, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,55104,65534, 4091, 0, 0, 0,21844, 0, +65535,65535,65535, 0, 0, 0,21845,21845, 1,21845,21845,21845, 2186,21845,17749, 277, + 0, 0, 0,65534,65535, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,65535,65535, 63, 0, 0, 0, +21845,21845,21845,21845,21845,21845,21845,21845,21845, 21,21845,21845,21845,21845,21845, 341, +65280,16128,65280,65280,16128,43520,65280, 0, 0, 0, 0, 3840, 3840, 3840, 7936, 3840, + 0, 0, 0, 0, 0, 0,65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,65472,65535, 0, 0, 0, + 0, 0,65534, 2047, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +struct GUnicodePairType +{ + UInt16 Key, Value; +}; + +static inline bool CmpUnicodeKey(const GUnicodePairType& a, UInt16 key) +{ + return a.Key < key; +} + +static const GUnicodePairType UnicodeToUpperTable[] = { +{ 97, 65}, { 98, 66}, { 99, 67}, { 100, 68}, { 101, 69}, { 102, 70}, { 103, 71}, +{ 104, 72}, { 105, 73}, { 106, 74}, { 107, 75}, { 108, 76}, { 109, 77}, { 110, 78}, +{ 111, 79}, { 112, 80}, { 113, 81}, { 114, 82}, { 115, 83}, { 116, 84}, { 117, 85}, +{ 118, 86}, { 119, 87}, { 120, 88}, { 121, 89}, { 122, 90}, { 224, 192}, { 225, 193}, +{ 226, 194}, { 227, 195}, { 228, 196}, { 229, 197}, { 230, 198}, { 231, 199}, { 232, 200}, +{ 233, 201}, { 234, 202}, { 235, 203}, { 236, 204}, { 237, 205}, { 238, 206}, { 239, 207}, +{ 240, 208}, { 241, 209}, { 242, 210}, { 243, 211}, { 244, 212}, { 245, 213}, { 246, 214}, +{ 248, 216}, { 249, 217}, { 250, 218}, { 251, 219}, { 252, 220}, { 253, 221}, { 254, 222}, +{ 255, 376}, { 257, 256}, { 259, 258}, { 261, 260}, { 263, 262}, { 265, 264}, { 267, 266}, +{ 269, 268}, { 271, 270}, { 273, 272}, { 275, 274}, { 277, 276}, { 279, 278}, { 281, 280}, +{ 283, 282}, { 285, 284}, { 287, 286}, { 289, 288}, { 291, 290}, { 293, 292}, { 295, 294}, +{ 297, 296}, { 299, 298}, { 301, 300}, { 303, 302}, { 305, 73}, { 307, 306}, { 309, 308}, { 311, 310}, +{ 314, 313}, { 316, 315}, { 318, 317}, { 320, 319}, { 322, 321}, { 324, 323}, { 326, 325}, +{ 328, 327}, { 331, 330}, { 333, 332}, { 335, 334}, { 337, 336}, { 339, 338}, { 341, 340}, +{ 343, 342}, { 345, 344}, { 347, 346}, { 349, 348}, { 351, 350}, { 353, 352}, { 355, 354}, +{ 357, 356}, { 359, 358}, { 361, 360}, { 363, 362}, { 365, 364}, { 367, 366}, { 369, 368}, +{ 371, 370}, { 373, 372}, { 375, 374}, { 378, 377}, { 380, 379}, { 382, 381}, { 383, 83}, { 387, 386}, +{ 389, 388}, { 392, 391}, { 396, 395}, { 402, 401}, { 409, 408}, { 417, 416}, { 419, 418}, +{ 421, 420}, { 424, 423}, { 429, 428}, { 432, 431}, { 436, 435}, { 438, 437}, { 441, 440}, +{ 445, 444}, { 454, 452}, { 457, 455}, { 460, 458}, { 462, 461}, { 464, 463}, { 466, 465}, +{ 468, 467}, { 470, 469}, { 472, 471}, { 474, 473}, { 476, 475}, { 477, 398}, { 479, 478}, +{ 481, 480}, { 483, 482}, { 485, 484}, { 487, 486}, { 489, 488}, { 491, 490}, { 493, 492}, +{ 495, 494}, { 499, 497}, { 501, 500}, { 507, 506}, { 509, 508}, { 511, 510}, { 513, 512}, +{ 515, 514}, { 517, 516}, { 519, 518}, { 521, 520}, { 523, 522}, { 525, 524}, { 527, 526}, +{ 529, 528}, { 531, 530}, { 533, 532}, { 535, 534}, { 595, 385}, { 596, 390}, { 598, 393}, +{ 599, 394}, { 601, 399}, { 603, 400}, { 608, 403}, { 611, 404}, { 616, 407}, { 617, 406}, +{ 623, 412}, { 626, 413}, { 629, 415}, { 643, 425}, { 648, 430}, { 650, 433}, { 651, 434}, +{ 658, 439}, { 940, 902}, { 941, 904}, { 942, 905}, { 943, 906}, { 945, 913}, { 946, 914}, +{ 947, 915}, { 948, 916}, { 949, 917}, { 950, 918}, { 951, 919}, { 952, 920}, { 953, 921}, +{ 954, 922}, { 955, 923}, { 956, 924}, { 957, 925}, { 958, 926}, { 959, 927}, { 960, 928}, +{ 961, 929}, { 962, 931}, { 963, 931}, { 964, 932}, { 965, 933}, { 966, 934}, { 967, 935}, +{ 968, 936}, { 969, 937}, { 970, 938}, { 971, 939}, { 972, 908}, { 973, 910}, { 974, 911}, +{ 995, 994}, { 997, 996}, { 999, 998}, { 1001, 1000}, { 1003, 1002}, { 1005, 1004}, { 1007, 1006}, +{ 1072, 1040}, { 1073, 1041}, { 1074, 1042}, { 1075, 1043}, { 1076, 1044}, { 1077, 1045}, { 1078, 1046}, +{ 1079, 1047}, { 1080, 1048}, { 1081, 1049}, { 1082, 1050}, { 1083, 1051}, { 1084, 1052}, { 1085, 1053}, +{ 1086, 1054}, { 1087, 1055}, { 1088, 1056}, { 1089, 1057}, { 1090, 1058}, { 1091, 1059}, { 1092, 1060}, +{ 1093, 1061}, { 1094, 1062}, { 1095, 1063}, { 1096, 1064}, { 1097, 1065}, { 1098, 1066}, { 1099, 1067}, +{ 1100, 1068}, { 1101, 1069}, { 1102, 1070}, { 1103, 1071}, { 1104, 1024}, { 1105, 1025}, { 1106, 1026}, { 1107, 1027}, +{ 1108, 1028}, { 1109, 1029}, { 1110, 1030}, { 1111, 1031}, { 1112, 1032}, { 1113, 1033}, { 1114, 1034}, +{ 1115, 1035}, { 1116, 1036}, { 1117, 1037}, { 1118, 1038}, { 1119, 1039}, { 1121, 1120}, { 1123, 1122}, { 1125, 1124}, +{ 1127, 1126}, { 1129, 1128}, { 1131, 1130}, { 1133, 1132}, { 1135, 1134}, { 1137, 1136}, { 1139, 1138}, +{ 1141, 1140}, { 1143, 1142}, { 1145, 1144}, { 1147, 1146}, { 1149, 1148}, { 1151, 1150}, { 1153, 1152}, +{ 1169, 1168}, { 1171, 1170}, { 1173, 1172}, { 1175, 1174}, { 1177, 1176}, { 1179, 1178}, { 1181, 1180}, +{ 1183, 1182}, { 1185, 1184}, { 1187, 1186}, { 1189, 1188}, { 1191, 1190}, { 1193, 1192}, { 1195, 1194}, +{ 1197, 1196}, { 1199, 1198}, { 1201, 1200}, { 1203, 1202}, { 1205, 1204}, { 1207, 1206}, { 1209, 1208}, +{ 1211, 1210}, { 1213, 1212}, { 1215, 1214}, { 1218, 1217}, { 1220, 1219}, { 1224, 1223}, { 1228, 1227}, +{ 1233, 1232}, { 1235, 1234}, { 1237, 1236}, { 1239, 1238}, { 1241, 1240}, { 1243, 1242}, { 1245, 1244}, +{ 1247, 1246}, { 1249, 1248}, { 1251, 1250}, { 1253, 1252}, { 1255, 1254}, { 1257, 1256}, { 1259, 1258}, +{ 1263, 1262}, { 1265, 1264}, { 1267, 1266}, { 1269, 1268}, { 1273, 1272}, { 1377, 1329}, { 1378, 1330}, +{ 1379, 1331}, { 1380, 1332}, { 1381, 1333}, { 1382, 1334}, { 1383, 1335}, { 1384, 1336}, { 1385, 1337}, +{ 1386, 1338}, { 1387, 1339}, { 1388, 1340}, { 1389, 1341}, { 1390, 1342}, { 1391, 1343}, { 1392, 1344}, +{ 1393, 1345}, { 1394, 1346}, { 1395, 1347}, { 1396, 1348}, { 1397, 1349}, { 1398, 1350}, { 1399, 1351}, +{ 1400, 1352}, { 1401, 1353}, { 1402, 1354}, { 1403, 1355}, { 1404, 1356}, { 1405, 1357}, { 1406, 1358}, +{ 1407, 1359}, { 1408, 1360}, { 1409, 1361}, { 1410, 1362}, { 1411, 1363}, { 1412, 1364}, { 1413, 1365}, +{ 1414, 1366}, { 7681, 7680}, { 7683, 7682}, { 7685, 7684}, { 7687, 7686}, { 7689, 7688}, { 7691, 7690}, +{ 7693, 7692}, { 7695, 7694}, { 7697, 7696}, { 7699, 7698}, { 7701, 7700}, { 7703, 7702}, { 7705, 7704}, +{ 7707, 7706}, { 7709, 7708}, { 7711, 7710}, { 7713, 7712}, { 7715, 7714}, { 7717, 7716}, { 7719, 7718}, +{ 7721, 7720}, { 7723, 7722}, { 7725, 7724}, { 7727, 7726}, { 7729, 7728}, { 7731, 7730}, { 7733, 7732}, +{ 7735, 7734}, { 7737, 7736}, { 7739, 7738}, { 7741, 7740}, { 7743, 7742}, { 7745, 7744}, { 7747, 7746}, +{ 7749, 7748}, { 7751, 7750}, { 7753, 7752}, { 7755, 7754}, { 7757, 7756}, { 7759, 7758}, { 7761, 7760}, +{ 7763, 7762}, { 7765, 7764}, { 7767, 7766}, { 7769, 7768}, { 7771, 7770}, { 7773, 7772}, { 7775, 7774}, +{ 7777, 7776}, { 7779, 7778}, { 7781, 7780}, { 7783, 7782}, { 7785, 7784}, { 7787, 7786}, { 7789, 7788}, +{ 7791, 7790}, { 7793, 7792}, { 7795, 7794}, { 7797, 7796}, { 7799, 7798}, { 7801, 7800}, { 7803, 7802}, +{ 7805, 7804}, { 7807, 7806}, { 7809, 7808}, { 7811, 7810}, { 7813, 7812}, { 7815, 7814}, { 7817, 7816}, +{ 7819, 7818}, { 7821, 7820}, { 7823, 7822}, { 7825, 7824}, { 7827, 7826}, { 7829, 7828}, { 7841, 7840}, +{ 7843, 7842}, { 7845, 7844}, { 7847, 7846}, { 7849, 7848}, { 7851, 7850}, { 7853, 7852}, { 7855, 7854}, +{ 7857, 7856}, { 7859, 7858}, { 7861, 7860}, { 7863, 7862}, { 7865, 7864}, { 7867, 7866}, { 7869, 7868}, +{ 7871, 7870}, { 7873, 7872}, { 7875, 7874}, { 7877, 7876}, { 7879, 7878}, { 7881, 7880}, { 7883, 7882}, +{ 7885, 7884}, { 7887, 7886}, { 7889, 7888}, { 7891, 7890}, { 7893, 7892}, { 7895, 7894}, { 7897, 7896}, +{ 7899, 7898}, { 7901, 7900}, { 7903, 7902}, { 7905, 7904}, { 7907, 7906}, { 7909, 7908}, { 7911, 7910}, +{ 7913, 7912}, { 7915, 7914}, { 7917, 7916}, { 7919, 7918}, { 7921, 7920}, { 7923, 7922}, { 7925, 7924}, +{ 7927, 7926}, { 7929, 7928}, { 7936, 7944}, { 7937, 7945}, { 7938, 7946}, { 7939, 7947}, { 7940, 7948}, +{ 7941, 7949}, { 7942, 7950}, { 7943, 7951}, { 7952, 7960}, { 7953, 7961}, { 7954, 7962}, { 7955, 7963}, +{ 7956, 7964}, { 7957, 7965}, { 7968, 7976}, { 7969, 7977}, { 7970, 7978}, { 7971, 7979}, { 7972, 7980}, +{ 7973, 7981}, { 7974, 7982}, { 7975, 7983}, { 7984, 7992}, { 7985, 7993}, { 7986, 7994}, { 7987, 7995}, +{ 7988, 7996}, { 7989, 7997}, { 7990, 7998}, { 7991, 7999}, { 8000, 8008}, { 8001, 8009}, { 8002, 8010}, +{ 8003, 8011}, { 8004, 8012}, { 8005, 8013}, { 8017, 8025}, { 8019, 8027}, { 8021, 8029}, { 8023, 8031}, +{ 8032, 8040}, { 8033, 8041}, { 8034, 8042}, { 8035, 8043}, { 8036, 8044}, { 8037, 8045}, { 8038, 8046}, +{ 8039, 8047}, { 8048, 8122}, { 8049, 8123}, { 8050, 8136}, { 8051, 8137}, { 8052, 8138}, { 8053, 8139}, +{ 8054, 8154}, { 8055, 8155}, { 8056, 8184}, { 8057, 8185}, { 8058, 8170}, { 8059, 8171}, { 8060, 8186}, +{ 8061, 8187}, { 8112, 8120}, { 8113, 8121}, { 8144, 8152}, { 8145, 8153}, { 8160, 8168}, { 8161, 8169}, +{ 8165, 8172}, { 8560, 8544}, { 8561, 8545}, { 8562, 8546}, { 8563, 8547}, { 8564, 8548}, { 8565, 8549}, +{ 8566, 8550}, { 8567, 8551}, { 8568, 8552}, { 8569, 8553}, { 8570, 8554}, { 8571, 8555}, { 8572, 8556}, +{ 8573, 8557}, { 8574, 8558}, { 8575, 8559}, { 9424, 9398}, { 9425, 9399}, { 9426, 9400}, { 9427, 9401}, +{ 9428, 9402}, { 9429, 9403}, { 9430, 9404}, { 9431, 9405}, { 9432, 9406}, { 9433, 9407}, { 9434, 9408}, +{ 9435, 9409}, { 9436, 9410}, { 9437, 9411}, { 9438, 9412}, { 9439, 9413}, { 9440, 9414}, { 9441, 9415}, +{ 9442, 9416}, { 9443, 9417}, { 9444, 9418}, { 9445, 9419}, { 9446, 9420}, { 9447, 9421}, { 9448, 9422}, +{ 9449, 9423}, {65345,65313}, {65346,65314}, {65347,65315}, {65348,65316}, {65349,65317}, {65350,65318}, +{65351,65319}, {65352,65320}, {65353,65321}, {65354,65322}, {65355,65323}, {65356,65324}, {65357,65325}, +{65358,65326}, {65359,65327}, {65360,65328}, {65361,65329}, {65362,65330}, {65363,65331}, {65364,65332}, +{65365,65333}, {65366,65334}, {65367,65335}, {65368,65336}, {65369,65337}, {65370,65338}, {65535, 0}}; + +static const GUnicodePairType UnicodeToLowerTable[] = { +{ 65, 97}, { 66, 98}, { 67, 99}, { 68, 100}, { 69, 101}, { 70, 102}, { 71, 103}, +{ 72, 104}, { 73, 105}, { 74, 106}, { 75, 107}, { 76, 108}, { 77, 109}, { 78, 110}, +{ 79, 111}, { 80, 112}, { 81, 113}, { 82, 114}, { 83, 115}, { 84, 116}, { 85, 117}, +{ 86, 118}, { 87, 119}, { 88, 120}, { 89, 121}, { 90, 122}, { 192, 224}, { 193, 225}, +{ 194, 226}, { 195, 227}, { 196, 228}, { 197, 229}, { 198, 230}, { 199, 231}, { 200, 232}, +{ 201, 233}, { 202, 234}, { 203, 235}, { 204, 236}, { 205, 237}, { 206, 238}, { 207, 239}, +{ 208, 240}, { 209, 241}, { 210, 242}, { 211, 243}, { 212, 244}, { 213, 245}, { 214, 246}, +{ 216, 248}, { 217, 249}, { 218, 250}, { 219, 251}, { 220, 252}, { 221, 253}, { 222, 254}, +{ 256, 257}, { 258, 259}, { 260, 261}, { 262, 263}, { 264, 265}, { 266, 267}, { 268, 269}, +{ 270, 271}, { 272, 273}, { 274, 275}, { 276, 277}, { 278, 279}, { 280, 281}, { 282, 283}, +{ 284, 285}, { 286, 287}, { 288, 289}, { 290, 291}, { 292, 293}, { 294, 295}, { 296, 297}, +{ 298, 299}, { 300, 301}, { 302, 303}, { 304, 105}, { 306, 307}, { 308, 309}, { 310, 311}, { 313, 314}, +{ 315, 316}, { 317, 318}, { 319, 320}, { 321, 322}, { 323, 324}, { 325, 326}, { 327, 328}, +{ 330, 331}, { 332, 333}, { 334, 335}, { 336, 337}, { 338, 339}, { 340, 341}, { 342, 343}, +{ 344, 345}, { 346, 347}, { 348, 349}, { 350, 351}, { 352, 353}, { 354, 355}, { 356, 357}, +{ 358, 359}, { 360, 361}, { 362, 363}, { 364, 365}, { 366, 367}, { 368, 369}, { 370, 371}, +{ 372, 373}, { 374, 375}, { 376, 255}, { 377, 378}, { 379, 380}, { 381, 382}, { 385, 595}, +{ 386, 387}, { 388, 389}, { 390, 596}, { 391, 392}, { 393, 598}, { 394, 599}, { 395, 396}, +{ 398, 477}, { 399, 601}, { 400, 603}, { 401, 402}, { 403, 608}, { 404, 611}, { 406, 617}, +{ 407, 616}, { 408, 409}, { 412, 623}, { 413, 626}, { 415, 629}, { 416, 417}, { 418, 419}, +{ 420, 421}, { 423, 424}, { 425, 643}, { 428, 429}, { 430, 648}, { 431, 432}, { 433, 650}, +{ 434, 651}, { 435, 436}, { 437, 438}, { 439, 658}, { 440, 441}, { 444, 445}, { 452, 454}, +{ 455, 457}, { 458, 460}, { 461, 462}, { 463, 464}, { 465, 466}, { 467, 468}, { 469, 470}, +{ 471, 472}, { 473, 474}, { 475, 476}, { 478, 479}, { 480, 481}, { 482, 483}, { 484, 485}, +{ 486, 487}, { 488, 489}, { 490, 491}, { 492, 493}, { 494, 495}, { 497, 499}, { 500, 501}, +{ 506, 507}, { 508, 509}, { 510, 511}, { 512, 513}, { 514, 515}, { 516, 517}, { 518, 519}, +{ 520, 521}, { 522, 523}, { 524, 525}, { 526, 527}, { 528, 529}, { 530, 531}, { 532, 533}, +{ 534, 535}, { 902, 940}, { 904, 941}, { 905, 942}, { 906, 943}, { 908, 972}, { 910, 973}, +{ 911, 974}, { 913, 945}, { 914, 946}, { 915, 947}, { 916, 948}, { 917, 949}, { 918, 950}, +{ 919, 951}, { 920, 952}, { 921, 953}, { 922, 954}, { 923, 955}, { 924, 956}, { 925, 957}, +{ 926, 958}, { 927, 959}, { 928, 960}, { 929, 961}, { 931, 963}, { 932, 964}, { 933, 965}, +{ 934, 966}, { 935, 967}, { 936, 968}, { 937, 969}, { 938, 970}, { 939, 971}, { 994, 995}, +{ 996, 997}, { 998, 999}, { 1000, 1001}, { 1002, 1003}, { 1004, 1005}, { 1006, 1007}, { 1024, 1104}, { 1025, 1105}, +{ 1026, 1106}, { 1027, 1107}, { 1028, 1108}, { 1029, 1109}, { 1030, 1110}, { 1031, 1111}, { 1032, 1112}, +{ 1033, 1113}, { 1034, 1114}, { 1035, 1115}, { 1036, 1116}, { 1037, 1117}, { 1038, 1118}, { 1039, 1119}, { 1040, 1072}, +{ 1041, 1073}, { 1042, 1074}, { 1043, 1075}, { 1044, 1076}, { 1045, 1077}, { 1046, 1078}, { 1047, 1079}, +{ 1048, 1080}, { 1049, 1081}, { 1050, 1082}, { 1051, 1083}, { 1052, 1084}, { 1053, 1085}, { 1054, 1086}, +{ 1055, 1087}, { 1056, 1088}, { 1057, 1089}, { 1058, 1090}, { 1059, 1091}, { 1060, 1092}, { 1061, 1093}, +{ 1062, 1094}, { 1063, 1095}, { 1064, 1096}, { 1065, 1097}, { 1066, 1098}, { 1067, 1099}, { 1068, 1100}, +{ 1069, 1101}, { 1070, 1102}, { 1071, 1103}, { 1120, 1121}, { 1122, 1123}, { 1124, 1125}, { 1126, 1127}, +{ 1128, 1129}, { 1130, 1131}, { 1132, 1133}, { 1134, 1135}, { 1136, 1137}, { 1138, 1139}, { 1140, 1141}, +{ 1142, 1143}, { 1144, 1145}, { 1146, 1147}, { 1148, 1149}, { 1150, 1151}, { 1152, 1153}, { 1168, 1169}, +{ 1170, 1171}, { 1172, 1173}, { 1174, 1175}, { 1176, 1177}, { 1178, 1179}, { 1180, 1181}, { 1182, 1183}, +{ 1184, 1185}, { 1186, 1187}, { 1188, 1189}, { 1190, 1191}, { 1192, 1193}, { 1194, 1195}, { 1196, 1197}, +{ 1198, 1199}, { 1200, 1201}, { 1202, 1203}, { 1204, 1205}, { 1206, 1207}, { 1208, 1209}, { 1210, 1211}, +{ 1212, 1213}, { 1214, 1215}, { 1217, 1218}, { 1219, 1220}, { 1223, 1224}, { 1227, 1228}, { 1232, 1233}, +{ 1234, 1235}, { 1236, 1237}, { 1238, 1239}, { 1240, 1241}, { 1242, 1243}, { 1244, 1245}, { 1246, 1247}, +{ 1248, 1249}, { 1250, 1251}, { 1252, 1253}, { 1254, 1255}, { 1256, 1257}, { 1258, 1259}, { 1262, 1263}, +{ 1264, 1265}, { 1266, 1267}, { 1268, 1269}, { 1272, 1273}, { 1329, 1377}, { 1330, 1378}, { 1331, 1379}, +{ 1332, 1380}, { 1333, 1381}, { 1334, 1382}, { 1335, 1383}, { 1336, 1384}, { 1337, 1385}, { 1338, 1386}, +{ 1339, 1387}, { 1340, 1388}, { 1341, 1389}, { 1342, 1390}, { 1343, 1391}, { 1344, 1392}, { 1345, 1393}, +{ 1346, 1394}, { 1347, 1395}, { 1348, 1396}, { 1349, 1397}, { 1350, 1398}, { 1351, 1399}, { 1352, 1400}, +{ 1353, 1401}, { 1354, 1402}, { 1355, 1403}, { 1356, 1404}, { 1357, 1405}, { 1358, 1406}, { 1359, 1407}, +{ 1360, 1408}, { 1361, 1409}, { 1362, 1410}, { 1363, 1411}, { 1364, 1412}, { 1365, 1413}, { 1366, 1414}, +{ 4256, 4304}, { 4257, 4305}, { 4258, 4306}, { 4259, 4307}, { 4260, 4308}, { 4261, 4309}, { 4262, 4310}, +{ 4263, 4311}, { 4264, 4312}, { 4265, 4313}, { 4266, 4314}, { 4267, 4315}, { 4268, 4316}, { 4269, 4317}, +{ 4270, 4318}, { 4271, 4319}, { 4272, 4320}, { 4273, 4321}, { 4274, 4322}, { 4275, 4323}, { 4276, 4324}, +{ 4277, 4325}, { 4278, 4326}, { 4279, 4327}, { 4280, 4328}, { 4281, 4329}, { 4282, 4330}, { 4283, 4331}, +{ 4284, 4332}, { 4285, 4333}, { 4286, 4334}, { 4287, 4335}, { 4288, 4336}, { 4289, 4337}, { 4290, 4338}, +{ 4291, 4339}, { 4292, 4340}, { 4293, 4341}, { 7680, 7681}, { 7682, 7683}, { 7684, 7685}, { 7686, 7687}, +{ 7688, 7689}, { 7690, 7691}, { 7692, 7693}, { 7694, 7695}, { 7696, 7697}, { 7698, 7699}, { 7700, 7701}, +{ 7702, 7703}, { 7704, 7705}, { 7706, 7707}, { 7708, 7709}, { 7710, 7711}, { 7712, 7713}, { 7714, 7715}, +{ 7716, 7717}, { 7718, 7719}, { 7720, 7721}, { 7722, 7723}, { 7724, 7725}, { 7726, 7727}, { 7728, 7729}, +{ 7730, 7731}, { 7732, 7733}, { 7734, 7735}, { 7736, 7737}, { 7738, 7739}, { 7740, 7741}, { 7742, 7743}, +{ 7744, 7745}, { 7746, 7747}, { 7748, 7749}, { 7750, 7751}, { 7752, 7753}, { 7754, 7755}, { 7756, 7757}, +{ 7758, 7759}, { 7760, 7761}, { 7762, 7763}, { 7764, 7765}, { 7766, 7767}, { 7768, 7769}, { 7770, 7771}, +{ 7772, 7773}, { 7774, 7775}, { 7776, 7777}, { 7778, 7779}, { 7780, 7781}, { 7782, 7783}, { 7784, 7785}, +{ 7786, 7787}, { 7788, 7789}, { 7790, 7791}, { 7792, 7793}, { 7794, 7795}, { 7796, 7797}, { 7798, 7799}, +{ 7800, 7801}, { 7802, 7803}, { 7804, 7805}, { 7806, 7807}, { 7808, 7809}, { 7810, 7811}, { 7812, 7813}, +{ 7814, 7815}, { 7816, 7817}, { 7818, 7819}, { 7820, 7821}, { 7822, 7823}, { 7824, 7825}, { 7826, 7827}, +{ 7828, 7829}, { 7840, 7841}, { 7842, 7843}, { 7844, 7845}, { 7846, 7847}, { 7848, 7849}, { 7850, 7851}, +{ 7852, 7853}, { 7854, 7855}, { 7856, 7857}, { 7858, 7859}, { 7860, 7861}, { 7862, 7863}, { 7864, 7865}, +{ 7866, 7867}, { 7868, 7869}, { 7870, 7871}, { 7872, 7873}, { 7874, 7875}, { 7876, 7877}, { 7878, 7879}, +{ 7880, 7881}, { 7882, 7883}, { 7884, 7885}, { 7886, 7887}, { 7888, 7889}, { 7890, 7891}, { 7892, 7893}, +{ 7894, 7895}, { 7896, 7897}, { 7898, 7899}, { 7900, 7901}, { 7902, 7903}, { 7904, 7905}, { 7906, 7907}, +{ 7908, 7909}, { 7910, 7911}, { 7912, 7913}, { 7914, 7915}, { 7916, 7917}, { 7918, 7919}, { 7920, 7921}, +{ 7922, 7923}, { 7924, 7925}, { 7926, 7927}, { 7928, 7929}, { 7944, 7936}, { 7945, 7937}, { 7946, 7938}, +{ 7947, 7939}, { 7948, 7940}, { 7949, 7941}, { 7950, 7942}, { 7951, 7943}, { 7960, 7952}, { 7961, 7953}, +{ 7962, 7954}, { 7963, 7955}, { 7964, 7956}, { 7965, 7957}, { 7976, 7968}, { 7977, 7969}, { 7978, 7970}, +{ 7979, 7971}, { 7980, 7972}, { 7981, 7973}, { 7982, 7974}, { 7983, 7975}, { 7992, 7984}, { 7993, 7985}, +{ 7994, 7986}, { 7995, 7987}, { 7996, 7988}, { 7997, 7989}, { 7998, 7990}, { 7999, 7991}, { 8008, 8000}, +{ 8009, 8001}, { 8010, 8002}, { 8011, 8003}, { 8012, 8004}, { 8013, 8005}, { 8025, 8017}, { 8027, 8019}, +{ 8029, 8021}, { 8031, 8023}, { 8040, 8032}, { 8041, 8033}, { 8042, 8034}, { 8043, 8035}, { 8044, 8036}, +{ 8045, 8037}, { 8046, 8038}, { 8047, 8039}, { 8120, 8112}, { 8121, 8113}, { 8122, 8048}, { 8123, 8049}, +{ 8136, 8050}, { 8137, 8051}, { 8138, 8052}, { 8139, 8053}, { 8152, 8144}, { 8153, 8145}, { 8154, 8054}, +{ 8155, 8055}, { 8168, 8160}, { 8169, 8161}, { 8170, 8058}, { 8171, 8059}, { 8172, 8165}, { 8184, 8056}, +{ 8185, 8057}, { 8186, 8060}, { 8187, 8061}, { 8544, 8560}, { 8545, 8561}, { 8546, 8562}, { 8547, 8563}, +{ 8548, 8564}, { 8549, 8565}, { 8550, 8566}, { 8551, 8567}, { 8552, 8568}, { 8553, 8569}, { 8554, 8570}, +{ 8555, 8571}, { 8556, 8572}, { 8557, 8573}, { 8558, 8574}, { 8559, 8575}, { 9398, 9424}, { 9399, 9425}, +{ 9400, 9426}, { 9401, 9427}, { 9402, 9428}, { 9403, 9429}, { 9404, 9430}, { 9405, 9431}, { 9406, 9432}, +{ 9407, 9433}, { 9408, 9434}, { 9409, 9435}, { 9410, 9436}, { 9411, 9437}, { 9412, 9438}, { 9413, 9439}, +{ 9414, 9440}, { 9415, 9441}, { 9416, 9442}, { 9417, 9443}, { 9418, 9444}, { 9419, 9445}, { 9420, 9446}, +{ 9421, 9447}, { 9422, 9448}, { 9423, 9449}, {65313,65345}, {65314,65346}, {65315,65347}, {65316,65348}, +{65317,65349}, {65318,65350}, {65319,65351}, {65320,65352}, {65321,65353}, {65322,65354}, {65323,65355}, +{65324,65356}, {65325,65357}, {65326,65358}, {65327,65359}, {65328,65360}, {65329,65361}, {65330,65362}, +{65331,65363}, {65332,65364}, {65333,65365}, {65334,65366}, {65335,65367}, {65336,65368}, {65337,65369}, +{65338,65370}, {65535, 0}}; + +int OVR_CDECL OVR_towupper(wchar_t charCode) +{ + // Don't use UnicodeUpperBits! It differs from UnicodeToUpperBits. + if (UnicodeCharIs(UnicodeToUpperBits, charCode)) + { + // To protect from memory overrun in case the character is not found + // we use one extra fake element in the table {65536, 0}. + UPInt idx = Alg::LowerBoundSliced( + UnicodeToUpperTable, + 0, + sizeof(UnicodeToUpperTable) / sizeof(UnicodeToUpperTable[0]) - 1, + (UInt16)charCode, + CmpUnicodeKey); + return UnicodeToUpperTable[idx].Value; + } + return charCode; +} + +int OVR_CDECL OVR_towlower(wchar_t charCode) +{ + // Don't use UnicodeLowerBits! It differs from UnicodeToLowerBits. + if (UnicodeCharIs(UnicodeToLowerBits, charCode)) + { + // To protect from memory overrun in case the character is not found + // we use one extra fake element in the table {65536, 0}. + UPInt idx = Alg::LowerBoundSliced( + UnicodeToLowerTable, + 0, + sizeof(UnicodeToLowerTable) / sizeof(UnicodeToLowerTable[0]) - 1, + (UInt16)charCode, + CmpUnicodeKey); + return UnicodeToLowerTable[idx].Value; + } + return charCode; +} + +#endif //OVR_NO_WCTYPE + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_Std.h b/LibOVR/Src/Kernel/OVR_Std.h new file mode 100644 index 0000000..c11f853 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Std.h @@ -0,0 +1,514 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Std.h +Content : Standard C function interface +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Std_h +#define OVR_Std_h + +#include "OVR_Types.h" +#include <stdarg.h> // for va_list args +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#if !defined(OVR_OS_WINCE) && defined(OVR_CC_MSVC) && (OVR_CC_MSVC >= 1400) +#define OVR_MSVC_SAFESTRING +#include <errno.h> +#endif + +// Wide-char funcs +#include <wchar.h> +#include <wctype.h> + +namespace OVR { + +#if defined(OVR_OS_WIN32) +inline char* OVR_CDECL OVR_itoa(int val, char *dest, UPInt destsize, int radix) +{ +#if defined(OVR_MSVC_SAFESTRING) + _itoa_s(val, dest, destsize, radix); + return dest; +#else + OVR_UNUSED(destsize); + return itoa(val, dest, radix); +#endif +} +#else // OVR_OS_WIN32 +inline char* OVR_itoa(int val, char* dest, unsigned int len, int radix) +{ + if (val == 0) + { + if (len > 1) + { + dest[0] = '0'; + dest[1] = '\0'; + } + return dest; + } + + int cur = val; + unsigned int i = 0; + unsigned int sign = 0; + + if (val < 0) + { + val = -val; + sign = 1; + } + + while ((val != 0) && (i < (len - 1 - sign))) + { + cur = val % radix; + val /= radix; + + if (radix == 16) + { + switch(cur) + { + case 10: + dest[i] = 'a'; + break; + case 11: + dest[i] = 'b'; + break; + case 12: + dest[i] = 'c'; + break; + case 13: + dest[i] = 'd'; + break; + case 14: + dest[i] = 'e'; + break; + case 15: + dest[i] = 'f'; + break; + default: + dest[i] = (char)('0' + cur); + break; + } + } + else + { + dest[i] = (char)('0' + cur); + } + ++i; + } + + if (sign) + { + dest[i++] = '-'; + } + + for (unsigned int j = 0; j < i / 2; ++j) + { + char tmp = dest[j]; + dest[j] = dest[i - 1 - j]; + dest[i - 1 - j] = tmp; + } + dest[i] = '\0'; + + return dest; +} + +#endif + + +// String functions + +inline UPInt OVR_CDECL OVR_strlen(const char* str) +{ + return strlen(str); +} + +inline char* OVR_CDECL OVR_strcpy(char* dest, UPInt destsize, const char* src) +{ +#if defined(OVR_MSVC_SAFESTRING) + strcpy_s(dest, destsize, src); + return dest; +#else + OVR_UNUSED(destsize); + return strcpy(dest, src); +#endif +} + +inline char* OVR_CDECL OVR_strncpy(char* dest, UPInt destsize, const char* src, UPInt count) +{ +#if defined(OVR_MSVC_SAFESTRING) + strncpy_s(dest, destsize, src, count); + return dest; +#else + OVR_UNUSED(destsize); + return strncpy(dest, src, count); +#endif +} + +inline char * OVR_CDECL OVR_strcat(char* dest, UPInt destsize, const char* src) +{ +#if defined(OVR_MSVC_SAFESTRING) + strcat_s(dest, destsize, src); + return dest; +#else + OVR_UNUSED(destsize); + return strcat(dest, src); +#endif +} + +inline int OVR_CDECL OVR_strcmp(const char* dest, const char* src) +{ + return strcmp(dest, src); +} + +inline const char* OVR_CDECL OVR_strchr(const char* str, char c) +{ + return strchr(str, c); +} + +inline char* OVR_CDECL OVR_strchr(char* str, char c) +{ + return strchr(str, c); +} + +inline const char* OVR_strrchr(const char* str, char c) +{ + UPInt len = OVR_strlen(str); + for (UPInt i=len; i>0; i--) + if (str[i]==c) + return str+i; + return 0; +} + +inline const UByte* OVR_CDECL OVR_memrchr(const UByte* str, UPInt size, UByte c) +{ + for (SPInt i = (SPInt)size - 1; i >= 0; i--) + { + if (str[i] == c) + return str + i; + } + return 0; +} + +inline char* OVR_CDECL OVR_strrchr(char* str, char c) +{ + UPInt len = OVR_strlen(str); + for (UPInt i=len; i>0; i--) + if (str[i]==c) + return str+i; + return 0; +} + + +double OVR_CDECL OVR_strtod(const char* string, char** tailptr); + +inline long OVR_CDECL OVR_strtol(const char* string, char** tailptr, int radix) +{ + return strtol(string, tailptr, radix); +} + +inline long OVR_CDECL OVR_strtoul(const char* string, char** tailptr, int radix) +{ + return strtoul(string, tailptr, radix); +} + +inline int OVR_CDECL OVR_strncmp(const char* ws1, const char* ws2, UPInt size) +{ + return strncmp(ws1, ws2, size); +} + +inline UInt64 OVR_CDECL OVR_strtouq(const char *nptr, char **endptr, int base) +{ +#if defined(OVR_CC_MSVC) && !defined(OVR_OS_WINCE) + return _strtoui64(nptr, endptr, base); +#else + return strtoull(nptr, endptr, base); +#endif +} + +inline SInt64 OVR_CDECL OVR_strtoq(const char *nptr, char **endptr, int base) +{ +#if defined(OVR_CC_MSVC) && !defined(OVR_OS_WINCE) + return _strtoi64(nptr, endptr, base); +#else + return strtoll(nptr, endptr, base); +#endif +} + + +inline SInt64 OVR_CDECL OVR_atoq(const char* string) +{ +#if defined(OVR_CC_MSVC) && !defined(OVR_OS_WINCE) + return _atoi64(string); +#else + return atoll(string); +#endif +} + +inline UInt64 OVR_CDECL OVR_atouq(const char* string) +{ + return OVR_strtouq(string, NULL, 10); +} + + +// Implemented in GStd.cpp in platform-specific manner. +int OVR_CDECL OVR_stricmp(const char* dest, const char* src); +int OVR_CDECL OVR_strnicmp(const char* dest, const char* src, UPInt count); + +inline UPInt OVR_CDECL OVR_sprintf(char *dest, UPInt destsize, const char* format, ...) +{ + va_list argList; + va_start(argList,format); + UPInt ret; +#if defined(OVR_CC_MSVC) + #if defined(OVR_MSVC_SAFESTRING) + ret = _vsnprintf_s(dest, destsize, _TRUNCATE, format, argList); + OVR_ASSERT(ret != -1); + #else + OVR_UNUSED(destsize); + ret = _vsnprintf(dest, destsize - 1, format, argList); // -1 for space for the null character + OVR_ASSERT(ret != -1); + dest[destsize-1] = 0; + #endif +#else + OVR_UNUSED(destsize); + ret = vsprintf(dest, format, argList); + OVR_ASSERT(ret < destsize); +#endif + va_end(argList); + return ret; +} + +inline UPInt OVR_CDECL OVR_vsprintf(char *dest, UPInt destsize, const char * format, va_list argList) +{ + UPInt ret; +#if defined(OVR_CC_MSVC) + #if defined(OVR_MSVC_SAFESTRING) + dest[0] = '\0'; + int rv = vsnprintf_s(dest, destsize, _TRUNCATE, format, argList); + if (rv == -1) + { + dest[destsize - 1] = '\0'; + ret = destsize - 1; + } + else + ret = (UPInt)rv; + #else + OVR_UNUSED(destsize); + int rv = _vsnprintf(dest, destsize - 1, format, argList); + OVR_ASSERT(rv != -1); + ret = (UPInt)rv; + dest[destsize-1] = 0; + #endif +#else + OVR_UNUSED(destsize); + ret = (UPInt)vsprintf(dest, format, argList); + OVR_ASSERT(ret < destsize); +#endif + return ret; +} + +// Returns the number of characters in the formatted string. +inline UPInt OVR_CDECL OVR_vscprintf(const char * format, va_list argList) +{ + UPInt ret; +#if defined(OVR_CC_MSVC) + ret = (UPInt) _vscprintf(format, argList); +#else + ret = (UPInt) vsnprintf(NULL, 0, format, argList); +#endif + return ret; +} + + +wchar_t* OVR_CDECL OVR_wcscpy(wchar_t* dest, UPInt destsize, const wchar_t* src); +wchar_t* OVR_CDECL OVR_wcsncpy(wchar_t* dest, UPInt destsize, const wchar_t* src, UPInt count); +wchar_t* OVR_CDECL OVR_wcscat(wchar_t* dest, UPInt destsize, const wchar_t* src); +UPInt OVR_CDECL OVR_wcslen(const wchar_t* str); +int OVR_CDECL OVR_wcscmp(const wchar_t* a, const wchar_t* b); +int OVR_CDECL OVR_wcsicmp(const wchar_t* a, const wchar_t* b); + +inline int OVR_CDECL OVR_wcsicoll(const wchar_t* a, const wchar_t* b) +{ +#if defined(OVR_OS_WIN32) +#if defined(OVR_CC_MSVC) && (OVR_CC_MSVC >= 1400) + return ::_wcsicoll(a, b); +#else + return ::wcsicoll(a, b); +#endif +#else + // not supported, use regular wcsicmp + return OVR_wcsicmp(a, b); +#endif +} + +inline int OVR_CDECL OVR_wcscoll(const wchar_t* a, const wchar_t* b) +{ +#if defined(OVR_OS_WIN32) || defined(OVR_OS_LINUX) + return wcscoll(a, b); +#else + // not supported, use regular wcscmp + return OVR_wcscmp(a, b); +#endif +} + +#ifndef OVR_NO_WCTYPE + +inline int OVR_CDECL UnicodeCharIs(const UInt16* table, wchar_t charCode) +{ + unsigned offset = table[charCode >> 8]; + if (offset == 0) return 0; + if (offset == 1) return 1; + return (table[offset + ((charCode >> 4) & 15)] & (1 << (charCode & 15))) != 0; +} + +extern const UInt16 UnicodeAlnumBits[]; +extern const UInt16 UnicodeAlphaBits[]; +extern const UInt16 UnicodeDigitBits[]; +extern const UInt16 UnicodeSpaceBits[]; +extern const UInt16 UnicodeXDigitBits[]; + +// Uncomment if necessary +//extern const UInt16 UnicodeCntrlBits[]; +//extern const UInt16 UnicodeGraphBits[]; +//extern const UInt16 UnicodeLowerBits[]; +//extern const UInt16 UnicodePrintBits[]; +//extern const UInt16 UnicodePunctBits[]; +//extern const UInt16 UnicodeUpperBits[]; + +inline int OVR_CDECL OVR_iswalnum (wchar_t charCode) { return UnicodeCharIs(UnicodeAlnumBits, charCode); } +inline int OVR_CDECL OVR_iswalpha (wchar_t charCode) { return UnicodeCharIs(UnicodeAlphaBits, charCode); } +inline int OVR_CDECL OVR_iswdigit (wchar_t charCode) { return UnicodeCharIs(UnicodeDigitBits, charCode); } +inline int OVR_CDECL OVR_iswspace (wchar_t charCode) { return UnicodeCharIs(UnicodeSpaceBits, charCode); } +inline int OVR_CDECL OVR_iswxdigit(wchar_t charCode) { return UnicodeCharIs(UnicodeXDigitBits, charCode); } + +// Uncomment if necessary +//inline int OVR_CDECL OVR_iswcntrl (wchar_t charCode) { return UnicodeCharIs(UnicodeCntrlBits, charCode); } +//inline int OVR_CDECL OVR_iswgraph (wchar_t charCode) { return UnicodeCharIs(UnicodeGraphBits, charCode); } +//inline int OVR_CDECL OVR_iswlower (wchar_t charCode) { return UnicodeCharIs(UnicodeLowerBits, charCode); } +//inline int OVR_CDECL OVR_iswprint (wchar_t charCode) { return UnicodeCharIs(UnicodePrintBits, charCode); } +//inline int OVR_CDECL OVR_iswpunct (wchar_t charCode) { return UnicodeCharIs(UnicodePunctBits, charCode); } +//inline int OVR_CDECL OVR_iswupper (wchar_t charCode) { return UnicodeCharIs(UnicodeUpperBits, charCode); } + +int OVR_CDECL OVR_towupper(wchar_t charCode); +int OVR_CDECL OVR_towlower(wchar_t charCode); + +#else // OVR_NO_WCTYPE + +inline int OVR_CDECL OVR_iswspace(wchar_t c) +{ + return iswspace(c); +} + +inline int OVR_CDECL OVR_iswdigit(wchar_t c) +{ + return iswdigit(c); +} + +inline int OVR_CDECL OVR_iswxdigit(wchar_t c) +{ + return iswxdigit(c); +} + +inline int OVR_CDECL OVR_iswalpha(wchar_t c) +{ + return iswalpha(c); +} + +inline int OVR_CDECL OVR_iswalnum(wchar_t c) +{ + return iswalnum(c); +} + +inline wchar_t OVR_CDECL OVR_towlower(wchar_t c) +{ + return (wchar_t)towlower(c); +} + +inline wchar_t OVR_towupper(wchar_t c) +{ + return (wchar_t)towupper(c); +} + +#endif // OVR_NO_WCTYPE + +// ASCII versions of tolower and toupper. Don't use "char" +inline int OVR_CDECL OVR_tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; +} + +inline int OVR_CDECL OVR_toupper(int c) +{ + return (c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c; +} + + + +inline double OVR_CDECL OVR_wcstod(const wchar_t* string, wchar_t** tailptr) +{ +#if defined(OVR_OS_OTHER) + OVR_UNUSED(tailptr); + char buffer[64]; + char* tp = NULL; + UPInt max = OVR_wcslen(string); + if (max > 63) max = 63; + unsigned char c = 0; + for (UPInt i=0; i < max; i++) + { + c = (unsigned char)string[i]; + buffer[i] = ((c) < 128 ? (char)c : '!'); + } + buffer[max] = 0; + return OVR_strtod(buffer, &tp); +#else + return wcstod(string, tailptr); +#endif +} + +inline long OVR_CDECL OVR_wcstol(const wchar_t* string, wchar_t** tailptr, int radix) +{ +#if defined(OVR_OS_OTHER) + OVR_UNUSED(tailptr); + char buffer[64]; + char* tp = NULL; + UPInt max = OVR_wcslen(string); + if (max > 63) max = 63; + unsigned char c = 0; + for (UPInt i=0; i < max; i++) + { + c = (unsigned char)string[i]; + buffer[i] = ((c) < 128 ? (char)c : '!'); + } + buffer[max] = 0; + return strtol(buffer, &tp, radix); +#else + return wcstol(string, tailptr, radix); +#endif +} + +} // OVR + +#endif // OVR_Std_h diff --git a/LibOVR/Src/Kernel/OVR_String.cpp b/LibOVR/Src/Kernel/OVR_String.cpp new file mode 100644 index 0000000..75b7c0e --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_String.cpp @@ -0,0 +1,768 @@ +/************************************************************************************ + +Filename : OVR_String.cpp +Content : String UTF8 string implementation with copy-on-write semantics + (thread-safe for assignment but not modification). +Created : September 19, 2012 +Notes : + +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 "OVR_String.h" + +#include <stdlib.h> +#include <ctype.h> + +#ifdef OVR_OS_QNX +# include <strings.h> +#endif + +namespace OVR { + +#define String_LengthIsSize (UPInt(1) << String::Flag_LengthIsSizeShift) + +String::DataDesc String::NullData = {String_LengthIsSize, 1, {0} }; + + +String::String() +{ + pData = &NullData; + pData->AddRef(); +}; + +String::String(const char* pdata) +{ + // Obtain length in bytes; it doesn't matter if _data is UTF8. + UPInt size = pdata ? OVR_strlen(pdata) : 0; + pData = AllocDataCopy1(size, 0, pdata, size); +}; + +String::String(const char* pdata1, const char* pdata2, const char* pdata3) +{ + // Obtain length in bytes; it doesn't matter if _data is UTF8. + UPInt size1 = pdata1 ? OVR_strlen(pdata1) : 0; + UPInt size2 = pdata2 ? OVR_strlen(pdata2) : 0; + UPInt size3 = pdata3 ? OVR_strlen(pdata3) : 0; + + DataDesc *pdataDesc = AllocDataCopy2(size1 + size2 + size3, 0, + pdata1, size1, pdata2, size2); + memcpy(pdataDesc->Data + size1 + size2, pdata3, size3); + pData = pdataDesc; +} + +String::String(const char* pdata, UPInt size) +{ + OVR_ASSERT((size == 0) || (pdata != 0)); + pData = AllocDataCopy1(size, 0, pdata, size); +}; + + +String::String(const InitStruct& src, UPInt size) +{ + pData = AllocData(size, 0); + src.InitString(GetData()->Data, size); +} + +String::String(const String& src) +{ + pData = src.GetData(); + pData->AddRef(); +} + +String::String(const StringBuffer& src) +{ + pData = AllocDataCopy1(src.GetSize(), 0, src.ToCStr(), src.GetSize()); +} + +String::String(const wchar_t* data) +{ + pData = &NullData; + pData->AddRef(); + // Simplified logic for wchar_t constructor. + if (data) + *this = data; +} + + +String::DataDesc* String::AllocData(UPInt size, UPInt lengthIsSize) +{ + String::DataDesc* pdesc; + + if (size == 0) + { + pdesc = &NullData; + pdesc->AddRef(); + return pdesc; + } + + pdesc = (DataDesc*)OVR_ALLOC(sizeof(DataDesc)+ size); + pdesc->Data[size] = 0; + pdesc->RefCount = 1; + pdesc->Size = size | lengthIsSize; + return pdesc; +} + + +String::DataDesc* String::AllocDataCopy1(UPInt size, UPInt lengthIsSize, + const char* pdata, UPInt copySize) +{ + String::DataDesc* pdesc = AllocData(size, lengthIsSize); + memcpy(pdesc->Data, pdata, copySize); + return pdesc; +} + +String::DataDesc* String::AllocDataCopy2(UPInt size, UPInt lengthIsSize, + const char* pdata1, UPInt copySize1, + const char* pdata2, UPInt copySize2) +{ + String::DataDesc* pdesc = AllocData(size, lengthIsSize); + memcpy(pdesc->Data, pdata1, copySize1); + memcpy(pdesc->Data + copySize1, pdata2, copySize2); + return pdesc; +} + + +UPInt String::GetLength() const +{ + // Optimize length accesses for non-UTF8 character strings. + DataDesc* pdata = GetData(); + UPInt length, size = pdata->GetSize(); + + if (pdata->LengthIsSize()) + return size; + + length = (UPInt)UTF8Util::GetLength(pdata->Data, (UPInt)size); + + if (length == size) + pdata->Size |= String_LengthIsSize; + + return length; +} + + +//static UInt32 String_CharSearch(const char* buf, ) + + +UInt32 String::GetCharAt(UPInt index) const +{ + SPInt i = (SPInt) index; + DataDesc* pdata = GetData(); + const char* buf = pdata->Data; + UInt32 c; + + if (pdata->LengthIsSize()) + { + OVR_ASSERT(index < pdata->GetSize()); + buf += i; + return UTF8Util::DecodeNextChar_Advance0(&buf); + } + + c = UTF8Util::GetCharAt(index, buf, pdata->GetSize()); + return c; +} + +UInt32 String::GetFirstCharAt(UPInt index, const char** offset) const +{ + DataDesc* pdata = GetData(); + SPInt i = (SPInt) index; + const char* buf = pdata->Data; + const char* end = buf + pdata->GetSize(); + UInt32 c; + + do + { + c = UTF8Util::DecodeNextChar_Advance0(&buf); + i--; + + if (buf >= end) + { + // We've hit the end of the string; don't go further. + OVR_ASSERT(i == 0); + return c; + } + } while (i >= 0); + + *offset = buf; + + return c; +} + +UInt32 String::GetNextChar(const char** offset) const +{ + return UTF8Util::DecodeNextChar(offset); +} + + + +void String::AppendChar(UInt32 ch) +{ + DataDesc* pdata = GetData(); + UPInt size = pdata->GetSize(); + char buff[8]; + SPInt encodeSize = 0; + + // Converts ch into UTF8 string and fills it into buff. + UTF8Util::EncodeChar(buff, &encodeSize, ch); + OVR_ASSERT(encodeSize >= 0); + + SetData(AllocDataCopy2(size + (UPInt)encodeSize, 0, + pdata->Data, size, buff, (UPInt)encodeSize)); + pdata->Release(); +} + + +void String::AppendString(const wchar_t* pstr, SPInt len) +{ + if (!pstr) + return; + + DataDesc* pdata = GetData(); + UPInt oldSize = pdata->GetSize(); + UPInt encodeSize = (UPInt)UTF8Util::GetEncodeStringSize(pstr, len); + + DataDesc* pnewData = AllocDataCopy1(oldSize + (UPInt)encodeSize, 0, + pdata->Data, oldSize); + UTF8Util::EncodeString(pnewData->Data + oldSize, pstr, len); + + SetData(pnewData); + pdata->Release(); +} + + +void String::AppendString(const char* putf8str, SPInt utf8StrSz) +{ + if (!putf8str || !utf8StrSz) + return; + if (utf8StrSz == -1) + utf8StrSz = (SPInt)OVR_strlen(putf8str); + + DataDesc* pdata = GetData(); + UPInt oldSize = pdata->GetSize(); + + SetData(AllocDataCopy2(oldSize + (UPInt)utf8StrSz, 0, + pdata->Data, oldSize, putf8str, (UPInt)utf8StrSz)); + pdata->Release(); +} + +void String::AssignString(const InitStruct& src, UPInt size) +{ + DataDesc* poldData = GetData(); + DataDesc* pnewData = AllocData(size, 0); + src.InitString(pnewData->Data, size); + SetData(pnewData); + poldData->Release(); +} + +void String::AssignString(const char* putf8str, UPInt size) +{ + DataDesc* poldData = GetData(); + SetData(AllocDataCopy1(size, 0, putf8str, size)); + poldData->Release(); +} + +void String::operator = (const char* pstr) +{ + AssignString(pstr, pstr ? OVR_strlen(pstr) : 0); +} + +void String::operator = (const wchar_t* pwstr) +{ + DataDesc* poldData = GetData(); + UPInt size = pwstr ? (UPInt)UTF8Util::GetEncodeStringSize(pwstr) : 0; + + DataDesc* pnewData = AllocData(size, 0); + UTF8Util::EncodeString(pnewData->Data, pwstr); + SetData(pnewData); + poldData->Release(); +} + + +void String::operator = (const String& src) +{ + DataDesc* psdata = src.GetData(); + DataDesc* pdata = GetData(); + + SetData(psdata); + psdata->AddRef(); + pdata->Release(); +} + + +void String::operator = (const StringBuffer& src) +{ + DataDesc* polddata = GetData(); + SetData(AllocDataCopy1(src.GetSize(), 0, src.ToCStr(), src.GetSize())); + polddata->Release(); +} + +void String::operator += (const String& src) +{ + DataDesc *pourData = GetData(), + *psrcData = src.GetData(); + UPInt ourSize = pourData->GetSize(), + srcSize = psrcData->GetSize(); + UPInt lflag = pourData->GetLengthFlag() & psrcData->GetLengthFlag(); + + SetData(AllocDataCopy2(ourSize + srcSize, lflag, + pourData->Data, ourSize, psrcData->Data, srcSize)); + pourData->Release(); +} + + +String String::operator + (const char* str) const +{ + String tmp1(*this); + tmp1 += (str ? str : ""); + return tmp1; +} + +String String::operator + (const String& src) const +{ + String tmp1(*this); + tmp1 += src; + return tmp1; +} + +void String::Remove(UPInt posAt, SPInt removeLength) +{ + DataDesc* pdata = GetData(); + UPInt oldSize = pdata->GetSize(); + // Length indicates the number of characters to remove. + UPInt length = GetLength(); + + // If index is past the string, nothing to remove. + if (posAt >= length) + return; + // Otherwise, cap removeLength to the length of the string. + if ((posAt + removeLength) > length) + removeLength = length - posAt; + + // Get the byte position of the UTF8 char at position posAt. + SPInt bytePos = UTF8Util::GetByteIndex(posAt, pdata->Data, oldSize); + SPInt removeSize = UTF8Util::GetByteIndex(removeLength, pdata->Data + bytePos, oldSize-bytePos); + + SetData(AllocDataCopy2(oldSize - removeSize, pdata->GetLengthFlag(), + pdata->Data, bytePos, + pData->Data + bytePos + removeSize, (oldSize - bytePos - removeSize))); + pdata->Release(); +} + + +String String::Substring(UPInt start, UPInt end) const +{ + UPInt length = GetLength(); + if ((start >= length) || (start >= end)) + return String(); + + DataDesc* pdata = GetData(); + + // If size matches, we know the exact index range. + if (pdata->LengthIsSize()) + return String(pdata->Data + start, end - start); + + // Get position of starting character. + SPInt byteStart = UTF8Util::GetByteIndex(start, pdata->Data, pdata->GetSize()); + SPInt byteSize = UTF8Util::GetByteIndex(end - start, pdata->Data + byteStart, pdata->GetSize()-byteStart); + return String(pdata->Data + byteStart, (UPInt)byteSize); +} + +void String::Clear() +{ + NullData.AddRef(); + GetData()->Release(); + SetData(&NullData); +} + + +String String::ToUpper() const +{ + UInt32 c; + const char* psource = GetData()->Data; + const char* pend = psource + GetData()->GetSize(); + String str; + SPInt bufferOffset = 0; + char buffer[512]; + + while(psource < pend) + { + do { + c = UTF8Util::DecodeNextChar_Advance0(&psource); + UTF8Util::EncodeChar(buffer, &bufferOffset, OVR_towupper(wchar_t(c))); + } while ((psource < pend) && (bufferOffset < SPInt(sizeof(buffer)-8))); + + // Append string a piece at a time. + str.AppendString(buffer, bufferOffset); + bufferOffset = 0; + } + + return str; +} + +String String::ToLower() const +{ + UInt32 c; + const char* psource = GetData()->Data; + const char* pend = psource + GetData()->GetSize(); + String str; + SPInt bufferOffset = 0; + char buffer[512]; + + while(psource < pend) + { + do { + c = UTF8Util::DecodeNextChar_Advance0(&psource); + UTF8Util::EncodeChar(buffer, &bufferOffset, OVR_towlower(wchar_t(c))); + } while ((psource < pend) && (bufferOffset < SPInt(sizeof(buffer)-8))); + + // Append string a piece at a time. + str.AppendString(buffer, bufferOffset); + bufferOffset = 0; + } + + return str; +} + + + +String& String::Insert(const char* substr, UPInt posAt, SPInt strSize) +{ + DataDesc* poldData = GetData(); + UPInt oldSize = poldData->GetSize(); + UPInt insertSize = (strSize < 0) ? OVR_strlen(substr) : (UPInt)strSize; + UPInt byteIndex = (poldData->LengthIsSize()) ? + posAt : (UPInt)UTF8Util::GetByteIndex(posAt, poldData->Data, oldSize); + + OVR_ASSERT(byteIndex <= oldSize); + + DataDesc* pnewData = AllocDataCopy2(oldSize + insertSize, 0, + poldData->Data, byteIndex, substr, insertSize); + memcpy(pnewData->Data + byteIndex + insertSize, + poldData->Data + byteIndex, oldSize - byteIndex); + SetData(pnewData); + poldData->Release(); + return *this; +} + +/* +String& String::Insert(const UInt32* substr, UPInt posAt, SPInt len) +{ + for (SPInt i = 0; i < len; ++i) + { + UPInt charw = InsertCharAt(substr[i], posAt); + posAt += charw; + } + return *this; +} +*/ + +UPInt String::InsertCharAt(UInt32 c, UPInt posAt) +{ + char buf[8]; + SPInt index = 0; + UTF8Util::EncodeChar(buf, &index, c); + OVR_ASSERT(index >= 0); + buf[(UPInt)index] = 0; + + Insert(buf, posAt, index); + return (UPInt)index; +} + + +int String::CompareNoCase(const char* a, const char* b) +{ + return OVR_stricmp(a, b); +} + +int String::CompareNoCase(const char* a, const char* b, SPInt len) +{ + if (len) + { + SPInt f,l; + SPInt slen = len; + const char *s = b; + do { + f = (SPInt)OVR_tolower((int)(*(a++))); + l = (SPInt)OVR_tolower((int)(*(b++))); + } while (--len && f && (f == l) && *b != 0); + + if (f == l && (len != 0 || *b != 0)) + { + f = (SPInt)slen; + l = (SPInt)OVR_strlen(s); + return int(f - l); + } + + return int(f - l); + } + else + return (0-(int)OVR_strlen(b)); +} + +// ***** Implement hash static functions + +// Hash function +UPInt String::BernsteinHashFunction(const void* pdataIn, UPInt size, UPInt seed) +{ + const UByte* pdata = (const UByte*) pdataIn; + UPInt h = seed; + while (size > 0) + { + size--; + h = ((h << 5) + h) ^ (unsigned) pdata[size]; + } + + return h; +} + +// Hash function, case-insensitive +UPInt String::BernsteinHashFunctionCIS(const void* pdataIn, UPInt size, UPInt seed) +{ + const UByte* pdata = (const UByte*) pdataIn; + UPInt h = seed; + while (size > 0) + { + size--; + h = ((h << 5) + h) ^ OVR_tolower(pdata[size]); + } + + // Alternative: "sdbm" hash function, suggested at same web page above. + // h = 0; + // for bytes { h = (h << 16) + (h << 6) - hash + *p; } + return h; +} + + + +// ***** String Buffer used for Building Strings + + +#define OVR_SBUFF_DEFAULT_GROW_SIZE 512 +// Constructors / Destructor. +StringBuffer::StringBuffer() + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ +} + +StringBuffer::StringBuffer(UPInt growSize) + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ + SetGrowSize(growSize); +} + +StringBuffer::StringBuffer(const char* data) + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ + AppendString(data); +} + +StringBuffer::StringBuffer(const char* data, UPInt dataSize) + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ + AppendString(data, dataSize); +} + +StringBuffer::StringBuffer(const String& src) + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ + AppendString(src.ToCStr(), src.GetSize()); +} + +StringBuffer::StringBuffer(const StringBuffer& src) + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ + AppendString(src.ToCStr(), src.GetSize()); +} + +StringBuffer::StringBuffer(const wchar_t* data) + : pData(NULL), Size(0), BufferSize(0), GrowSize(OVR_SBUFF_DEFAULT_GROW_SIZE), LengthIsSize(false) +{ + *this = data; +} + +StringBuffer::~StringBuffer() +{ + if (pData) + OVR_FREE(pData); +} +void StringBuffer::SetGrowSize(UPInt growSize) +{ + if (growSize <= 16) + GrowSize = 16; + else + { + UByte bits = Alg::UpperBit(UInt32(growSize-1)); + UPInt size = 1<<bits; + GrowSize = size == growSize ? growSize : size; + } +} + +UPInt StringBuffer::GetLength() const +{ + UPInt length, size = GetSize(); + if (LengthIsSize) + return size; + + length = (UPInt)UTF8Util::GetLength(pData, (UPInt)GetSize()); + + if (length == GetSize()) + LengthIsSize = true; + return length; +} + +void StringBuffer::Reserve(UPInt _size) +{ + if (_size >= BufferSize) // >= because of trailing zero! (!AB) + { + BufferSize = (_size + 1 + GrowSize - 1)& ~(GrowSize-1); + if (!pData) + pData = (char*)OVR_ALLOC(BufferSize); + else + pData = (char*)OVR_REALLOC(pData, BufferSize); + } +} +void StringBuffer::Resize(UPInt _size) +{ + Reserve(_size); + LengthIsSize = false; + Size = _size; + if (pData) + pData[Size] = 0; +} + +void StringBuffer::Clear() +{ + Resize(0); + /* + if (pData != pEmptyNullData) + { + OVR_FREE(pHeap, pData); + pData = pEmptyNullData; + Size = BufferSize = 0; + LengthIsSize = false; + } + */ +} +// Appends a character +void StringBuffer::AppendChar(UInt32 ch) +{ + char buff[8]; + UPInt origSize = GetSize(); + + // Converts ch into UTF8 string and fills it into buff. Also increments index according to the number of bytes + // in the UTF8 string. + SPInt srcSize = 0; + UTF8Util::EncodeChar(buff, &srcSize, ch); + OVR_ASSERT(srcSize >= 0); + + UPInt size = origSize + srcSize; + Resize(size); + memcpy(pData + origSize, buff, srcSize); +} + +// Append a string +void StringBuffer::AppendString(const wchar_t* pstr, SPInt len) +{ + if (!pstr) + return; + + SPInt srcSize = UTF8Util::GetEncodeStringSize(pstr, len); + UPInt origSize = GetSize(); + UPInt size = srcSize + origSize; + + Resize(size); + UTF8Util::EncodeString(pData + origSize, pstr, len); +} + +void StringBuffer::AppendString(const char* putf8str, SPInt utf8StrSz) +{ + if (!putf8str || !utf8StrSz) + return; + if (utf8StrSz == -1) + utf8StrSz = (SPInt)OVR_strlen(putf8str); + + UPInt origSize = GetSize(); + UPInt size = utf8StrSz + origSize; + + Resize(size); + memcpy(pData + origSize, putf8str, utf8StrSz); +} + + +void StringBuffer::operator = (const char* pstr) +{ + pstr = pstr ? pstr : ""; + UPInt size = OVR_strlen(pstr); + Resize(size); + memcpy(pData, pstr, size); +} + +void StringBuffer::operator = (const wchar_t* pstr) +{ + pstr = pstr ? pstr : L""; + UPInt size = (UPInt)UTF8Util::GetEncodeStringSize(pstr); + Resize(size); + UTF8Util::EncodeString(pData, pstr); +} + +void StringBuffer::operator = (const String& src) +{ + Resize(src.GetSize()); + memcpy(pData, src.ToCStr(), src.GetSize()); +} + +void StringBuffer::operator = (const StringBuffer& src) +{ + Clear(); + AppendString(src.ToCStr(), src.GetSize()); +} + + +// Inserts substr at posAt +void StringBuffer::Insert(const char* substr, UPInt posAt, SPInt len) +{ + UPInt oldSize = Size; + UPInt insertSize = (len < 0) ? OVR_strlen(substr) : (UPInt)len; + UPInt byteIndex = LengthIsSize ? posAt : + (UPInt)UTF8Util::GetByteIndex(posAt, pData, (SPInt)Size); + + OVR_ASSERT(byteIndex <= oldSize); + Reserve(oldSize + insertSize); + + memmove(pData + byteIndex + insertSize, pData + byteIndex, oldSize - byteIndex + 1); + memcpy (pData + byteIndex, substr, insertSize); + LengthIsSize = false; + Size = oldSize + insertSize; + pData[Size] = 0; +} + +// Inserts character at posAt +UPInt StringBuffer::InsertCharAt(UInt32 c, UPInt posAt) +{ + char buf[8]; + SPInt len = 0; + UTF8Util::EncodeChar(buf, &len, c); + OVR_ASSERT(len >= 0); + buf[(UPInt)len] = 0; + + Insert(buf, posAt, len); + return (UPInt)len; +} + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_String.h b/LibOVR/Src/Kernel/OVR_String.h new file mode 100644 index 0000000..0866968 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_String.h @@ -0,0 +1,657 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_String.h +Content : String UTF8 string implementation with copy-on-write semantics + (thread-safe for assignment but not modification). +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_String_h +#define OVR_String_h + +#include "OVR_Types.h" +#include "OVR_Allocator.h" +#include "OVR_UTF8Util.h" +#include "OVR_Atomic.h" +#include "OVR_Std.h" +#include "OVR_Alg.h" + +namespace OVR { + +// ***** Classes + +class String; +class StringBuffer; + + +//----------------------------------------------------------------------------------- +// ***** String Class + +// String is UTF8 based string class with copy-on-write implementation +// for assignment. + +class String +{ +protected: + + enum FlagConstants + { + //Flag_GetLength = 0x7FFFFFFF, + // This flag is set if GetLength() == GetSize() for a string. + // Avoid extra scanning is Substring and indexing logic. + Flag_LengthIsSizeShift = (sizeof(UPInt)*8 - 1) + }; + + + // Internal structure to hold string data + struct DataDesc + { + // Number of bytes. Will be the same as the number of chars if the characters + // are ascii, may not be equal to number of chars in case string data is UTF8. + UPInt Size; + volatile SInt32 RefCount; + char Data[1]; + + void AddRef() + { + AtomicOps<SInt32>::ExchangeAdd_NoSync(&RefCount, 1); + } + // Decrement ref count. This needs to be thread-safe, since + // a different thread could have also decremented the ref count. + // For example, if u start off with a ref count = 2. Now if u + // decrement the ref count and check against 0 in different + // statements, a different thread can also decrement the ref count + // in between our decrement and checking against 0 and will find + // the ref count = 0 and delete the object. This will lead to a crash + // when context switches to our thread and we'll be trying to delete + // an already deleted object. Hence decrementing the ref count and + // checking against 0 needs to made an atomic operation. + void Release() + { + if ((AtomicOps<SInt32>::ExchangeAdd_NoSync(&RefCount, -1) - 1) == 0) + OVR_FREE(this); + } + + static UPInt GetLengthFlagBit() { return UPInt(1) << Flag_LengthIsSizeShift; } + UPInt GetSize() const { return Size & ~GetLengthFlagBit() ; } + UPInt GetLengthFlag() const { return Size & GetLengthFlagBit(); } + bool LengthIsSize() const { return GetLengthFlag() != 0; } + }; + + // Heap type of the string is encoded in the lower bits. + enum HeapType + { + HT_Global = 0, // Heap is global. + HT_Local = 1, // SF::String_loc: Heap is determined based on string's address. + HT_Dynamic = 2, // SF::String_temp: Heap is stored as a part of the class. + HT_Mask = 3 + }; + + union { + DataDesc* pData; + UPInt HeapTypeBits; + }; + typedef union { + DataDesc* pData; + UPInt HeapTypeBits; + } DataDescUnion; + + inline HeapType GetHeapType() const { return (HeapType) (HeapTypeBits & HT_Mask); } + + inline DataDesc* GetData() const + { + DataDescUnion u; + u.pData = pData; + u.HeapTypeBits = (u.HeapTypeBits & ~(UPInt)HT_Mask); + return u.pData; + } + + inline void SetData(DataDesc* pdesc) + { + HeapType ht = GetHeapType(); + pData = pdesc; + OVR_ASSERT((HeapTypeBits & HT_Mask) == 0); + HeapTypeBits |= ht; + } + + + DataDesc* AllocData(UPInt size, UPInt lengthIsSize); + DataDesc* AllocDataCopy1(UPInt size, UPInt lengthIsSize, + const char* pdata, UPInt copySize); + DataDesc* AllocDataCopy2(UPInt size, UPInt lengthIsSize, + const char* pdata1, UPInt copySize1, + const char* pdata2, UPInt copySize2); + + // Special constructor to avoid data initalization when used in derived class. + struct NoConstructor { }; + String(const NoConstructor&) { } + +public: + + // For initializing string with dynamic buffer + struct InitStruct + { + virtual ~InitStruct() { } + virtual void InitString(char* pbuffer, UPInt size) const = 0; + }; + + + // Constructors / Destructors. + String(); + String(const char* data); + String(const char* data1, const char* pdata2, const char* pdata3 = 0); + String(const char* data, UPInt buflen); + String(const String& src); + String(const StringBuffer& src); + String(const InitStruct& src, UPInt size); + explicit String(const wchar_t* data); + + // Destructor (Captain Obvious guarantees!) + ~String() + { + GetData()->Release(); + } + + // Declaration of NullString + static DataDesc NullData; + + + // *** General Functions + + void Clear(); + + // For casting to a pointer to char. + operator const char*() const { return GetData()->Data; } + // Pointer to raw buffer. + const char* ToCStr() const { return GetData()->Data; } + + // Returns number of bytes + UPInt GetSize() const { return GetData()->GetSize() ; } + // Tells whether or not the string is empty + bool IsEmpty() const { return GetSize() == 0; } + + // Returns number of characters + UPInt GetLength() const; + + // Returns character at the specified index + UInt32 GetCharAt(UPInt index) const; + UInt32 GetFirstCharAt(UPInt index, const char** offset) const; + UInt32 GetNextChar(const char** offset) const; + + // Appends a character + void AppendChar(UInt32 ch); + + // Append a string + void AppendString(const wchar_t* pstr, SPInt len = -1); + void AppendString(const char* putf8str, SPInt utf8StrSz = -1); + + // Assigned a string with dynamic data (copied through initializer). + void AssignString(const InitStruct& src, UPInt size); + // Assigns string with known size. + void AssignString(const char* putf8str, UPInt size); + + // Resize the string to the new size +// void Resize(UPInt _size); + + // Removes the character at posAt + void Remove(UPInt posAt, SPInt len = 1); + + // Returns a String that's a substring of this. + // -start is the index of the first UTF8 character you want to include. + // -end is the index one past the last UTF8 character you want to include. + String Substring(UPInt start, UPInt end) const; + + // Case-conversion + String ToUpper() const; + String ToLower() const; + + // Inserts substr at posAt + String& Insert (const char* substr, UPInt posAt, SPInt len = -1); + + // Inserts character at posAt + UPInt InsertCharAt(UInt32 c, UPInt posAt); + + // Inserts substr at posAt, which is an index of a character (not byte). + // Of size is specified, it is in bytes. +// String& Insert(const UInt32* substr, UPInt posAt, SPInt size = -1); + + // Get Byte index of the character at position = index + UPInt GetByteIndex(UPInt index) const { return (UPInt)UTF8Util::GetByteIndex(index, GetData()->Data); } + + // Utility: case-insensitive string compare. stricmp() & strnicmp() are not + // ANSI or POSIX, do not seem to appear in Linux. + static int OVR_STDCALL CompareNoCase(const char* a, const char* b); + static int OVR_STDCALL CompareNoCase(const char* a, const char* b, SPInt len); + + // Hash function, case-insensitive + static UPInt OVR_STDCALL BernsteinHashFunctionCIS(const void* pdataIn, UPInt size, UPInt seed = 5381); + + // Hash function, case-sensitive + static UPInt OVR_STDCALL BernsteinHashFunction(const void* pdataIn, UPInt size, UPInt seed = 5381); + + + // ***** File path parsing helper functions. + // Implemented in OVR_String_FilePath.cpp. + + // Absolute paths can star with: + // - protocols: 'file://', 'http://' + // - windows drive: 'c:\' + // - UNC share name: '\\share' + // - unix root '/' + static bool HasAbsolutePath(const char* path); + static bool HasExtension(const char* path); + static bool HasProtocol(const char* path); + + bool HasAbsolutePath() const { return HasAbsolutePath(ToCStr()); } + bool HasExtension() const { return HasExtension(ToCStr()); } + bool HasProtocol() const { return HasProtocol(ToCStr()); } + + String GetProtocol() const; // Returns protocol, if any, with trailing '://'. + String GetPath() const; // Returns path with trailing '/'. + String GetFilename() const; // Returns filename, including extension. + String GetExtension() const; // Returns extension with a dot. + + void StripProtocol(); // Strips front protocol, if any, from the string. + void StripExtension(); // Strips off trailing extension. + + + // Operators + // Assignment + void operator = (const char* str); + void operator = (const wchar_t* str); + void operator = (const String& src); + void operator = (const StringBuffer& src); + + // Addition + void operator += (const String& src); + void operator += (const char* psrc) { AppendString(psrc); } + void operator += (const wchar_t* psrc) { AppendString(psrc); } + void operator += (char ch) { AppendChar(ch); } + String operator + (const char* str) const; + String operator + (const String& src) const; + + // Comparison + bool operator == (const String& str) const + { + return (OVR_strcmp(GetData()->Data, str.GetData()->Data)== 0); + } + + bool operator != (const String& str) const + { + return !operator == (str); + } + + bool operator == (const char* str) const + { + return OVR_strcmp(GetData()->Data, str) == 0; + } + + bool operator != (const char* str) const + { + return !operator == (str); + } + + bool operator < (const char* pstr) const + { + return OVR_strcmp(GetData()->Data, pstr) < 0; + } + + bool operator < (const String& str) const + { + return *this < str.GetData()->Data; + } + + bool operator > (const char* pstr) const + { + return OVR_strcmp(GetData()->Data, pstr) > 0; + } + + bool operator > (const String& str) const + { + return *this > str.GetData()->Data; + } + + int CompareNoCase(const char* pstr) const + { + return CompareNoCase(GetData()->Data, pstr); + } + int CompareNoCase(const String& str) const + { + return CompareNoCase(GetData()->Data, str.ToCStr()); + } + + // Accesses raw bytes + const char& operator [] (int index) const + { + OVR_ASSERT(index >= 0 && (UPInt)index < GetSize()); + return GetData()->Data[index]; + } + const char& operator [] (UPInt index) const + { + OVR_ASSERT(index < GetSize()); + return GetData()->Data[index]; + } + + + // Case insensitive keys are used to look up insensitive string in hash tables + // for SWF files with version before SWF 7. + struct NoCaseKey + { + const String* pStr; + NoCaseKey(const String &str) : pStr(&str){}; + }; + + bool operator == (const NoCaseKey& strKey) const + { + return (CompareNoCase(ToCStr(), strKey.pStr->ToCStr()) == 0); + } + bool operator != (const NoCaseKey& strKey) const + { + return !(CompareNoCase(ToCStr(), strKey.pStr->ToCStr()) == 0); + } + + // Hash functor used for strings. + struct HashFunctor + { + UPInt operator()(const String& data) const + { + UPInt size = data.GetSize(); + return String::BernsteinHashFunction((const char*)data, size); + } + }; + // Case-insensitive hash functor used for strings. Supports additional + // lookup based on NoCaseKey. + struct NoCaseHashFunctor + { + UPInt operator()(const String& data) const + { + UPInt size = data.GetSize(); + return String::BernsteinHashFunctionCIS((const char*)data, size); + } + UPInt operator()(const NoCaseKey& data) const + { + UPInt size = data.pStr->GetSize(); + return String::BernsteinHashFunctionCIS((const char*)data.pStr->ToCStr(), size); + } + }; + +}; + + +//----------------------------------------------------------------------------------- +// ***** String Buffer used for Building Strings + +class StringBuffer +{ + char* pData; + UPInt Size; + UPInt BufferSize; + UPInt GrowSize; + mutable bool LengthIsSize; + +public: + + // Constructors / Destructor. + StringBuffer(); + explicit StringBuffer(UPInt growSize); + StringBuffer(const char* data); + StringBuffer(const char* data, UPInt buflen); + StringBuffer(const String& src); + StringBuffer(const StringBuffer& src); + explicit StringBuffer(const wchar_t* data); + ~StringBuffer(); + + + // Modify grow size used for growing/shrinking the buffer. + UPInt GetGrowSize() const { return GrowSize; } + void SetGrowSize(UPInt growSize); + + + // *** General Functions + // Does not release memory, just sets Size to 0 + void Clear(); + + // For casting to a pointer to char. + operator const char*() const { return (pData) ? pData : ""; } + // Pointer to raw buffer. + const char* ToCStr() const { return (pData) ? pData : ""; } + + // Returns number of bytes. + UPInt GetSize() const { return Size ; } + // Tells whether or not the string is empty. + bool IsEmpty() const { return GetSize() == 0; } + + // Returns number of characters + UPInt GetLength() const; + + // Returns character at the specified index + UInt32 GetCharAt(UPInt index) const; + UInt32 GetFirstCharAt(UPInt index, const char** offset) const; + UInt32 GetNextChar(const char** offset) const; + + + // Resize the string to the new size + void Resize(UPInt _size); + void Reserve(UPInt _size); + + // Appends a character + void AppendChar(UInt32 ch); + + // Append a string + void AppendString(const wchar_t* pstr, SPInt len = -1); + void AppendString(const char* putf8str, SPInt utf8StrSz = -1); + void AppendFormat(const char* format, ...); + + // Assigned a string with dynamic data (copied through initializer). + //void AssignString(const InitStruct& src, UPInt size); + + // Inserts substr at posAt + void Insert (const char* substr, UPInt posAt, SPInt len = -1); + // Inserts character at posAt + UPInt InsertCharAt(UInt32 c, UPInt posAt); + + // Assignment + void operator = (const char* str); + void operator = (const wchar_t* str); + void operator = (const String& src); + void operator = (const StringBuffer& src); + + // Addition + void operator += (const String& src) { AppendString(src.ToCStr(),src.GetSize()); } + void operator += (const char* psrc) { AppendString(psrc); } + void operator += (const wchar_t* psrc) { AppendString(psrc); } + void operator += (char ch) { AppendChar(ch); } + //String operator + (const char* str) const ; + //String operator + (const String& src) const ; + + // Accesses raw bytes + char& operator [] (int index) + { + OVR_ASSERT(((UPInt)index) < GetSize()); + return pData[index]; + } + char& operator [] (UPInt index) + { + OVR_ASSERT(index < GetSize()); + return pData[index]; + } + + const char& operator [] (int index) const + { + OVR_ASSERT(((UPInt)index) < GetSize()); + return pData[index]; + } + const char& operator [] (UPInt index) const + { + OVR_ASSERT(index < GetSize()); + return pData[index]; + } +}; + + +// +// Wrapper for string data. The data must have a guaranteed +// lifespan throughout the usage of the wrapper. Not intended for +// cached usage. Not thread safe. +// +class StringDataPtr +{ +public: + StringDataPtr() : pStr(NULL), Size(0) {} + StringDataPtr(const StringDataPtr& p) + : pStr(p.pStr), Size(p.Size) {} + StringDataPtr(const char* pstr, UPInt sz) + : pStr(pstr), Size(sz) {} + StringDataPtr(const char* pstr) + : pStr(pstr), Size((pstr != NULL) ? OVR_strlen(pstr) : 0) {} + explicit StringDataPtr(const String& str) + : pStr(str.ToCStr()), Size(str.GetSize()) {} + template <typename T, int N> + StringDataPtr(const T (&v)[N]) + : pStr(v), Size(N) {} + +public: + const char* ToCStr() const { return pStr; } + UPInt GetSize() const { return Size; } + bool IsEmpty() const { return GetSize() == 0; } + + // value is a prefix of this string + // Character's values are not compared. + bool IsPrefix(const StringDataPtr& value) const + { + return ToCStr() == value.ToCStr() && GetSize() >= value.GetSize(); + } + // value is a suffix of this string + // Character's values are not compared. + bool IsSuffix(const StringDataPtr& value) const + { + return ToCStr() <= value.ToCStr() && (End()) == (value.End()); + } + + // Find first character. + // init_ind - initial index. + SPInt FindChar(char c, UPInt init_ind = 0) const + { + for (UPInt i = init_ind; i < GetSize(); ++i) + if (pStr[i] == c) + return static_cast<SPInt>(i); + + return -1; + } + + // Find last character. + // init_ind - initial index. + SPInt FindLastChar(char c, UPInt init_ind = ~0) const + { + if (init_ind == (UPInt)~0 || init_ind > GetSize()) + init_ind = GetSize(); + else + ++init_ind; + + for (UPInt i = init_ind; i > 0; --i) + if (pStr[i - 1] == c) + return static_cast<SPInt>(i - 1); + + return -1; + } + + // Create new object and trim size bytes from the left. + StringDataPtr GetTrimLeft(UPInt size) const + { + // Limit trim size to the size of the string. + size = Alg::PMin(GetSize(), size); + + return StringDataPtr(ToCStr() + size, GetSize() - size); + } + // Create new object and trim size bytes from the right. + StringDataPtr GetTrimRight(UPInt size) const + { + // Limit trim to the size of the string. + size = Alg::PMin(GetSize(), size); + + return StringDataPtr(ToCStr(), GetSize() - size); + } + + // Create new object, which contains next token. + // Useful for parsing. + StringDataPtr GetNextToken(char separator = ':') const + { + UPInt cur_pos = 0; + const char* cur_str = ToCStr(); + + for (; cur_pos < GetSize() && cur_str[cur_pos]; ++cur_pos) + { + if (cur_str[cur_pos] == separator) + { + break; + } + } + + return StringDataPtr(ToCStr(), cur_pos); + } + + // Trim size bytes from the left. + StringDataPtr& TrimLeft(UPInt size) + { + // Limit trim size to the size of the string. + size = Alg::PMin(GetSize(), size); + pStr += size; + Size -= size; + + return *this; + } + // Trim size bytes from the right. + StringDataPtr& TrimRight(UPInt size) + { + // Limit trim to the size of the string. + size = Alg::PMin(GetSize(), size); + Size -= size; + + return *this; + } + + const char* Begin() const { return ToCStr(); } + const char* End() const { return ToCStr() + GetSize(); } + + // Hash functor used string data pointers + struct HashFunctor + { + UPInt operator()(const StringDataPtr& data) const + { + return String::BernsteinHashFunction(data.ToCStr(), data.GetSize()); + } + }; + + bool operator== (const StringDataPtr& data) const + { + return (OVR_strncmp(pStr, data.pStr, data.Size) == 0); + } + +protected: + const char* pStr; + UPInt Size; +}; + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_StringHash.h b/LibOVR/Src/Kernel/OVR_StringHash.h new file mode 100644 index 0000000..baa80a7 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_StringHash.h @@ -0,0 +1,100 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_StringHash.h +Content : String hash table used when optional case-insensitive + lookup is required. +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_StringHash_h +#define OVR_StringHash_h + +#include "OVR_String.h" +#include "OVR_Hash.h" + +namespace OVR { + +//----------------------------------------------------------------------------------- +// *** StringHash + +// This is a custom string hash table that supports case-insensitive +// searches through special functions such as GetCaseInsensitive, etc. +// This class is used for Flash labels, exports and other case-insensitive tables. + +template<class U, class Allocator = ContainerAllocator<U> > +class StringHash : public Hash<String, U, String::NoCaseHashFunctor, Allocator> +{ +public: + typedef U ValueType; + typedef StringHash<U, Allocator> SelfType; + typedef Hash<String, U, String::NoCaseHashFunctor, Allocator> BaseType; + +public: + + void operator = (const SelfType& src) { BaseType::operator = (src); } + + bool GetCaseInsensitive(const String& key, U* pvalue) const + { + String::NoCaseKey ikey(key); + return BaseType::GetAlt(ikey, pvalue); + } + // Pointer-returning get variety. + const U* GetCaseInsensitive(const String& key) const + { + String::NoCaseKey ikey(key); + return BaseType::GetAlt(ikey); + } + U* GetCaseInsensitive(const String& key) + { + String::NoCaseKey ikey(key); + return BaseType::GetAlt(ikey); + } + + + typedef typename BaseType::Iterator base_iterator; + + base_iterator FindCaseInsensitive(const String& key) + { + String::NoCaseKey ikey(key); + return BaseType::FindAlt(ikey); + } + + // Set just uses a find and assigns value if found. The key is not modified; + // this behavior is identical to Flash string variable assignment. + void SetCaseInsensitive(const String& key, const U& value) + { + base_iterator it = FindCaseInsensitive(key); + if (it != BaseType::End()) + { + it->Second = value; + } + else + { + BaseType::Add(key, value); + } + } +}; + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_String_FormatUtil.cpp b/LibOVR/Src/Kernel/OVR_String_FormatUtil.cpp new file mode 100644 index 0000000..e196dd7 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_String_FormatUtil.cpp @@ -0,0 +1,53 @@ +/************************************************************************************ + +Filename : OVR_String_FormatUtil.cpp +Content : String format functions. +Created : February 27, 2013 +Notes : + +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 "OVR_String.h" +#include "OVR_Log.h" + +namespace OVR { + +void StringBuffer::AppendFormat(const char* format, ...) +{ + va_list argList; + + va_start(argList, format); + UPInt size = OVR_vscprintf(format, argList); + va_end(argList); + + char* buffer = (char*) OVR_ALLOC(sizeof(char) * (size+1)); + + va_start(argList, format); + UPInt result = OVR_vsprintf(buffer, size+1, format, argList); + OVR_UNUSED1(result); + va_end(argList); + OVR_ASSERT_LOG(result == size, ("Error in OVR_vsprintf")); + + AppendString(buffer); + + OVR_FREE(buffer); +} + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_String_PathUtil.cpp b/LibOVR/Src/Kernel/OVR_String_PathUtil.cpp new file mode 100644 index 0000000..02abe15 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_String_PathUtil.cpp @@ -0,0 +1,211 @@ +/************************************************************************************ + +Filename : OVR_String_PathUtil.cpp +Content : String filename/url helper function +Created : September 19, 2012 +Notes : + +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 "OVR_String.h" +#include "OVR_UTF8Util.h" + +namespace OVR { + +//-------------------------------------------------------------------- +// ***** Path-Scanner helper function + +// Scans file path finding filename start and extension start, fills in their addess. +void ScanFilePath(const char* url, const char** pfilename, const char** pext) +{ + const char* urlStart = url; + const char *filename = 0; + const char *lastDot = 0; + + UInt32 charVal = UTF8Util::DecodeNextChar(&url); + + while (charVal != 0) + { + if ((charVal == '/') || (charVal == '\\')) + { + filename = url; + lastDot = 0; + } + else if (charVal == '.') + { + lastDot = url - 1; + } + + charVal = UTF8Util::DecodeNextChar(&url); + } + + if (pfilename) + { + // It was a naked filename + if (urlStart && (*urlStart != '.') && *urlStart) + *pfilename = urlStart; + else + *pfilename = filename; + } + + if (pext) + { + *pext = lastDot; + } +} + +// Scans till the end of protocol. Returns first character past protocol, +// 0 if not found. +// - protocol: 'file://', 'http://' +const char* ScanPathProtocol(const char* url) +{ + UInt32 charVal = UTF8Util::DecodeNextChar(&url); + UInt32 charVal2; + + while (charVal != 0) + { + // Treat a colon followed by a slash as absolute. + if (charVal == ':') + { + charVal2 = UTF8Util::DecodeNextChar(&url); + charVal = UTF8Util::DecodeNextChar(&url); + if ((charVal == '/') && (charVal2 == '\\')) + return url; + } + charVal = UTF8Util::DecodeNextChar(&url); + } + return 0; +} + + +//-------------------------------------------------------------------- +// ***** String Path API implementation + +bool String::HasAbsolutePath(const char* url) +{ + // Absolute paths can star with: + // - protocols: 'file://', 'http://' + // - windows drive: 'c:\' + // - UNC share name: '\\share' + // - unix root '/' + + // On the other hand, relative paths are: + // - directory: 'directory/file' + // - this directory: './file' + // - parent directory: '../file' + // + // For now, we don't parse '.' or '..' out, but instead let it be concatenated + // to string and let the OS figure it out. This, however, is not good for file + // name matching in library/etc, so it should be improved. + + if (!url || !*url) + return true; // Treat empty strings as absolute. + + UInt32 charVal = UTF8Util::DecodeNextChar(&url); + + // Fist character of '/' or '\\' means absolute url. + if ((charVal == '/') || (charVal == '\\')) + return true; + + while (charVal != 0) + { + // Treat a colon followed by a slash as absolute. + if (charVal == ':') + { + charVal = UTF8Util::DecodeNextChar(&url); + // Protocol or windows drive. Absolute. + if ((charVal == '/') || (charVal == '\\')) + return true; + } + else if ((charVal == '/') || (charVal == '\\')) + { + // Not a first character (else 'if' above the loop would have caught it). + // Must be a relative url. + break; + } + + charVal = UTF8Util::DecodeNextChar(&url); + } + + // We get here for relative paths. + return false; +} + + +bool String::HasExtension(const char* path) +{ + const char* ext = 0; + ScanFilePath(path, 0, &ext); + return ext != 0; +} +bool String::HasProtocol(const char* path) +{ + return ScanPathProtocol(path) != 0; +} + + +String String::GetPath() const +{ + const char* filename = 0; + ScanFilePath(ToCStr(), &filename, 0); + + // Technically we can have extra logic somewhere for paths, + // such as enforcing protocol and '/' only based on flags, + // but we keep it simple for now. + return String(ToCStr(), filename ? (filename-ToCStr()) : GetSize()); +} + +String String::GetProtocol() const +{ + const char* protocolEnd = ScanPathProtocol(ToCStr()); + return String(ToCStr(), protocolEnd ? (protocolEnd-ToCStr()) : 0); +} + +String String::GetFilename() const +{ + const char* filename = 0; + ScanFilePath(ToCStr(), &filename, 0); + return String(filename); +} +String String::GetExtension() const +{ + const char* ext = 0; + ScanFilePath(ToCStr(), 0, &ext); + return String(ext); +} + +void String::StripExtension() +{ + const char* ext = 0; + ScanFilePath(ToCStr(), 0, &ext); + if (ext) + { + *this = String(ToCStr(), ext-ToCStr()); + } +} + +void String::StripProtocol() +{ + const char* protocol = ScanPathProtocol(ToCStr()); + if (protocol) + AssignString(protocol, OVR_strlen(protocol)); +} + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_SysFile.cpp b/LibOVR/Src/Kernel/OVR_SysFile.cpp new file mode 100644 index 0000000..604527a --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_SysFile.cpp @@ -0,0 +1,138 @@ +/************************************************************************** + +Filename : OVR_SysFile.cpp +Content : File wrapper class implementation (Win32) + +Created : April 5, 1999 +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. + +**************************************************************************/ + +#define GFILE_CXX + +// Standard C library (Captain Obvious guarantees!) +#include <stdio.h> + +#include "OVR_SysFile.h" +#include "OVR_Log.h" + +namespace OVR { + +// This is - a dummy file that fails on all calls. + +class UnopenedFile : public File +{ +public: + UnopenedFile() { } + ~UnopenedFile() { } + + virtual const char* GetFilePath() { return 0; } + + // ** File Information + virtual bool IsValid() { return 0; } + virtual bool IsWritable() { return 0; } + + // Return position / file size + virtual int Tell() { return 0; } + virtual SInt64 LTell() { return 0; } + virtual int GetLength() { return 0; } + virtual SInt64 LGetLength() { return 0; } + +// virtual bool Stat(FileStats *pfs) { return 0; } + virtual int GetErrorCode() { return Error_FileNotFound; } + + // ** Stream implementation & I/O + virtual int Write(const UByte *pbuffer, int numBytes) { return -1; OVR_UNUSED2(pbuffer, numBytes); } + virtual int Read(UByte *pbuffer, int numBytes) { return -1; OVR_UNUSED2(pbuffer, numBytes); } + virtual int SkipBytes(int numBytes) { return 0; OVR_UNUSED(numBytes); } + virtual int BytesAvailable() { return 0; } + virtual bool Flush() { return 0; } + virtual int Seek(int offset, int origin) { return -1; OVR_UNUSED2(offset, origin); } + virtual SInt64 LSeek(SInt64 offset, int origin) { return -1; OVR_UNUSED2(offset, origin); } + + virtual int CopyFromStream(File *pstream, int byteSize) { return -1; OVR_UNUSED2(pstream, byteSize); } + virtual bool Close() { return 0; } +}; + + + +// ***** System File + +// System file is created to access objects on file system directly +// This file can refer directly to path + +// ** Constructor +SysFile::SysFile() : DelegatedFile(0) +{ + pFile = *new UnopenedFile; +} + +Ptr<File> FileFILEOpen(const String& path, int flags, int mode); + +// Opens a file +SysFile::SysFile(const String& path, int flags, int mode) : DelegatedFile(0) +{ + Open(path, flags, mode); +} + + +// ** Open & management +// Will fail if file's already open +bool SysFile::Open(const String& path, int flags, int mode) +{ + pFile = FileFILEOpen(path, flags, mode); + if ((!pFile) || (!pFile->IsValid())) + { + pFile = *new UnopenedFile; + OVR_DEBUG_LOG(("Failed to open file: %s", path.ToCStr())); + return 0; + } + //pFile = *OVR_NEW DelegatedFile(pFile); // MA Testing + if (flags & Open_Buffered) + pFile = *new BufferedFile(pFile); + return 1; +} + + +// ** Overrides + +int SysFile::GetErrorCode() +{ + return pFile ? pFile->GetErrorCode() : Error_FileNotFound; +} + + +// Overrides to provide re-open support +bool SysFile::IsValid() +{ + return pFile && pFile->IsValid(); +} +bool SysFile::Close() +{ + if (IsValid()) + { + DelegatedFile::Close(); + pFile = *new UnopenedFile; + return 1; + } + return 0; +} + +} // OVR diff --git a/LibOVR/Src/Kernel/OVR_SysFile.h b/LibOVR/Src/Kernel/OVR_SysFile.h new file mode 100644 index 0000000..61ad6e8 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_SysFile.h @@ -0,0 +1,104 @@ +/************************************************************************************ + +PublicHeader: Kernel +Filename : OVR_SysFile.h +Content : Header for all internal file management - functions and structures + to be inherited by OS specific subclasses. +Created : September 19, 2012 +Notes : + +Notes : errno may not be preserved across use of GBaseFile member functions + : Directories cannot be deleted while files opened from them are in use + (For the GetFullName function) + +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. + +************************************************************************************/ + +#ifndef OVR_SysFile_h +#define OVR_SysFile_h + +#include "OVR_File.h" + +namespace OVR { + +// ***** Declared classes +class SysFile; + +//----------------------------------------------------------------------------------- +// *** File Statistics + +// This class contents are similar to _stat, providing +// creation, modify and other information about the file. +struct FileStat +{ + // No change or create time because they are not available on most systems + SInt64 ModifyTime; + SInt64 AccessTime; + SInt64 FileSize; + + bool operator== (const FileStat& stat) const + { + return ( (ModifyTime == stat.ModifyTime) && + (AccessTime == stat.AccessTime) && + (FileSize == stat.FileSize) ); + } +}; + +//----------------------------------------------------------------------------------- +// *** System File + +// System file is created to access objects on file system directly +// This file can refer directly to path. +// System file can be open & closed several times; however, such use is not recommended +// This class is realy a wrapper around an implementation of File interface for a +// particular platform. + +class SysFile : public DelegatedFile +{ +protected: + SysFile(const SysFile &source) : DelegatedFile () { OVR_UNUSED(source); } +public: + + // ** Constructor + SysFile(); + // Opens a file + SysFile(const String& path, int flags = Open_Read|Open_Buffered, int mode = Mode_ReadWrite); + + // ** Open & management + bool Open(const String& path, int flags = Open_Read|Open_Buffered, int mode = Mode_ReadWrite); + + OVR_FORCE_INLINE bool Create(const String& path, int mode = Mode_ReadWrite) + { return Open(path, Open_ReadWrite|Open_Create, mode); } + + // Helper function: obtain file statistics information. In OVR, this is used to detect file changes. + // Return 0 if function failed, most likely because the file doesn't exist. + static bool OVR_CDECL GetFileStat(FileStat* pfileStats, const String& path); + + // ** Overrides + // Overridden to provide re-open support + virtual int GetErrorCode(); + + virtual bool IsValid(); + + virtual bool Close(); +}; + +} // Namespace OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_System.cpp b/LibOVR/Src/Kernel/OVR_System.cpp new file mode 100644 index 0000000..3144ade --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_System.cpp @@ -0,0 +1,81 @@ +/************************************************************************************ + +Filename : OVR_System.cpp +Content : General kernel initialization/cleanup, including that + of the memory allocator. +Created : September 19, 2012 +Notes : + +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 "OVR_System.h" +#include "OVR_Threads.h" +#include "OVR_Timer.h" + +namespace OVR { + +// ***** OVR::System Implementation + +// Initializes System core, installing allocator. +void System::Init(Log* log, Allocator *palloc) +{ + if (!Allocator::GetInstance()) + { + Log::SetGlobalLog(log); + Timer::initializeTimerSystem(); + Allocator::setInstance(palloc); + } + else + { + OVR_DEBUG_LOG(("System::Init failed - duplicate call.")); + } +} + +void System::Destroy() +{ + if (Allocator::GetInstance()) + { + // Wait for all threads to finish; this must be done so that memory + // allocator and all destructors finalize correctly. +#ifdef OVR_ENABLE_THREADS + Thread::FinishAllThreads(); +#endif + + // Shutdown heap and destroy SysAlloc singleton, if any. + Allocator::GetInstance()->onSystemShutdown(); + Allocator::setInstance(0); + + Timer::shutdownTimerSystem(); + Log::SetGlobalLog(Log::GetDefaultLog()); + } + else + { + OVR_DEBUG_LOG(("System::Destroy failed - System not initialized.")); + } +} + +// Returns 'true' if system was properly initialized. +bool System::IsInitialized() +{ + return Allocator::GetInstance() != 0; +} + +} // OVR + diff --git a/LibOVR/Src/Kernel/OVR_System.h b/LibOVR/Src/Kernel/OVR_System.h new file mode 100644 index 0000000..253fe19 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_System.h @@ -0,0 +1,78 @@ +/************************************************************************************ + +PublicHeader: OVR +Filename : OVR_System.h +Content : General kernel initialization/cleanup, including that + of the memory allocator. +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_System_h +#define OVR_System_h + +#include "OVR_Allocator.h" +#include "OVR_Log.h" + +namespace OVR { + +// ***** System Core Initialization class + +// System initialization must take place before any other OVR_Kernel objects are used; +// this is done my calling System::Init(). Among other things, this is necessary to +// initialize the memory allocator. Similarly, System::Destroy must be +// called before program exist for proper cleanup. Both of these tasks can be achieved by +// simply creating System object first, allowing its constructor/destructor do the work. + +// TBD: Require additional System class for Oculus Rift API? + +class System +{ +public: + + // System constructor expects allocator to be specified, if it is being substituted. + System(Log* log = Log::ConfigureDefaultLog(LogMask_Debug), + Allocator* palloc = DefaultAllocator::InitSystemSingleton()) + { + Init(log, palloc); + } + + ~System() + { + Destroy(); + } + + // Returns 'true' if system was properly initialized. + static bool OVR_CDECL IsInitialized(); + + // Initializes System core. Users can override memory implementation by passing + // a different Allocator here. + static void OVR_CDECL Init(Log* log = Log::ConfigureDefaultLog(LogMask_Debug), + Allocator *palloc = DefaultAllocator::InitSystemSingleton()); + + // De-initializes System more, finalizing the threading system and destroying + // the global memory allocator. + static void OVR_CDECL Destroy(); +}; + +} // OVR + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Threads.h b/LibOVR/Src/Kernel/OVR_Threads.h new file mode 100644 index 0000000..307f107 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Threads.h @@ -0,0 +1,407 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_Threads.h +Content : Contains thread-related (safe) functionality +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ +#ifndef OVR_Threads_h +#define OVR_Threads_h + +#include "OVR_Types.h" +#include "OVR_Atomic.h" +#include "OVR_RefCount.h" +#include "OVR_Array.h" + +// Defines the infinite wait delay timeout +#define OVR_WAIT_INFINITE 0xFFFFFFFF + +// To be defined in the project configuration options +#ifdef OVR_ENABLE_THREADS + + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ****** Declared classes + +// Declared with thread support only +class Mutex; +class WaitCondition; +class Event; +// Implementation forward declarations +class MutexImpl; +class WaitConditionImpl; + + + +//----------------------------------------------------------------------------------- +// ***** Mutex + +// Mutex class represents a system Mutex synchronization object that provides access +// serialization between different threads, allowing one thread mutually exclusive access +// to a resource. Mutex is more heavy-weight then Lock, but supports WaitCondition. + +class Mutex +{ + friend class WaitConditionImpl; + friend class MutexImpl; + + MutexImpl *pImpl; + +public: + // Constructor/destructor + Mutex(bool recursive = 1); + ~Mutex(); + + // Locking functions + void DoLock(); + bool TryLock(); + void Unlock(); + + // Returns 1 if the mutes is currently locked by another thread + // Returns 0 if the mutex is not locked by another thread, and can therefore be acquired. + bool IsLockedByAnotherThread(); + + // Locker class; Used for automatic locking of a mutex withing scope + class Locker + { + public: + Mutex *pMutex; + Locker(Mutex *pmutex) + { pMutex = pmutex; pMutex->DoLock(); } + ~Locker() + { pMutex->Unlock(); } + }; +}; + + +//----------------------------------------------------------------------------------- +// ***** WaitCondition + +/* + WaitCondition is a synchronization primitive that can be used to implement what is known as a monitor. + Dependent threads wait on a wait condition by calling Wait(), and get woken up by other threads that + call Notify() or NotifyAll(). + + The unique feature of this class is that it provides an atomic way of first releasing a Mutex, and then + starting a wait on a wait condition. If both the mutex and the wait condition are associated with the same + resource, this ensures that any condition checked for while the mutex was locked does not change before + the wait on the condition is actually initiated. +*/ + +class WaitCondition +{ + friend class WaitConditionImpl; + // Internal implementation structure + WaitConditionImpl *pImpl; + +public: + // Constructor/destructor + WaitCondition(); + ~WaitCondition(); + + // Release mutex and wait for condition. The mutex is re-aquired after the wait. + // Delay is specified in milliseconds (1/1000 of a second). + bool Wait(Mutex *pmutex, unsigned delay = OVR_WAIT_INFINITE); + + // Notify a condition, releasing at one object waiting + void Notify(); + // Notify a condition, releasing all objects waiting + void NotifyAll(); +}; + + +//----------------------------------------------------------------------------------- +// ***** Event + +// Event is a wait-able synchronization object similar to Windows event. +// Event can be waited on until it's signaled by another thread calling +// either SetEvent or PulseEvent. + +class Event +{ + // Event state, its mutex and the wait condition + volatile bool State; + volatile bool Temporary; + mutable Mutex StateMutex; + WaitCondition StateWaitCondition; + + void updateState(bool newState, bool newTemp, bool mustNotify); + +public: + Event(bool setInitially = 0) : State(setInitially), Temporary(false) { } + ~Event() { } + + // Wait on an event condition until it is set + // Delay is specified in milliseconds (1/1000 of a second). + bool Wait(unsigned delay = OVR_WAIT_INFINITE); + + // Set an event, releasing objects waiting on it + void SetEvent() + { updateState(true, false, true); } + + // Reset an event, un-signaling it + void ResetEvent() + { updateState(false, false, false); } + + // Set and then reset an event once a waiter is released. + // If threads are already waiting, they will be notified and released + // If threads are not waiting, the event is set until the first thread comes in + void PulseEvent() + { updateState(true, true, true); } +}; + + +//----------------------------------------------------------------------------------- +// ***** Thread class + +// ThreadId uniquely identifies a thread; returned by GetCurrentThreadId() and +// Thread::GetThreadId. +typedef void* ThreadId; + + +// *** Thread flags + +// Indicates that the thread is has been started, i.e. Start method has been called, and threads +// OnExit() method has not yet been called/returned. +#define OVR_THREAD_STARTED 0x01 +// This flag is set once the thread has ran, and finished. +#define OVR_THREAD_FINISHED 0x02 +// This flag is set temporarily if this thread was started suspended. It is used internally. +#define OVR_THREAD_START_SUSPENDED 0x08 +// This flag is used to ask a thread to exit. Message driven threads will usually check this flag +// and finish once it is set. +#define OVR_THREAD_EXIT 0x10 + + +class Thread : public RefCountBase<Thread> +{ // NOTE: Waitable must be the first base since it implements RefCountImpl. + +public: + + // *** Callback functions, can be used instead of overriding Run + + // Run function prototypes. + // Thread function and user handle passed to it, executed by the default + // Thread::Run implementation if not null. + typedef int (*ThreadFn)(Thread *pthread, void* h); + + // Thread ThreadFunction1 is executed if not 0, otherwise ThreadFunction2 is tried + ThreadFn ThreadFunction; + // User handle passes to a thread + void* UserHandle; + + // Thread state to start a thread with + enum ThreadState + { + NotRunning = 0, + Running = 1, + Suspended = 2 + }; + + // Thread priority + enum ThreadPriority + { + CriticalPriority, + HighestPriority, + AboveNormalPriority, + NormalPriority, + BelowNormalPriority, + LowestPriority, + IdlePriority, + }; + + // Thread constructor parameters + struct CreateParams + { + CreateParams(ThreadFn func = 0, void* hand = 0, UPInt ssize = 128 * 1024, + int proc = -1, ThreadState state = NotRunning, ThreadPriority prior = NormalPriority) + : threadFunction(func), userHandle(hand), stackSize(ssize), + processor(proc), initialState(state), priority(prior) {} + ThreadFn threadFunction; // Thread function + void* userHandle; // User handle passes to a thread + UPInt stackSize; // Thread stack size + int processor; // Thread hardware processor + ThreadState initialState; // + ThreadPriority priority; // Thread priority + }; + + // *** Constructors + + // A default constructor always creates a thread in NotRunning state, because + // the derived class has not yet been initialized. The derived class can call Start explicitly. + // "processor" parameter specifies which hardware processor this thread will be run on. + // -1 means OS decides this. Implemented only on Win32 + Thread(UPInt stackSize = 128 * 1024, int processor = -1); + // Constructors that initialize the thread with a pointer to function. + // An option to start a thread is available, but it should not be used if classes are derived from Thread. + // "processor" parameter specifies which hardware processor this thread will be run on. + // -1 means OS decides this. Implemented only on Win32 + Thread(ThreadFn threadFunction, void* userHandle = 0, UPInt stackSize = 128 * 1024, + int processor = -1, ThreadState initialState = NotRunning); + // Constructors that initialize the thread with a create parameters structure. + explicit Thread(const CreateParams& params); + + // Destructor. + virtual ~Thread(); + + // Waits for all Threads to finish; should be called only from the root + // application thread. Once this function returns, we know that all other + // thread's references to Thread object have been released. + static void OVR_CDECL FinishAllThreads(); + + + // *** Overridable Run function for thread processing + + // - returning from this method will end the execution of the thread + // - return value is usually 0 for success + virtual int Run(); + // Called after return/exit function + virtual void OnExit(); + + + // *** Thread management + + // Starts the thread if its not already running + // - internally sets up the threading and calls Run() + // - initial state can either be Running or Suspended, NotRunning will just fail and do nothing + // - returns the exit code + virtual bool Start(ThreadState initialState = Running); + + // Quits with an exit code + virtual void Exit(int exitCode=0); + + // Suspend the thread until resumed + // Returns 1 for success, 0 for failure. + bool Suspend(); + // Resumes currently suspended thread + // Returns 1 for success, 0 for failure. + bool Resume(); + + // Static function to return a pointer to the current thread + //static Thread* GetThread(); + + + // *** Thread status query functions + + bool GetExitFlag() const; + void SetExitFlag(bool exitFlag); + + // Determines whether the thread was running and is now finished + bool IsFinished() const; + // Determines if the thread is currently suspended + bool IsSuspended() const; + // Returns current thread state + ThreadState GetThreadState() const; + + // Returns the number of available CPUs on the system + static int GetCPUCount(); + + // Returns the thread exit code. Exit code is initialized to 0, + // and set to the return value if Run function after the thread is finished. + inline int GetExitCode() const { return ExitCode; } + // Returns an OS handle +#if defined(OVR_OS_WIN32) + void* GetOSHandle() const { return ThreadHandle; } +#else + pthread_t GetOSHandle() const { return ThreadHandle; } +#endif + +#if defined(OVR_OS_WIN32) + ThreadId GetThreadId() const { return IdValue; } +#else + ThreadId GetThreadId() const { return (ThreadId)GetOSHandle(); } +#endif + + static int GetOSPriority(ThreadPriority); + // *** Sleep + + // Sleep secs seconds + static bool Sleep(unsigned secs); + // Sleep msecs milliseconds + static bool MSleep(unsigned msecs); + + + // *** Debugging functionality +#if defined(OVR_OS_WIN32) + virtual void SetThreadName( const char* name ); +#else + virtual void SetThreadName( const char* name ) { OVR_UNUSED(name); } +#endif + +private: +#if defined(OVR_OS_WIN32) + friend unsigned WINAPI Thread_Win32StartFn(void *pthread); + +#else + friend void *Thread_PthreadStartFn(void * phandle); + + static int InitAttr; + static pthread_attr_t Attr; +#endif + +protected: + // Thread state flags + AtomicInt<UInt32> ThreadFlags; + AtomicInt<SInt32> SuspendCount; + UPInt StackSize; + + // Hardware processor which this thread is running on. + int Processor; + ThreadPriority Priority; + +#if defined(OVR_OS_WIN32) + void* ThreadHandle; + volatile ThreadId IdValue; + + // System-specific cleanup function called from destructor + void CleanupSystemThread(); + +#else + pthread_t ThreadHandle; +#endif + + // Exit code of the thread, as returned by Run. + int ExitCode; + + // Internal run function. + int PRun(); + // Finishes the thread and releases internal reference to it. + void FinishAndRelease(); + + void Init(const CreateParams& params); + + // Protected copy constructor + Thread(const Thread &source) : RefCountBase<Thread>() { OVR_UNUSED(source); } + +}; + +// Returns the unique Id of a thread it is called on, intended for +// comparison purposes. +ThreadId GetCurrentThreadId(); + + +} // OVR + +#endif // OVR_ENABLE_THREADS +#endif // OVR_Threads_h diff --git a/LibOVR/Src/Kernel/OVR_ThreadsPthread.cpp b/LibOVR/Src/Kernel/OVR_ThreadsPthread.cpp new file mode 100644 index 0000000..da483d5 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_ThreadsPthread.cpp @@ -0,0 +1,787 @@ +/************************************************************************************ + +Filename : OVR_ThreadsPthread.cpp +Content : +Created : +Notes : + +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 "OVR_Threads.h" +#include "OVR_Hash.h" + +#ifdef OVR_ENABLE_THREADS + +#include "OVR_Timer.h" +#include "OVR_Log.h" + +#include <pthread.h> +#include <time.h> +#include <unistd.h> +#include <sys/time.h> +#include <errno.h> + + +namespace OVR { + +// ***** Mutex implementation + + +// *** Internal Mutex implementation structure + +class MutexImpl : public NewOverrideBase +{ + // System mutex or semaphore + pthread_mutex_t SMutex; + bool Recursive; + unsigned LockCount; + pthread_t LockedBy; + + friend class WaitConditionImpl; + +public: + // Constructor/destructor + MutexImpl(Mutex* pmutex, bool recursive = 1); + ~MutexImpl(); + + // Locking functions + void DoLock(); + bool TryLock(); + void Unlock(Mutex* pmutex); + // Returns 1 if the mutes is currently locked + bool IsLockedByAnotherThread(Mutex* pmutex); + bool IsSignaled() const; +}; + +pthread_mutexattr_t Lock::RecursiveAttr; +bool Lock::RecursiveAttrInit = 0; + +// *** Constructor/destructor +MutexImpl::MutexImpl(Mutex* pmutex, bool recursive) +{ + OVR_UNUSED(pmutex); + Recursive = recursive; + LockCount = 0; + + if (Recursive) + { + if (!Lock::RecursiveAttrInit) + { + pthread_mutexattr_init(&Lock::RecursiveAttr); + pthread_mutexattr_settype(&Lock::RecursiveAttr, PTHREAD_MUTEX_RECURSIVE); + Lock::RecursiveAttrInit = 1; + } + + pthread_mutex_init(&SMutex, &Lock::RecursiveAttr); + } + else + pthread_mutex_init(&SMutex, 0); +} + +MutexImpl::~MutexImpl() +{ + pthread_mutex_destroy(&SMutex); +} + + +// Lock and try lock +void MutexImpl::DoLock() +{ + while (pthread_mutex_lock(&SMutex)) + ; + LockCount++; + LockedBy = pthread_self(); +} + +bool MutexImpl::TryLock() +{ + if (!pthread_mutex_trylock(&SMutex)) + { + LockCount++; + LockedBy = pthread_self(); + return 1; + } + + return 0; +} + +void MutexImpl::Unlock(Mutex* pmutex) +{ + OVR_UNUSED(pmutex); + OVR_ASSERT(pthread_self() == LockedBy && LockCount > 0); + + unsigned lockCount; + LockCount--; + lockCount = LockCount; + + pthread_mutex_unlock(&SMutex); +} + +bool MutexImpl::IsLockedByAnotherThread(Mutex* pmutex) +{ + OVR_UNUSED(pmutex); + // There could be multiple interpretations of IsLocked with respect to current thread + if (LockCount == 0) + return 0; + if (pthread_self() != LockedBy) + return 1; + return 0; +} + +bool MutexImpl::IsSignaled() const +{ + // An mutex is signaled if it is not locked ANYWHERE + // Note that this is different from IsLockedByAnotherThread function, + // that takes current thread into account + return LockCount == 0; +} + + +// *** Actual Mutex class implementation + +Mutex::Mutex(bool recursive) +{ + // NOTE: RefCount mode already thread-safe for all waitables. + pImpl = new MutexImpl(this, recursive); +} + +Mutex::~Mutex() +{ + delete pImpl; +} + +// Lock and try lock +void Mutex::DoLock() +{ + pImpl->DoLock(); +} +bool Mutex::TryLock() +{ + return pImpl->TryLock(); +} +void Mutex::Unlock() +{ + pImpl->Unlock(this); +} +bool Mutex::IsLockedByAnotherThread() +{ + return pImpl->IsLockedByAnotherThread(this); +} + + + +//----------------------------------------------------------------------------------- +// ***** Event + +bool Event::Wait(unsigned delay) +{ + Mutex::Locker lock(&StateMutex); + + // Do the correct amount of waiting + if (delay == OVR_WAIT_INFINITE) + { + while(!State) + StateWaitCondition.Wait(&StateMutex); + } + else if (delay) + { + if (!State) + StateWaitCondition.Wait(&StateMutex, delay); + } + + bool state = State; + // Take care of temporary 'pulsing' of a state + if (Temporary) + { + Temporary = false; + State = false; + } + return state; +} + +void Event::updateState(bool newState, bool newTemp, bool mustNotify) +{ + Mutex::Locker lock(&StateMutex); + State = newState; + Temporary = newTemp; + if (mustNotify) + StateWaitCondition.NotifyAll(); +} + + + +// ***** Wait Condition Implementation + +// Internal implementation class +class WaitConditionImpl : public NewOverrideBase +{ + pthread_mutex_t SMutex; + pthread_cond_t Condv; + +public: + + // Constructor/destructor + WaitConditionImpl(); + ~WaitConditionImpl(); + + // Release mutex and wait for condition. The mutex is re-aqured after the wait. + bool Wait(Mutex *pmutex, unsigned delay = OVR_WAIT_INFINITE); + + // Notify a condition, releasing at one object waiting + void Notify(); + // Notify a condition, releasing all objects waiting + void NotifyAll(); +}; + + +WaitConditionImpl::WaitConditionImpl() +{ + pthread_mutex_init(&SMutex, 0); + pthread_cond_init(&Condv, 0); +} + +WaitConditionImpl::~WaitConditionImpl() +{ + pthread_mutex_destroy(&SMutex); + pthread_cond_destroy(&Condv); +} + +bool WaitConditionImpl::Wait(Mutex *pmutex, unsigned delay) +{ + bool result = 1; + unsigned lockCount = pmutex->pImpl->LockCount; + + // Mutex must have been locked + if (lockCount == 0) + return 0; + + pthread_mutex_lock(&SMutex); + + // Finally, release a mutex or semaphore + if (pmutex->pImpl->Recursive) + { + // Release the recursive mutex N times + pmutex->pImpl->LockCount = 0; + for(unsigned i=0; i<lockCount; i++) + pthread_mutex_unlock(&pmutex->pImpl->SMutex); + } + else + { + pmutex->pImpl->LockCount = 0; + pthread_mutex_unlock(&pmutex->pImpl->SMutex); + } + + // Note that there is a gap here between mutex.Unlock() and Wait(). + // The other mutex protects this gap. + + if (delay == OVR_WAIT_INFINITE) + pthread_cond_wait(&Condv,&SMutex); + else + { + timespec ts; + + struct timeval tv; + gettimeofday(&tv, 0); + + ts.tv_sec = tv.tv_sec + (delay / 1000); + ts.tv_nsec = (tv.tv_usec + (delay % 1000) * 1000) * 1000; + + if (ts.tv_nsec > 999999999) + { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + int r = pthread_cond_timedwait(&Condv,&SMutex, &ts); + OVR_ASSERT(r == 0 || r == ETIMEDOUT); + if (r) + result = 0; + } + + pthread_mutex_unlock(&SMutex); + + // Re-aquire the mutex + for(unsigned i=0; i<lockCount; i++) + pmutex->DoLock(); + + // Return the result + return result; +} + +// Notify a condition, releasing the least object in a queue +void WaitConditionImpl::Notify() +{ + pthread_mutex_lock(&SMutex); + pthread_cond_signal(&Condv); + pthread_mutex_unlock(&SMutex); +} + +// Notify a condition, releasing all objects waiting +void WaitConditionImpl::NotifyAll() +{ + pthread_mutex_lock(&SMutex); + pthread_cond_broadcast(&Condv); + pthread_mutex_unlock(&SMutex); +} + + + +// *** Actual implementation of WaitCondition + +WaitCondition::WaitCondition() +{ + pImpl = new WaitConditionImpl; +} +WaitCondition::~WaitCondition() +{ + delete pImpl; +} + +bool WaitCondition::Wait(Mutex *pmutex, unsigned delay) +{ + return pImpl->Wait(pmutex, delay); +} +// Notification +void WaitCondition::Notify() +{ + pImpl->Notify(); +} +void WaitCondition::NotifyAll() +{ + pImpl->NotifyAll(); +} + + +// ***** Current thread + +// Per-thread variable +/* +static __thread Thread* pCurrentThread = 0; + +// Static function to return a pointer to the current thread +void Thread::InitCurrentThread(Thread *pthread) +{ + pCurrentThread = pthread; +} + +// Static function to return a pointer to the current thread +Thread* Thread::GetThread() +{ + return pCurrentThread; +} +*/ + + +// *** Thread constructors. + +Thread::Thread(UPInt stackSize, int processor) +{ + // NOTE: RefCount mode already thread-safe for all Waitable objects. + CreateParams params; + params.stackSize = stackSize; + params.processor = processor; + Init(params); +} + +Thread::Thread(Thread::ThreadFn threadFunction, void* userHandle, UPInt stackSize, + int processor, Thread::ThreadState initialState) +{ + CreateParams params(threadFunction, userHandle, stackSize, processor, initialState); + Init(params); +} + +Thread::Thread(const CreateParams& params) +{ + Init(params); +} + +void Thread::Init(const CreateParams& params) +{ + // Clear the variables + ThreadFlags = 0; + ThreadHandle = 0; + ExitCode = 0; + SuspendCount = 0; + StackSize = params.stackSize; + Processor = params.processor; + Priority = params.priority; + + // Clear Function pointers + ThreadFunction = params.threadFunction; + UserHandle = params.userHandle; + if (params.initialState != NotRunning) + Start(params.initialState); +} + +Thread::~Thread() +{ + // Thread should not running while object is being destroyed, + // this would indicate ref-counting issue. + //OVR_ASSERT(IsRunning() == 0); + + // Clean up thread. + ThreadHandle = 0; +} + + + +// *** Overridable User functions. + +// Default Run implementation +int Thread::Run() +{ + // Call pointer to function, if available. + return (ThreadFunction) ? ThreadFunction(this, UserHandle) : 0; +} +void Thread::OnExit() +{ +} + + +// Finishes the thread and releases internal reference to it. +void Thread::FinishAndRelease() +{ + // Note: thread must be US. + ThreadFlags &= (UInt32)~(OVR_THREAD_STARTED); + ThreadFlags |= OVR_THREAD_FINISHED; + + // Release our reference; this is equivalent to 'delete this' + // from the point of view of our thread. + Release(); +} + + + +// *** ThreadList - used to track all created threads + +class ThreadList : public NewOverrideBase +{ + //------------------------------------------------------------------------ + struct ThreadHashOp + { + size_t operator()(const Thread* ptr) + { + return (((size_t)ptr) >> 6) ^ (size_t)ptr; + } + }; + + HashSet<Thread*, ThreadHashOp> ThreadSet; + Mutex ThreadMutex; + WaitCondition ThreadsEmpty; + // Track the root thread that created us. + pthread_t RootThreadId; + + static ThreadList* volatile pRunningThreads; + + void addThread(Thread *pthread) + { + Mutex::Locker lock(&ThreadMutex); + ThreadSet.Add(pthread); + } + + void removeThread(Thread *pthread) + { + Mutex::Locker lock(&ThreadMutex); + ThreadSet.Remove(pthread); + if (ThreadSet.GetSize() == 0) + ThreadsEmpty.Notify(); + } + + void finishAllThreads() + { + // Only original root thread can call this. + OVR_ASSERT(pthread_self() == RootThreadId); + + Mutex::Locker lock(&ThreadMutex); + while (ThreadSet.GetSize() != 0) + ThreadsEmpty.Wait(&ThreadMutex); + } + +public: + + ThreadList() + { + RootThreadId = pthread_self(); + } + ~ThreadList() { } + + + static void AddRunningThread(Thread *pthread) + { + // Non-atomic creation ok since only the root thread + if (!pRunningThreads) + { + pRunningThreads = new ThreadList; + OVR_ASSERT(pRunningThreads); + } + pRunningThreads->addThread(pthread); + } + + // NOTE: 'pthread' might be a dead pointer when this is + // called so it should not be accessed; it is only used + // for removal. + static void RemoveRunningThread(Thread *pthread) + { + OVR_ASSERT(pRunningThreads); + pRunningThreads->removeThread(pthread); + } + + static void FinishAllThreads() + { + // This is ok because only root thread can wait for other thread finish. + if (pRunningThreads) + { + pRunningThreads->finishAllThreads(); + delete pRunningThreads; + pRunningThreads = 0; + } + } +}; + +// By default, we have no thread list. +ThreadList* volatile ThreadList::pRunningThreads = 0; + + +// FinishAllThreads - exposed publicly in Thread. +void Thread::FinishAllThreads() +{ + ThreadList::FinishAllThreads(); +} + +// *** Run override + +int Thread::PRun() +{ + // Suspend us on start, if requested + if (ThreadFlags & OVR_THREAD_START_SUSPENDED) + { + Suspend(); + ThreadFlags &= (UInt32)~OVR_THREAD_START_SUSPENDED; + } + + // Call the virtual run function + ExitCode = Run(); + return ExitCode; +} + + + + +// *** User overridables + +bool Thread::GetExitFlag() const +{ + return (ThreadFlags & OVR_THREAD_EXIT) != 0; +} + +void Thread::SetExitFlag(bool exitFlag) +{ + // The below is atomic since ThreadFlags is AtomicInt. + if (exitFlag) + ThreadFlags |= OVR_THREAD_EXIT; + else + ThreadFlags &= (UInt32) ~OVR_THREAD_EXIT; +} + + +// Determines whether the thread was running and is now finished +bool Thread::IsFinished() const +{ + return (ThreadFlags & OVR_THREAD_FINISHED) != 0; +} +// Determines whether the thread is suspended +bool Thread::IsSuspended() const +{ + return SuspendCount > 0; +} +// Returns current thread state +Thread::ThreadState Thread::GetThreadState() const +{ + if (IsSuspended()) + return Suspended; + if (ThreadFlags & OVR_THREAD_STARTED) + return Running; + return NotRunning; +} +/* +static const char* mapsched_policy(int policy) +{ + switch(policy) + { + case SCHED_OTHER: + return "SCHED_OTHER"; + case SCHED_RR: + return "SCHED_RR"; + case SCHED_FIFO: + return "SCHED_FIFO"; + + } + return "UNKNOWN"; +} + int policy; + sched_param sparam; + pthread_getschedparam(pthread_self(), &policy, &sparam); + int max_prior = sched_get_priority_max(policy); + int min_prior = sched_get_priority_min(policy); + printf(" !!!! policy: %s, priority: %d, max priority: %d, min priority: %d\n", mapsched_policy(policy), sparam.sched_priority, max_prior, min_prior); +#include <stdio.h> +*/ +// ***** Thread management + +// The actual first function called on thread start +void* Thread_PthreadStartFn(void* phandle) +{ + Thread* pthread = (Thread*)phandle; + int result = pthread->PRun(); + // Signal the thread as done and release it atomically. + pthread->FinishAndRelease(); + // At this point Thread object might be dead; however we can still pass + // it to RemoveRunningThread since it is only used as a key there. + ThreadList::RemoveRunningThread(pthread); + return reinterpret_cast<void*>(result); +} + +int Thread::InitAttr = 0; +pthread_attr_t Thread::Attr; + +/* static */ +int Thread::GetOSPriority(ThreadPriority p) +//static inline int MapToSystemPrority(Thread::ThreadPriority p) +{ + OVR_UNUSED(p); + return -1; +} + +bool Thread::Start(ThreadState initialState) +{ + if (initialState == NotRunning) + return 0; + if (GetThreadState() != NotRunning) + { + OVR_DEBUG_LOG(("Thread::Start failed - thread %p already running", this)); + return 0; + } + + if (!InitAttr) + { + pthread_attr_init(&Attr); + pthread_attr_setdetachstate(&Attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setstacksize(&Attr, 128 * 1024); + sched_param sparam; + sparam.sched_priority = Thread::GetOSPriority(NormalPriority); + pthread_attr_setschedparam(&Attr, &sparam); + InitAttr = 1; + } + + ExitCode = 0; + SuspendCount = 0; + ThreadFlags = (initialState == Running) ? 0 : OVR_THREAD_START_SUSPENDED; + + // AddRef to us until the thread is finished + AddRef(); + ThreadList::AddRunningThread(this); + + int result; + if (StackSize != 128 * 1024 || Priority != NormalPriority) + { + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setstacksize(&attr, StackSize); + sched_param sparam; + sparam.sched_priority = Thread::GetOSPriority(Priority); + pthread_attr_setschedparam(&attr, &sparam); + result = pthread_create(&ThreadHandle, &attr, Thread_PthreadStartFn, this); + pthread_attr_destroy(&attr); + } + else + result = pthread_create(&ThreadHandle, &Attr, Thread_PthreadStartFn, this); + + if (result) + { + ThreadFlags = 0; + Release(); + ThreadList::RemoveRunningThread(this); + return 0; + } + return 1; +} + + +// Suspend the thread until resumed +bool Thread::Suspend() +{ + OVR_DEBUG_LOG(("Thread::Suspend - cannot suspend threads on this system")); + return 0; +} + +// Resumes currently suspended thread +bool Thread::Resume() +{ + return 0; +} + + +// Quits with an exit code +void Thread::Exit(int exitCode) +{ + // Can only exist the current thread + // if (GetThread() != this) + // return; + + // Call the virtual OnExit function + OnExit(); + + // Signal this thread object as done and release it's references. + FinishAndRelease(); + ThreadList::RemoveRunningThread(this); + + pthread_exit(reinterpret_cast<void*>(exitCode)); +} + +ThreadId GetCurrentThreadId() +{ + return (void*)pthread_self(); +} + +// *** Sleep functions + +/* static */ +bool Thread::Sleep(unsigned secs) +{ + sleep(secs); + return 1; +} +/* static */ +bool Thread::MSleep(unsigned msecs) +{ + usleep(msecs*1000); + return 1; +} + +/* static */ +int Thread::GetCPUCount() +{ + return 1; +} + +} + +#endif // OVR_ENABLE_THREADS diff --git a/LibOVR/Src/Kernel/OVR_Timer.cpp b/LibOVR/Src/Kernel/OVR_Timer.cpp new file mode 100644 index 0000000..a8de47d --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Timer.cpp @@ -0,0 +1,287 @@ +/************************************************************************************ + +Filename : OVR_Timer.cpp +Content : Provides static functions for precise timing +Created : September 19, 2012 +Notes : + +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 "OVR_Timer.h" +#include "OVR_Log.h" + +#if defined (OVR_OS_WIN32) +#include <windows.h> +#elif defined(OVR_OS_ANDROID) +#include <time.h> +#include <android/log.h> + +#else +#include <sys/time.h> +#endif + +namespace OVR { + +// For recorded data playback +bool Timer::useFakeSeconds = false; +double Timer::FakeSeconds = 0; + + +//------------------------------------------------------------------------ +// *** Timer - Platform Independent functions + +// Returns global high-resolution application timer in seconds. +double Timer::GetSeconds() +{ + if(useFakeSeconds) + return FakeSeconds; + + return double(Timer::GetTicksNanos()) * 0.000000001; +} + + +#ifndef OVR_OS_WIN32 + +// Unused on OSs other then Win32. +void Timer::initializeTimerSystem() +{ +} +void Timer::shutdownTimerSystem() +{ +} + +#endif + + + +//------------------------------------------------------------------------ +// *** Android Specific Timer + +#if defined(OVR_OS_ANDROID) + +UInt64 Timer::GetTicksNanos() +{ + if (useFakeSeconds) + return (UInt64) (FakeSeconds * NanosPerSecond); + + // Choreographer vsync timestamp is based on. + struct timespec tp; + const int status = clock_gettime(CLOCK_MONOTONIC, &tp); + + if (status != 0) + { + OVR_DEBUG_LOG(("clock_gettime status=%i", status )); + } + const UInt64 result = (UInt64)tp.tv_sec * (UInt64)(1000 * 1000 * 1000) + UInt64(tp.tv_nsec); + return result; +} + + +//------------------------------------------------------------------------ +// *** Win32 Specific Timer + +#elif defined (OVR_OS_WIN32) + + +// This helper class implements high-resolution wrapper that combines timeGetTime() output +// with QueryPerformanceCounter. timeGetTime() is lower precision but drives the high bits, +// as it's tied to the system clock. +struct PerformanceTimer +{ + PerformanceTimer() + : OldMMTimeMs(0), MMTimeWrapCounter(0), PrefFrequency(0), + LastResultNanos(0), PerfMinusTicksDeltaNanos(0) + { } + + enum { + MMTimerResolutionNanos = 1000000 + }; + + void Initialize(); + void Shutdown(); + + UInt64 GetTimeNanos(); + + + UINT64 getFrequency() + { + if (PrefFrequency == 0) + { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + PrefFrequency = freq.QuadPart; + } + return PrefFrequency; + } + + + CRITICAL_SECTION TimeCS; + // timeGetTime() support with wrap. + UInt32 OldMMTimeMs; + UInt32 MMTimeWrapCounter; + // Cached performance frequency result. + UInt64 PrefFrequency; + + // Computed as (perfCounterNanos - ticksCounterNanos) initially, + // and used to adjust timing. + UInt64 PerfMinusTicksDeltaNanos; + // Last returned value in nanoseconds, to ensure we don't back-step in time. + UInt64 LastResultNanos; +}; + +PerformanceTimer Win32_PerfTimer; + + +void PerformanceTimer::Initialize() +{ + timeBeginPeriod(1); + InitializeCriticalSection(&TimeCS); + MMTimeWrapCounter = 0; + getFrequency(); +} + +void PerformanceTimer::Shutdown() +{ + DeleteCriticalSection(&TimeCS); + timeEndPeriod(1); +} + +UInt64 PerformanceTimer::GetTimeNanos() +{ + UInt64 resultNanos; + LARGE_INTEGER li; + DWORD mmTimeMs; + + // On Win32 QueryPerformanceFrequency is unreliable due to SMP and + // performance levels, so use this logic to detect wrapping and track + // high bits. + ::EnterCriticalSection(&TimeCS); + + // Get raw value and perf counter "At the same time". + mmTimeMs = timeGetTime(); + QueryPerformanceCounter(&li); + + if (OldMMTimeMs > mmTimeMs) + MMTimeWrapCounter++; + OldMMTimeMs = mmTimeMs; + + // Normalize to nanoseconds. + UInt64 mmCounterNanos = ((UInt64(MMTimeWrapCounter) << 32) | mmTimeMs) * 1000000; + UInt64 frequency = getFrequency(); + UInt64 perfCounterSeconds = UInt64(li.QuadPart) / frequency; + UInt64 perfRemainderNanos = ( (UInt64(li.QuadPart) - perfCounterSeconds * frequency) * + Timer::NanosPerSecond ) / frequency; + UInt64 perfCounterNanos = perfCounterSeconds * Timer::NanosPerSecond + perfRemainderNanos; + + if (PerfMinusTicksDeltaNanos == 0) + PerfMinusTicksDeltaNanos = perfCounterNanos - mmCounterNanos; + + + // Compute result before snapping. + // + // On first call, this evaluates to: + // resultNanos = mmCounterNanos. + // Next call, assuming no wrap: + // resultNanos = prev_mmCounterNanos + (perfCounterNanos - prev_perfCounterNanos). + // After wrap, this would be: + // resultNanos = snapped(prev_mmCounterNanos +/- 1ms) + (perfCounterNanos - prev_perfCounterNanos). + // + resultNanos = perfCounterNanos - PerfMinusTicksDeltaNanos; + + // Snap the range so that resultNanos never moves further apart then its target resolution. + // It's better to allow more slack on the high side as timeGetTime() may be updated at sporadically + // larger then 1 ms intervals even when 1 ms resolution is requested. + if (resultNanos > (mmCounterNanos + MMTimerResolutionNanos*2)) + { + resultNanos = mmCounterNanos + MMTimerResolutionNanos*2; + if (resultNanos < LastResultNanos) + resultNanos = LastResultNanos; + PerfMinusTicksDeltaNanos = perfCounterNanos - resultNanos; + } + else if (resultNanos < (mmCounterNanos - MMTimerResolutionNanos)) + { + resultNanos = mmCounterNanos - MMTimerResolutionNanos; + if (resultNanos < LastResultNanos) + resultNanos = LastResultNanos; + PerfMinusTicksDeltaNanos = perfCounterNanos - resultNanos; + } + + LastResultNanos = resultNanos; + ::LeaveCriticalSection(&TimeCS); + + //Tom's addition, to keep precision + static UInt64 initial_time = 0; + if (!initial_time) initial_time = resultNanos; + resultNanos -= initial_time; + + + return resultNanos; +} + + +// Delegate to PerformanceTimer. +UInt64 Timer::GetTicksNanos() +{ + if (useFakeSeconds) + return (UInt64) (FakeSeconds * NanosPerSecond); + + return Win32_PerfTimer.GetTimeNanos(); +} +void Timer::initializeTimerSystem() +{ + Win32_PerfTimer.Initialize(); + +} +void Timer::shutdownTimerSystem() +{ + Win32_PerfTimer.Shutdown(); +} + +#else // !OVR_OS_WIN32 && !OVR_OS_ANDROID + + +//------------------------------------------------------------------------ +// *** Standard OS Timer + +UInt64 Timer::GetTicksNanos() +{ + if (useFakeSeconds) + return (UInt64) (FakeSeconds * NanosPerSecond); + + // TODO: prefer rdtsc when available? + UInt64 result; + + // Return microseconds. + struct timeval tv; + + gettimeofday(&tv, 0); + + result = (UInt64)tv.tv_sec * 1000000; + result += tv.tv_usec; + + return result * 1000; +} + +#endif // OS-specific + + + +} // OVR + diff --git a/LibOVR/Src/Kernel/OVR_Timer.h b/LibOVR/Src/Kernel/OVR_Timer.h new file mode 100644 index 0000000..12cba3b --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Timer.h @@ -0,0 +1,88 @@ +/************************************************************************************ + +PublicHeader: OVR +Filename : OVR_Timer.h +Content : Provides static functions for precise timing +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Timer_h +#define OVR_Timer_h + +#include "OVR_Types.h" + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Timer + +// Timer class defines a family of static functions used for application +// timing and profiling. + +class Timer +{ +public: + enum { + MsPerSecond = 1000, // Milliseconds in one second. + NanosPerSecond = MsPerSecond * 1000 * 1000, + MksPerSecond = MsPerSecond * 1000 + }; + + // ***** Timing APIs for Application + + // These APIs should be used to guide animation and other program functions + // that require precision. + + // Returns global high-resolution application timer in seconds. + static double OVR_STDCALL GetSeconds(); + + // Returns time in Nanoseconds, using highest possible system resolution. + static UInt64 OVR_STDCALL GetTicksNanos(); + + // Kept for compatibility. + // Returns ticks in milliseconds, as a 32-bit number. May wrap around every 49.2 days. + // Use either time difference of two values of GetTicks to avoid wrap-around. + static UInt32 OVR_STDCALL GetTicksMs() + { return UInt32(GetTicksNanos() / 1000000); } + + // for recorded data playback + static void SetFakeSeconds(double fakeSeconds) + { + FakeSeconds = fakeSeconds; + useFakeSeconds = true; + } + +private: + friend class System; + // System called during program startup/shutdown. + static void initializeTimerSystem(); + static void shutdownTimerSystem(); + + // for recorded data playback + static double FakeSeconds; + static bool useFakeSeconds; +}; + + +} // OVR::Timer + +#endif diff --git a/LibOVR/Src/Kernel/OVR_Types.h b/LibOVR/Src/Kernel/OVR_Types.h new file mode 100644 index 0000000..8f2b3f3 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_Types.h @@ -0,0 +1,474 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Types.h +Content : Standard library defines and simple types +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Types_H +#define OVR_Types_H + +//----------------------------------------------------------------------------------- +// ****** Operating System +// +// Type definitions exist for the following operating systems: (OVR_OS_x) +// +// WIN32 - Win32 (Windows 95/98/ME and Windows NT/2000/XP) +// DARWIN - Darwin OS (Mac OS X) +// LINUX - Linux +// ANDROID - Android +// IPHONE - iPhone + +#if (defined(__APPLE__) && (defined(__GNUC__) ||\ + defined(__xlC__) || defined(__xlc__))) || defined(__MACOS__) +# if (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) || defined(__IPHONE_OS_VERSION_MIN_REQUIRED)) +# define OVR_OS_IPHONE +# else +# define OVR_OS_DARWIN +# define OVR_OS_MAC +# endif +#elif (defined(WIN64) || defined(_WIN64) || defined(__WIN64__)) +# define OVR_OS_WIN32 +#elif (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)) +# define OVR_OS_WIN32 +#elif defined(__linux__) || defined(__linux) +# define OVR_OS_LINUX +#else +# define OVR_OS_OTHER +#endif + +#if defined(ANDROID) +# define OVR_OS_ANDROID +#endif + + +//----------------------------------------------------------------------------------- +// ***** CPU Architecture +// +// The following CPUs are defined: (OVR_CPU_x) +// +// X86 - x86 (IA-32) +// X86_64 - x86_64 (amd64) +// PPC - PowerPC +// PPC64 - PowerPC64 +// MIPS - MIPS +// OTHER - CPU for which no special support is present or needed + + +#if defined(__x86_64__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) +# define OVR_CPU_X86_64 +# define OVR_64BIT_POINTERS +#elif defined(__i386__) || defined(OVR_OS_WIN32) +# define OVR_CPU_X86 +#elif defined(__powerpc64__) +# define OVR_CPU_PPC64 +#elif defined(__ppc__) +# define OVR_CPU_PPC +#elif defined(__mips__) || defined(__MIPSEL__) +# define OVR_CPU_MIPS +#elif defined(__arm__) +# define OVR_CPU_ARM +#else +# define OVR_CPU_OTHER +#endif + +//----------------------------------------------------------------------------------- +// ***** Co-Processor Architecture +// +// The following co-processors are defined: (OVR_CPU_x) +// +// SSE - Available on all modern x86 processors. +// Altivec - Available on all modern ppc processors. +// Neon - Available on some armv7+ processors. + +#if defined(__SSE__) || defined(OVR_OS_WIN32) +# define OVR_CPU_SSE +#endif // __SSE__ + +#if defined( __ALTIVEC__ ) +# define OVR_CPU_ALTIVEC +#endif // __ALTIVEC__ + +#if defined(__ARM_NEON__) +# define OVR_CPU_ARM_NEON +#endif // __ARM_NEON__ + + +//----------------------------------------------------------------------------------- +// ***** Compiler +// +// The following compilers are defined: (OVR_CC_x) +// +// MSVC - Microsoft Visual C/C++ +// INTEL - Intel C++ for Linux / Windows +// GNU - GNU C++ +// ARM - ARM C/C++ + +#if defined(__INTEL_COMPILER) +// Intel 4.0 = 400 +// Intel 5.0 = 500 +// Intel 6.0 = 600 +// Intel 8.0 = 800 +// Intel 9.0 = 900 +# define OVR_CC_INTEL __INTEL_COMPILER + +#elif defined(_MSC_VER) +// MSVC 5.0 = 1100 +// MSVC 6.0 = 1200 +// MSVC 7.0 (VC2002) = 1300 +// MSVC 7.1 (VC2003) = 1310 +// MSVC 8.0 (VC2005) = 1400 +// MSVC 9.0 (VC2008) = 1500 +// MSVC 10.0 (VC2010) = 1600 +// MSVC 11.0 (VC2012) = 1700 +// MSVC 12.0 (VC2013) = 1800 +# define OVR_CC_MSVC _MSC_VER + +#elif defined(__GNUC__) +# define OVR_CC_GNU + +#elif defined(__CC_ARM) +# define OVR_CC_ARM + +#else +# error "Oculus does not support this Compiler" +#endif + + +//----------------------------------------------------------------------------------- +// ***** Compiler Warnings + +// Disable MSVC warnings +#if defined(OVR_CC_MSVC) +# pragma warning(disable : 4127) // Inconsistent dll linkage +# pragma warning(disable : 4530) // Exception handling +# if (OVR_CC_MSVC<1300) +# pragma warning(disable : 4514) // Unreferenced inline function has been removed +# pragma warning(disable : 4710) // Function not inlined +# pragma warning(disable : 4714) // _force_inline not inlined +# pragma warning(disable : 4786) // Debug variable name longer than 255 chars +# endif // (OVR_CC_MSVC<1300) +#endif // (OVR_CC_MSVC) + + + +// *** Linux Unicode - must come before Standard Includes + +#ifdef OVR_OS_LINUX +// Use glibc unicode functions on linux. +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#endif + +//----------------------------------------------------------------------------------- +// ***** Standard Includes +// +#include <stddef.h> +#include <limits.h> +#include <float.h> + + +// MSVC Based Memory Leak checking - for now +#if defined(OVR_CC_MSVC) && defined(OVR_BUILD_DEBUG) +# define _CRTDBG_MAP_ALLOC +# include <stdlib.h> +# include <crtdbg.h> + +#if 0 +// Uncomment this to help debug memory leaks under Visual Studio in OVR apps only. +// This shouldn't be defined in customer releases. +# ifndef OVR_DEFINE_NEW +# define OVR_DEFINE_NEW new(__FILE__, __LINE__) +# define new OVR_DEFINE_NEW +# endif +#endif + +#endif + + +//----------------------------------------------------------------------------------- +// ***** Type definitions for Common Systems + +namespace OVR { + +typedef char Char; + +// Pointer-sized integer +typedef size_t UPInt; +typedef ptrdiff_t SPInt; + + +#if defined(OVR_OS_WIN32) + +typedef char SByte; // 8 bit Integer (Byte) +typedef unsigned char UByte; +typedef short SInt16; // 16 bit Integer (Word) +typedef unsigned short UInt16; +typedef long SInt32; // 32 bit Integer +typedef unsigned long UInt32; +typedef __int64 SInt64; // 64 bit Integer (QWord) +typedef unsigned __int64 UInt64; + + +#elif defined(OVR_OS_MAC) || defined(OVR_OS_IPHONE) || defined(OVR_CC_GNU) + +typedef int SByte __attribute__((__mode__ (__QI__))); +typedef unsigned int UByte __attribute__((__mode__ (__QI__))); +typedef int SInt16 __attribute__((__mode__ (__HI__))); +typedef unsigned int UInt16 __attribute__((__mode__ (__HI__))); +typedef int SInt32 __attribute__((__mode__ (__SI__))); +typedef unsigned int UInt32 __attribute__((__mode__ (__SI__))); +typedef int SInt64 __attribute__((__mode__ (__DI__))); +typedef unsigned int UInt64 __attribute__((__mode__ (__DI__))); + +#else + +#include <sys/types.h> +typedef int8_t SByte; +typedef uint8_t UByte; +typedef int16_t SInt16; +typedef uint16_t UInt16; +typedef int32_t SInt32; +typedef uint32_t UInt32; +typedef int64_t SInt64; +typedef uint64_t UInt64; + +#endif + + +// ***** BaseTypes Namespace + +// BaseTypes namespace is explicitly declared to allow base types to be used +// by customers directly without other contents of OVR namespace. +// +// Its is expected that OVR samples will declare 'using namespace OVR::BaseTypes' +// to allow using these directly without polluting the target scope with other +// OVR declarations, such as Ptr<>, String or Mutex. +namespace BaseTypes +{ + using OVR::UPInt; + using OVR::SPInt; + using OVR::UByte; + using OVR::SByte; + using OVR::UInt16; + using OVR::SInt16; + using OVR::UInt32; + using OVR::SInt32; + using OVR::UInt64; + using OVR::SInt64; +} // OVR::BaseTypes + +} // OVR + + +//----------------------------------------------------------------------------------- +// ***** Macro Definitions +// +// We define the following: +// +// OVR_BYTE_ORDER - Defined to either OVR_LITTLE_ENDIAN or OVR_BIG_ENDIAN +// OVR_FORCE_INLINE - Forces inline expansion of function +// OVR_ASM - Assembly language prefix +// OVR_STR - Prefixes string with L"" if building unicode +// +// OVR_STDCALL - Use stdcall calling convention (Pascal arg order) +// OVR_CDECL - Use cdecl calling convention (C argument order) +// OVR_FASTCALL - Use fastcall calling convention (registers) +// + +// Byte order constants, OVR_BYTE_ORDER is defined to be one of these. +#define OVR_LITTLE_ENDIAN 1 +#define OVR_BIG_ENDIAN 2 + + +// Force inline substitute - goes before function declaration +#if defined(OVR_CC_MSVC) +# define OVR_FORCE_INLINE __forceinline +#elif defined(OVR_CC_GNU) +# define OVR_FORCE_INLINE __attribute__((always_inline)) inline +#else +# define OVR_FORCE_INLINE inline +#endif // OVR_CC_MSVC + + +#if defined(OVR_OS_WIN32) + + // ***** Win32 + + // Byte order + #define OVR_BYTE_ORDER OVR_LITTLE_ENDIAN + + // Calling convention - goes after function return type but before function name + #ifdef __cplusplus_cli + # define OVR_FASTCALL __stdcall + #else + # define OVR_FASTCALL __fastcall + #endif + + #define OVR_STDCALL __stdcall + #define OVR_CDECL __cdecl + + + // Assembly macros + #if defined(OVR_CC_MSVC) + # define OVR_ASM _asm + #else + # define OVR_ASM asm + #endif // (OVR_CC_MSVC) + + #ifdef UNICODE + # define OVR_STR(str) L##str + #else + # define OVR_STR(str) str + #endif // UNICODE + +#else + + // **** Standard systems + + #if (defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN))|| \ + (defined(_BYTE_ORDER) && (_BYTE_ORDER == _BIG_ENDIAN)) + # define OVR_BYTE_ORDER OVR_BIG_ENDIAN + #elif (defined(__ARMEB__) || defined(OVR_CPU_PPC) || defined(OVR_CPU_PPC64)) + # define OVR_BYTE_ORDER OVR_BIG_ENDIAN + #else + # define OVR_BYTE_ORDER OVR_LITTLE_ENDIAN + #endif + + // Assembly macros + #define OVR_ASM __asm__ + #define OVR_ASM_PROC(procname) OVR_ASM + #define OVR_ASM_END OVR_ASM + + // Calling convention - goes after function return type but before function name + #define OVR_FASTCALL + #define OVR_STDCALL + #define OVR_CDECL + +#endif // defined(OVR_OS_WIN32) + + + +//----------------------------------------------------------------------------------- +// ***** OVR_DEBUG_BREAK, OVR_ASSERT +// +// If not in debug build, macros do nothing +#ifndef OVR_BUILD_DEBUG + +# define OVR_DEBUG_CODE(c) c +# define OVR_DEBUG_BREAK ((void)0) +# define OVR_ASSERT(p) ((void)0) + +#else + +// Microsoft Win32 specific debugging support +#if defined(OVR_OS_WIN32) +# ifdef OVR_CPU_X86 +# if defined(__cplusplus_cli) +# define OVR_DEBUG_BREAK do { __debugbreak(); } while(0) +# elif defined(OVR_CC_GNU) +# define OVR_DEBUG_BREAK do { OVR_ASM("int $3\n\t"); } while(0) +# else +# define OVR_DEBUG_BREAK do { OVR_ASM int 3 } while (0) +# endif +# else +# define OVR_DEBUG_BREAK do { __debugbreak(); } while(0) +# endif +// Unix specific debugging support +#elif defined(OVR_CPU_X86) || defined(OVR_CPU_X86_64) +# define OVR_DEBUG_BREAK do { OVR_ASM("int $3\n\t"); } while(0) +#else +# define OVR_DEBUG_BREAK do { *((int *) 0) = 1; } while(0) +#endif + +#define OVR_DEBUG_CODE(c) + +// This will cause compiler breakpoint +#define OVR_ASSERT(p) do { if (!(p)) { OVR_DEBUG_BREAK; } } while(0) + +#endif // OVR_BUILD_DEBUG + + +// Compile-time assert; produces compiler error if condition is false +#define OVR_COMPILER_ASSERT(x) { int zero = 0; switch(zero) {case 0: case x:;} } + + + +//----------------------------------------------------------------------------------- +// ***** OVR_UNUSED - Unused Argument handling + +// Macro to quiet compiler warnings about unused parameters/variables. +#if defined(OVR_CC_GNU) +# define OVR_UNUSED(a) do {__typeof__ (&a) __attribute__ ((unused)) __tmp = &a; } while(0) +#else +# define OVR_UNUSED(a) (a) +#endif + +#define OVR_UNUSED1(a1) OVR_UNUSED(a1) +#define OVR_UNUSED2(a1,a2) OVR_UNUSED(a1); OVR_UNUSED(a2) +#define OVR_UNUSED3(a1,a2,a3) OVR_UNUSED2(a1,a2); OVR_UNUSED(a3) +#define OVR_UNUSED4(a1,a2,a3,a4) OVR_UNUSED3(a1,a2,a3); OVR_UNUSED(a4) +#define OVR_UNUSED5(a1,a2,a3,a4,a5) OVR_UNUSED4(a1,a2,a3,a4); OVR_UNUSED(a5) +#define OVR_UNUSED6(a1,a2,a3,a4,a5,a6) OVR_UNUSED4(a1,a2,a3,a4); OVR_UNUSED2(a5,a6) +#define OVR_UNUSED7(a1,a2,a3,a4,a5,a6,a7) OVR_UNUSED4(a1,a2,a3,a4); OVR_UNUSED3(a5,a6,a7) +#define OVR_UNUSED8(a1,a2,a3,a4,a5,a6,a7,a8) OVR_UNUSED4(a1,a2,a3,a4); OVR_UNUSED4(a5,a6,a7,a8) +#define OVR_UNUSED9(a1,a2,a3,a4,a5,a6,a7,a8,a9) OVR_UNUSED4(a1,a2,a3,a4); OVR_UNUSED5(a5,a6,a7,a8,a9) + + +//----------------------------------------------------------------------------------- +// ***** Configuration Macros + +// SF Build type +#ifdef OVR_BUILD_DEBUG +# define OVR_BUILD_STRING "Debug" +#else +# define OVR_BUILD_STRING "Release" +#endif + + +//// Enables SF Debugging information +//# define OVR_BUILD_DEBUG + +// OVR_DEBUG_STATEMENT injects a statement only in debug builds. +// OVR_DEBUG_SELECT injects first argument in debug builds, second argument otherwise. +#ifdef OVR_BUILD_DEBUG +#define OVR_DEBUG_STATEMENT(s) s +#define OVR_DEBUG_SELECT(d, nd) d +#else +#define OVR_DEBUG_STATEMENT(s) +#define OVR_DEBUG_SELECT(d, nd) nd +#endif + + +#define OVR_ENABLE_THREADS +// +// Prevents OVR from defining new within +// type macros, so developers can override +// new using the #define new new(...) trick +// - used with OVR_DEFINE_NEW macro +//# define OVR_BUILD_DEFINE_NEW +// + + +#endif // OVR_Types_h diff --git a/LibOVR/Src/Kernel/OVR_UTF8Util.cpp b/LibOVR/Src/Kernel/OVR_UTF8Util.cpp new file mode 100644 index 0000000..f8aa697 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_UTF8Util.cpp @@ -0,0 +1,556 @@ +/************************************************************************** + +Filename : OVR_UTF8Util.cpp +Content : UTF8 Unicode character encoding/decoding support +Created : September 19, 2012 +Notes : +Notes : Much useful info at "UTF-8 and Unicode FAQ" + http://www.cl.cam.ac.uk/~mgk25/unicode.html + +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 "OVR_UTF8Util.h" + +namespace OVR { namespace UTF8Util { + +SPInt OVR_STDCALL GetLength(const char* buf, SPInt buflen) +{ + const char* p = buf; + SPInt length = 0; + + if (buflen != -1) + { + while (p - buf < buflen) + { + // We should be able to have ASStrings with 0 in the middle. + UTF8Util::DecodeNextChar_Advance0(&p); + length++; + } + } + else + { + while (UTF8Util::DecodeNextChar_Advance0(&p)) + length++; + } + + return length; +} + +UInt32 OVR_STDCALL GetCharAt(SPInt index, const char* putf8str, SPInt length) +{ + const char* buf = putf8str; + UInt32 c = 0; + + if (length != -1) + { + while (buf - putf8str < length) + { + c = UTF8Util::DecodeNextChar_Advance0(&buf); + if (index == 0) + return c; + index--; + } + + return c; + } + + do + { + c = UTF8Util::DecodeNextChar_Advance0(&buf); + index--; + + if (c == 0) + { + // We've hit the end of the string; don't go further. + OVR_ASSERT(index == 0); + return c; + } + } while (index >= 0); + + return c; +} + +SPInt OVR_STDCALL GetByteIndex(SPInt index, const char *putf8str, SPInt length) +{ + const char* buf = putf8str; + + if (length != -1) + { + while ((buf - putf8str) < length && index > 0) + { + UTF8Util::DecodeNextChar_Advance0(&buf); + index--; + } + + return buf-putf8str; + } + + while (index > 0) + { + UInt32 c = UTF8Util::DecodeNextChar_Advance0(&buf); + index--; + + if (c == 0) + return buf-putf8str; + }; + + return buf-putf8str; +} + +int OVR_STDCALL GetEncodeCharSize(UInt32 ucs_character) +{ + if (ucs_character <= 0x7F) + return 1; + else if (ucs_character <= 0x7FF) + return 2; + else if (ucs_character <= 0xFFFF) + return 3; + else if (ucs_character <= 0x1FFFFF) + return 4; + else if (ucs_character <= 0x3FFFFFF) + return 5; + else if (ucs_character <= 0x7FFFFFFF) + return 6; + else + return 0; +} + +UInt32 OVR_STDCALL DecodeNextChar_Advance0(const char** putf8Buffer) +{ + UInt32 uc; + char c; + + // Security considerations: + // + // Changed, this is now only the case for DecodeNextChar: + // - If we hit a zero byte, we want to return 0 without stepping + // the buffer pointer past the 0. th + // + // If we hit an "overlong sequence"; i.e. a character encoded + // in a longer multibyte string than is necessary, then we + // need to discard the character. This is so attackers can't + // disguise dangerous characters or character sequences -- + // there is only one valid encoding for each character. + // + // If we decode characters { 0xD800 .. 0xDFFF } or { 0xFFFE, + // 0xFFFF } then we ignore them; they are not valid in UTF-8. + + // This isn't actually an invalid character; it's a valid char that + // looks like an inverted question mark. +#define INVALID_CHAR 0x0FFFD + +#define FIRST_BYTE(mask, shift) \ + uc = (c & (mask)) << (shift); + +#define NEXT_BYTE(shift) \ + c = **putf8Buffer; \ + if (c == 0) return 0; /* end of buffer, do not advance */ \ + if ((c & 0xC0) != 0x80) return INVALID_CHAR; /* standard check */ \ + (*putf8Buffer)++; \ + uc |= (c & 0x3F) << shift; + + c = **putf8Buffer; + (*putf8Buffer)++; + if (c == 0) + return 0; // End of buffer. + + if ((c & 0x80) == 0) return (UInt32) c; // Conventional 7-bit ASCII. + + // Multi-byte sequences. + if ((c & 0xE0) == 0xC0) + { + // Two-byte sequence. + FIRST_BYTE(0x1F, 6); + NEXT_BYTE(0); + if (uc < 0x80) return INVALID_CHAR; // overlong + return uc; + } + else if ((c & 0xF0) == 0xE0) + { + // Three-byte sequence. + FIRST_BYTE(0x0F, 12); + NEXT_BYTE(6); + NEXT_BYTE(0); + if (uc < 0x800) return INVALID_CHAR; // overlong + // Not valid ISO 10646, but Flash requires these to work + // see AS3 test e15_5_3_2_3 for String.fromCharCode().charCodeAt(0) + // if (uc >= 0x0D800 && uc <= 0x0DFFF) return INVALID_CHAR; + // if (uc == 0x0FFFE || uc == 0x0FFFF) return INVALID_CHAR; // not valid ISO 10646 + return uc; + } + else if ((c & 0xF8) == 0xF0) + { + // Four-byte sequence. + FIRST_BYTE(0x07, 18); + NEXT_BYTE(12); + NEXT_BYTE(6); + NEXT_BYTE(0); + if (uc < 0x010000) return INVALID_CHAR; // overlong + return uc; + } + else if ((c & 0xFC) == 0xF8) + { + // Five-byte sequence. + FIRST_BYTE(0x03, 24); + NEXT_BYTE(18); + NEXT_BYTE(12); + NEXT_BYTE(6); + NEXT_BYTE(0); + if (uc < 0x0200000) return INVALID_CHAR; // overlong + return uc; + } + else if ((c & 0xFE) == 0xFC) + { + // Six-byte sequence. + FIRST_BYTE(0x01, 30); + NEXT_BYTE(24); + NEXT_BYTE(18); + NEXT_BYTE(12); + NEXT_BYTE(6); + NEXT_BYTE(0); + if (uc < 0x04000000) return INVALID_CHAR; // overlong + return uc; + } + else + { + // Invalid. + return INVALID_CHAR; + } +} + + +void OVR_STDCALL EncodeChar(char* pbuffer, SPInt* pindex, UInt32 ucs_character) +{ + if (ucs_character <= 0x7F) + { + // Plain single-byte ASCII. + pbuffer[(*pindex)++] = (char) ucs_character; + } + else if (ucs_character <= 0x7FF) + { + // Two bytes. + pbuffer[(*pindex)++] = 0xC0 | (char)(ucs_character >> 6); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 0) & 0x3F); + } + else if (ucs_character <= 0xFFFF) + { + // Three bytes. + pbuffer[(*pindex)++] = 0xE0 | (char)(ucs_character >> 12); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 6) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 0) & 0x3F); + } + else if (ucs_character <= 0x1FFFFF) + { + // Four bytes. + pbuffer[(*pindex)++] = 0xF0 | (char)(ucs_character >> 18); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 12) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 6) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 0) & 0x3F); + } + else if (ucs_character <= 0x3FFFFFF) + { + // Five bytes. + pbuffer[(*pindex)++] = 0xF8 | (char)(ucs_character >> 24); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 18) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 12) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 6) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 0) & 0x3F); + } + else if (ucs_character <= 0x7FFFFFFF) + { + // Six bytes. + pbuffer[(*pindex)++] = 0xFC | (char)(ucs_character >> 30); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 24) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 18) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 12) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 6) & 0x3F); + pbuffer[(*pindex)++] = 0x80 | (char)((ucs_character >> 0) & 0x3F); + } + else + { + // Invalid char; don't encode anything. + } +} + +SPInt OVR_STDCALL GetEncodeStringSize(const wchar_t* pchar, SPInt length) +{ + SPInt len = 0; + if (length != -1) + for (int i = 0; i < length; i++) + { + len += GetEncodeCharSize(pchar[i]); + } + else + for (int i = 0;; i++) + { + if (pchar[i] == 0) + return len; + len += GetEncodeCharSize(pchar[i]); + } + return len; +} + +void OVR_STDCALL EncodeString(char *pbuff, const wchar_t* pchar, SPInt length) +{ + SPInt ofs = 0; + if (length != -1) + { + for (int i = 0; i < length; i++) + { + EncodeChar(pbuff, &ofs, pchar[i]); + } + } + else + { + for (int i = 0;; i++) + { + if (pchar[i] == 0) + break; + EncodeChar(pbuff, &ofs, pchar[i]); + } + } + pbuff[ofs] = 0; +} + +UPInt OVR_STDCALL DecodeString(wchar_t *pbuff, const char* putf8str, SPInt bytesLen) +{ + wchar_t *pbegin = pbuff; + if (bytesLen == -1) + { + while (1) + { + UInt32 ch = DecodeNextChar_Advance0(&putf8str); + if (ch == 0) + break; + else if (ch >= 0xFFFF) + ch = 0xFFFD; + *pbuff++ = wchar_t(ch); + } + } + else + { + const char* p = putf8str; + while ((p - putf8str) < bytesLen) + { + UInt32 ch = DecodeNextChar_Advance0(&p); + if (ch >= 0xFFFF) + ch = 0xFFFD; + *pbuff++ = wchar_t(ch); + } + } + + *pbuff = 0; + return pbuff - pbegin; +} + + +#ifdef UTF8_UNIT_TEST + +// Compile this test case with something like: +// +// gcc utf8.cpp -g -I.. -DUTF8_UNIT_TEST -lstdc++ -o utf8_test +// +// or +// +// cl utf8.cpp -Zi -Od -DUTF8_UNIT_TEST -I.. +// +// If possible, try running the test program with the first arg +// pointing at the file: +// +// http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt +// +// and examine the results by eye to make sure they are acceptable to +// you. + + +#include "base/utility.h" +#include <stdio.h> + + +bool check_equal(const char* utf8_in, const UInt32* ucs_in) +{ + for (;;) + { + UInt32 next_ucs = *ucs_in++; + UInt32 next_ucs_from_utf8 = utf8::decode_next_unicode_character(&utf8_in); + if (next_ucs != next_ucs_from_utf8) + { + return false; + } + if (next_ucs == 0) + { + OVR_ASSERT(next_ucs_from_utf8 == 0); + break; + } + } + + return true; +} + + +void log_ascii(const char* line) +{ + for (;;) + { + unsigned char c = (unsigned char) *line++; + if (c == 0) + { + // End of line. + return; + } + else if (c != '\n' + && (c < 32 || c > 127)) + { + // Non-printable as plain ASCII. + printf("<0x%02X>", (int) c); + } + else + { + printf("%c", c); + } + } +} + + +void log_ucs(const UInt32* line) +{ + for (;;) + { + UInt32 uc = *line++; + if (uc == 0) + { + // End of line. + return; + } + else if (uc != '\n' + && (uc < 32 || uc > 127)) + { + // Non-printable as plain ASCII. + printf("<U-%04X>", uc); + } + else + { + printf("%c", (char) uc); + } + } +} + + +// Simple canned test. +int main(int argc, const char* argv[]) +{ + { + const char* test8 = "Ignacio Castaño"; + const UInt32 test32[] = + { + 0x49, 0x67, 0x6E, 0x61, 0x63, + 0x69, 0x6F, 0x20, 0x43, 0x61, + 0x73, 0x74, 0x61, 0xF1, 0x6F, + 0x00 + }; + + OVR_ASSERT(check_equal(test8, test32)); + } + + // If user passed an arg, try reading the file as UTF-8 encoded text. + if (argc > 1) + { + const char* filename = argv[1]; + FILE* fp = fopen(filename, "rb"); + if (fp == NULL) + { + printf("Can't open file '%s'\n", filename); + return 1; + } + + // Read lines from the file, encode/decode them, and highlight discrepancies. + const int LINE_SIZE = 200; // max line size + char line_buffer_utf8[LINE_SIZE]; + char reencoded_utf8[6 * LINE_SIZE]; + UInt32 line_buffer_ucs[LINE_SIZE]; + + int byte_counter = 0; + for (;;) + { + int c = fgetc(fp); + if (c == EOF) + { + // Done. + break; + } + line_buffer_utf8[byte_counter++] = c; + if (c == '\n' || byte_counter >= LINE_SIZE - 2) + { + // End of line. Process the line. + line_buffer_utf8[byte_counter++] = 0; // terminate. + + // Decode into UCS. + const char* p = line_buffer_utf8; + UInt32* q = line_buffer_ucs; + for (;;) + { + UInt32 uc = UTF8Util::DecodeNextChar(&p); + *q++ = uc; + + OVR_ASSERT(q < line_buffer_ucs + LINE_SIZE); + OVR_ASSERT(p < line_buffer_utf8 + LINE_SIZE); + + if (uc == 0) break; + } + + // Encode back into UTF-8. + q = line_buffer_ucs; + int index = 0; + for (;;) + { + UInt32 uc = *q++; + OVR_ASSERT(index < LINE_SIZE * 6 - 6); + int last_index = index; + UTF8Util::EncodeChar(reencoded_utf8, &index, uc); + OVR_ASSERT(index <= last_index + 6); + if (uc == 0) break; + } + + // This can be useful for debugging. +#if 0 + // Show the UCS and the re-encoded UTF-8. + log_ucs(line_buffer_ucs); + log_ascii(reencoded_utf8); +#endif // 0 + + OVR_ASSERT(check_equal(line_buffer_utf8, line_buffer_ucs)); + OVR_ASSERT(check_equal(reencoded_utf8, line_buffer_ucs)); + + // Start next line. + byte_counter = 0; + } + } + + fclose(fp); + } + + return 0; +} + + +#endif // UTF8_UNIT_TEST + +}} // namespace UTF8Util::OVR + diff --git a/LibOVR/Src/Kernel/OVR_UTF8Util.h b/LibOVR/Src/Kernel/OVR_UTF8Util.h new file mode 100644 index 0000000..6a59601 --- /dev/null +++ b/LibOVR/Src/Kernel/OVR_UTF8Util.h @@ -0,0 +1,99 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_UTF8Util.h +Content : UTF8 Unicode character encoding/decoding support +Created : September 19, 2012 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_UTF8Util_h +#define OVR_UTF8Util_h + +#include "OVR_Types.h" + +namespace OVR { namespace UTF8Util { + +//----------------------------------------------------------------------------------- + +// *** UTF8 string length and indexing. + +// Determines the length of UTF8 string in characters. +// If source length is specified (in bytes), null 0 character is counted properly. +SPInt OVR_STDCALL GetLength(const char* putf8str, SPInt length = -1); + +// Gets a decoded UTF8 character at index; you can access up to the index returned +// by GetLength. 0 will be returned for out of bounds access. +UInt32 OVR_STDCALL GetCharAt(SPInt index, const char* putf8str, SPInt length = -1); + +// Converts UTF8 character index into byte offset. +// -1 is returned if index was out of bounds. +SPInt OVR_STDCALL GetByteIndex(SPInt index, const char* putf8str, SPInt length = -1); + + +// *** 16-bit Unicode string Encoding/Decoding routines. + +// Determines the number of bytes necessary to encode a string. +// Does not count the terminating 0 (null) character. +SPInt OVR_STDCALL GetEncodeStringSize(const wchar_t* pchar, SPInt length = -1); + +// Encodes a unicode (UCS-2 only) string into a buffer. The size of buffer must be at +// least GetEncodeStringSize() + 1. +void OVR_STDCALL EncodeString(char *pbuff, const wchar_t* pchar, SPInt length = -1); + +// Decode UTF8 into a wchar_t buffer. Must have GetLength()+1 characters available. +// Characters over 0xFFFF are replaced with 0xFFFD. +// Returns the length of resulting string (number of characters) +UPInt OVR_STDCALL DecodeString(wchar_t *pbuff, const char* putf8str, SPInt bytesLen = -1); + + +// *** Individual character Encoding/Decoding. + +// Determined the number of bytes necessary to encode a UCS character. +int OVR_STDCALL GetEncodeCharSize(UInt32 ucsCharacter); + +// Encodes the given UCS character into the given UTF-8 buffer. +// Writes the data starting at buffer[offset], and +// increments offset by the number of bytes written. +// May write up to 6 bytes, so make sure there's room in the buffer +void OVR_STDCALL EncodeChar(char* pbuffer, SPInt* poffset, UInt32 ucsCharacter); + +// Return the next Unicode character in the UTF-8 encoded buffer. +// Invalid UTF-8 sequences produce a U+FFFD character as output. +// Advances *utf8_buffer past the character returned. Pointer advance +// occurs even if the terminating 0 character is hit, since that allows +// strings with middle '\0' characters to be supported. +UInt32 OVR_STDCALL DecodeNextChar_Advance0(const char** putf8Buffer); + +// Safer version of DecodeNextChar, which doesn't advance pointer if +// null character is hit. +inline UInt32 DecodeNextChar(const char** putf8Buffer) +{ + UInt32 ch = DecodeNextChar_Advance0(putf8Buffer); + if (ch == 0) + (*putf8Buffer)--; + return ch; +} + + +}} // OVR::UTF8Util + +#endif diff --git a/LibOVR/Src/OVR_CAPI.cpp b/LibOVR/Src/OVR_CAPI.cpp new file mode 100644 index 0000000..a7f921f --- /dev/null +++ b/LibOVR/Src/OVR_CAPI.cpp @@ -0,0 +1,925 @@ +/************************************************************************************ + +Filename : OVR_CAPI.cpp +Content : Experimental simple C interface to the HMD - version 1. +Created : November 30, 2013 +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 "OVR_CAPI.h" +#include "Kernel/OVR_Timer.h" +#include "Kernel/OVR_Math.h" +#include "Kernel/OVR_System.h" +#include "OVR_Stereo.h" +#include "OVR_Profile.h" + +#include "CAPI/CAPI_GlobalState.h" +#include "CAPI/CAPI_HMDState.h" +#include "CAPI/CAPI_FrameTimeManager.h" + + +using namespace OVR; +using namespace OVR::Util::Render; + +//------------------------------------------------------------------------------------- +// Math +namespace OVR { + + +// ***** FovPort + +// C-interop support: FovPort <-> ovrFovPort +FovPort::FovPort(const ovrFovPort &src) + : UpTan(src.UpTan), DownTan(src.DownTan), LeftTan(src.LeftTan), RightTan(src.RightTan) +{ } + +FovPort::operator ovrFovPort () const +{ + ovrFovPort result; + result.LeftTan = LeftTan; + result.RightTan = RightTan; + result.UpTan = UpTan; + result.DownTan = DownTan; + return result; +} + +// Converts Fov Tan angle units to [-1,1] render target NDC space +Vector2f FovPort::TanAngleToRendertargetNDC(Vector2f const &tanEyeAngle) +{ + ScaleAndOffset2D eyeToSourceNDC = CreateNDCScaleAndOffsetFromFov(*this); + return tanEyeAngle * eyeToSourceNDC.Scale + eyeToSourceNDC.Offset; +} + + +// ***** SensorState + +SensorState::SensorState(const ovrSensorState& s) +{ + Predicted = s.Predicted; + Recorded = s.Recorded; + Temperature = s.Temperature; + StatusFlags = s.StatusFlags; +} + +SensorState::operator ovrSensorState() const +{ + ovrSensorState result; + result.Predicted = Predicted; + result.Recorded = Recorded; + result.Temperature = Temperature; + result.StatusFlags = StatusFlags; + return result; +} + + +} // namespace OVR + +//------------------------------------------------------------------------------------- + +using namespace OVR::CAPI; + +#ifdef __cplusplus +extern "C" { +#endif + + +// Used to generate projection from ovrEyeDesc::Fov +OVR_EXPORT ovrMatrix4f ovrMatrix4f_Projection(ovrFovPort fov, float znear, float zfar, ovrBool rightHanded) +{ + return CreateProjection(rightHanded ? true : false, fov, znear, zfar); +} + + +OVR_EXPORT ovrMatrix4f ovrMatrix4f_OrthoSubProjection(ovrMatrix4f projection, ovrVector2f orthoScale, + float orthoDistance, float eyeViewAdjustX) +{ + + float orthoHorizontalOffset = eyeViewAdjustX / orthoDistance; + + // Current projection maps real-world vector (x,y,1) to the RT. + // We want to find the projection that maps the range [-FovPixels/2,FovPixels/2] to + // the physical [-orthoHalfFov,orthoHalfFov] + // Note moving the offset from M[0][2]+M[1][2] to M[0][3]+M[1][3] - this means + // we don't have to feed in Z=1 all the time. + // The horizontal offset math is a little hinky because the destination is + // actually [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset] + // So we need to first map [-FovPixels/2,FovPixels/2] to + // [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]: + // x1 = x0 * orthoHalfFov/(FovPixels/2) + orthoHorizontalOffset; + // = x0 * 2*orthoHalfFov/FovPixels + orthoHorizontalOffset; + // But then we need the sam mapping as the existing projection matrix, i.e. + // x2 = x1 * Projection.M[0][0] + Projection.M[0][2]; + // = x0 * (2*orthoHalfFov/FovPixels + orthoHorizontalOffset) * Projection.M[0][0] + Projection.M[0][2]; + // = x0 * Projection.M[0][0]*2*orthoHalfFov/FovPixels + + // orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]; + // So in the new projection matrix we need to scale by Projection.M[0][0]*2*orthoHalfFov/FovPixels and + // offset by orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]. + + Matrix4f ortho; + ortho.M[0][0] = projection.M[0][0] * orthoScale.x; + ortho.M[0][1] = 0.0f; + ortho.M[0][2] = 0.0f; + ortho.M[0][3] = -projection.M[0][2] + ( orthoHorizontalOffset * projection.M[0][0] ); + + ortho.M[1][0] = 0.0f; + ortho.M[1][1] = -projection.M[1][1] * orthoScale.y; // Note sign flip (text rendering uses Y=down). + ortho.M[1][2] = 0.0f; + ortho.M[1][3] = -projection.M[1][2]; + + /* + if ( fabsf ( zNear - zFar ) < 0.001f ) + { + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + ortho.M[2][2] = 0.0f; + ortho.M[2][3] = zFar; + } + else + { + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + ortho.M[2][2] = zFar / (zNear - zFar); + ortho.M[2][3] = (zFar * zNear) / (zNear - zFar); + } + */ + + // MA: Undo effect of sign + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + //ortho.M[2][2] = projection.M[2][2] * projection.M[3][2] * -1.0f; // reverse right-handedness + ortho.M[2][2] = 0.0f; + ortho.M[2][3] = 0.0f; + //projection.M[2][3]; + + // No perspective correction for ortho. + ortho.M[3][0] = 0.0f; + ortho.M[3][1] = 0.0f; + ortho.M[3][2] = 0.0f; + ortho.M[3][3] = 1.0f; + + return ortho; +} + + +OVR_EXPORT double ovr_GetTimeInSeconds() +{ + return Timer::GetSeconds(); +} + +// Waits until the specified absolute time. +OVR_EXPORT double ovr_WaitTillTime(double absTime) +{ + volatile int i; + double initialTime = ovr_GetTimeInSeconds(); + double newTime = initialTime; + + while(newTime < absTime) + { + for (int j = 0; j < 50; j++) + i = 0; + newTime = ovr_GetTimeInSeconds(); + } + + // How long we waited + return newTime - initialTime; +} + +//------------------------------------------------------------------------------------- + +// 1. Init/shutdown. + +static ovrBool CAPI_SystemInitCalled = 0; + +OVR_EXPORT ovrBool ovr_Initialize() +{ + if (OVR::CAPI::GlobalState::pInstance) + return 1; + + // We must set up the system for the plugin to work + if (!OVR::System::IsInitialized()) + { + OVR::System::Init(OVR::Log::ConfigureDefaultLog(OVR::LogMask_All)); + CAPI_SystemInitCalled = 1; + } + + // Constructor detects devices + GlobalState::pInstance = new GlobalState; + return 1; +} + +OVR_EXPORT void ovr_Shutdown() +{ + if (!GlobalState::pInstance) + return; + + delete GlobalState::pInstance; + GlobalState::pInstance = 0; + + // We should clean up the system to be complete + if (CAPI_SystemInitCalled) + { + OVR::System::Destroy(); + CAPI_SystemInitCalled = 0; + } + return; +} + + +// There is a thread safety issue with ovrHmd_Detect in that multiple calls from different +// threads can corrupt the global array state. This would lead to two problems: +// a) Create(index) enumerator may miss or overshoot items. Probably not a big deal +// as game logic can easily be written to only do Detect(s)/Creates in one place. +// The alternative would be to return list handle. +// b) TBD: Un-mutexed Detect access from two threads could lead to crash. We should +// probably check this. +// + +OVR_EXPORT int ovrHmd_Detect() +{ + if (!GlobalState::pInstance) + return 0; + return GlobalState::pInstance->EnumerateDevices(); +} + + +// ovrHmd_Create us explicitly separated from StartSensor and Configure to allow creation of +// a relatively light-weight handle that would reference the device going forward and would +// survive future ovrHmd_Detect calls. That is once ovrHMD is returned, index is no longer +// necessary and can be changed by a ovrHmd_Detect call. + +OVR_EXPORT ovrHmd ovrHmd_Create(int index) +{ + if (!GlobalState::pInstance) + return 0; + Ptr<HMDDevice> device = *GlobalState::pInstance->CreateDevice(index); + if (!device) + return 0; + + HMDState* hmds = new HMDState(device); + if (!hmds) + return 0; + + return hmds; +} + +OVR_EXPORT ovrHmd ovrHmd_CreateDebug(ovrHmdType type) +{ + if (!GlobalState::pInstance) + return 0; + + HMDState* hmds = new HMDState(type); + return hmds; +} + +OVR_EXPORT void ovrHmd_Destroy(ovrHmd hmd) +{ + if (!hmd) + return; + // TBD: Any extra shutdown? + HMDState* hmds = (HMDState*)hmd; + + { // Thread checker in its own scope, to avoid access after 'delete'. + // Essentially just checks that no other RenderAPI function is executing. + ThreadChecker::Scope checkScope(&hmds->RenderAPIThreadChecker, "ovrHmd_Destroy"); + } + + delete (HMDState*)hmd; +} + + +OVR_EXPORT const char* ovrHmd_GetLastError(ovrHmd hmd) +{ + using namespace OVR; + if (!hmd) + { + if (!GlobalState::pInstance) + return "LibOVR not initialized."; + return GlobalState::pInstance->GetLastError(); + } + HMDState* p = (HMDState*)hmd; + return p->GetLastError(); +} + + +//------------------------------------------------------------------------------------- + +// Returns capability bits that are enabled at this time; described by ovrHmdCapBits. +// Note that this value is different font ovrHmdDesc::Caps, which describes what +// capabilities are available. +OVR_EXPORT unsigned int ovrHmd_GetEnabledCaps(ovrHmd hmd) +{ + HMDState* p = (HMDState*)hmd; + return p ? p->EnabledHmdCaps : 0; +} + +// Modifies capability bits described by ovrHmdCapBits that can be modified, +// such as ovrHmd_LowPersistance. +OVR_EXPORT void ovrHmd_SetEnabledCaps(ovrHmd hmd, unsigned int capsBits) +{ + HMDState* p = (HMDState*)hmd; + if (p) + p->SetEnabledHmdCaps(capsBits); +} + + +//------------------------------------------------------------------------------------- +// *** Sensor + +// Sensor APIs are separated from Create & Configure for several reasons: +// - They need custom parameters that control allocation of heavy resources +// such as Vision tracking, which you don't want to create unless necessary. +// - A game may want to switch some sensor settings based on user input, +// or at lease enable/disable features such as Vision for debugging. +// - The same or syntactically similar sensor interface is likely to be used if we +// introduce controllers. +// +// - Sensor interface functions are all Thread-safe, unlike the frame/render API +// functions that have different rules (all frame access functions +// must be on render thread) + +OVR_EXPORT ovrBool ovrHmd_StartSensor(ovrHmd hmd, unsigned int supportedCaps, unsigned int requiredCaps) +{ + HMDState* p = (HMDState*)hmd; + // TBD: Decide if we null-check arguments. + return p->StartSensor(supportedCaps, requiredCaps); +} + +OVR_EXPORT void ovrHmd_StopSensor(ovrHmd hmd) +{ + HMDState* p = (HMDState*)hmd; + p->StopSensor(); +} + +OVR_EXPORT void ovrHmd_ResetSensor(ovrHmd hmd) +{ + HMDState* p = (HMDState*)hmd; + p->ResetSensor(); +} + +OVR_EXPORT ovrSensorState ovrHmd_GetSensorState(ovrHmd hmd, double absTime) +{ + HMDState* p = (HMDState*)hmd; + return p->PredictedSensorState(absTime); +} + +// Returns information about a sensor. Only valid after SensorStart. +OVR_EXPORT ovrBool ovrHmd_GetSensorDesc(ovrHmd hmd, ovrSensorDesc* descOut) +{ + HMDState* p = (HMDState*)hmd; + return p->GetSensorDesc(descOut) ? 1 : 0; +} + + + +//------------------------------------------------------------------------------------- +// *** General Setup + + +OVR_EXPORT void ovrHmd_GetDesc(ovrHmd hmd, ovrHmdDesc* desc) +{ + HMDState* hmds = (HMDState*)hmd; + *desc = hmds->RenderState.GetDesc(); + desc->Handle = hmd; +} + +// Per HMD -> calculateIdealPixelSize +OVR_EXPORT ovrSizei ovrHmd_GetFovTextureSize(ovrHmd hmd, ovrEyeType eye, ovrFovPort fov, + float pixelsPerDisplayPixel) +{ + if (!hmd) return Sizei(0); + + HMDState* hmds = (HMDState*)hmd; + return hmds->RenderState.GetFOVTextureSize(eye, fov, pixelsPerDisplayPixel); +} + + +//------------------------------------------------------------------------------------- + + +OVR_EXPORT +ovrBool ovrHmd_ConfigureRendering( ovrHmd hmd, + const ovrRenderAPIConfig* apiConfig, + unsigned int distortionCaps, + const ovrFovPort eyeFovIn[2], + ovrEyeRenderDesc eyeRenderDescOut[2] ) +{ + if (!hmd) return 0; + return ((HMDState*)hmd)->ConfigureRendering(eyeRenderDescOut, eyeFovIn, + apiConfig, distortionCaps); +} + + + +// TBD: MA - Deprecated, need alternative +void ovrHmd_SetVsync(ovrHmd hmd, ovrBool vsync) +{ + if (!hmd) return; + + return ((HMDState*)hmd)->TimeManager.SetVsync(vsync? true : false); +} + + +OVR_EXPORT ovrFrameTiming ovrHmd_BeginFrame(ovrHmd hmd, unsigned int frameIndex) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) + { + ovrFrameTiming f; + memset(&f, 0, sizeof(f)); + return f; + } + + // Check: Proper configure and threading state for the call. + hmds->checkRenderingConfigured("ovrHmd_BeginFrame"); + OVR_ASSERT_LOG(hmds->BeginFrameCalled == false, ("ovrHmd_BeginFrame called multiple times.")); + ThreadChecker::Scope checkScope(&hmds->RenderAPIThreadChecker, "ovrHmd_BeginFrame"); + + hmds->BeginFrameCalled = true; + hmds->BeginFrameThreadId = OVR::GetCurrentThreadId(); + + return ovrHmd_BeginFrameTiming(hmd, frameIndex); +} + + +// Renders textures to frame buffer +OVR_EXPORT void ovrHmd_EndFrame(ovrHmd hmd) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return; + + // Debug state checks: Must be in BeginFrame, on the same thread. + hmds->checkBeginFrameScope("ovrHmd_EndFrame"); + ThreadChecker::Scope checkScope(&hmds->RenderAPIThreadChecker, "ovrHmd_EndFrame"); + + // TBD: Move directly into renderer + bool dk2LatencyTest = (hmds->HMDInfo.HmdType == HmdType_DK2) && + (hmds->EnabledHmdCaps & ovrHmdCap_LatencyTest); + if (dk2LatencyTest) + { + hmds->LatencyTest2DrawColor[0] = hmds->TimeManager.GetFrameLatencyTestDrawColor(); + hmds->LatencyTest2DrawColor[1] = hmds->LatencyTest2DrawColor[0]; + hmds->LatencyTest2DrawColor[2] = hmds->LatencyTest2DrawColor[0]; + } + + if (hmds->pRenderer) + { + hmds->pRenderer->SaveGraphicsState(); + hmds->pRenderer->EndFrame(true, + hmds->LatencyTestActive ? hmds->LatencyTestDrawColor : NULL, + + // MA: Use this color since we are running DK2 test all the time. + dk2LatencyTest ? hmds->LatencyTest2DrawColor : 0 + //hmds->LatencyTest2Active ? hmds->LatencyTest2DrawColor : NULL + ); + hmds->pRenderer->RestoreGraphicsState(); + } + // Call after present + ovrHmd_EndFrameTiming(hmd); + + if (dk2LatencyTest) + { + hmds->TimeManager.UpdateFrameLatencyTrackingAfterEndFrame( + hmds->LatencyTest2DrawColor[0], hmds->LatencyUtil2.GetLocklessState()); + } + + // Out of BeginFrame + hmds->BeginFrameThreadId = 0; + hmds->BeginFrameCalled = false; +} + + +OVR_EXPORT ovrPosef ovrHmd_BeginEyeRender(ovrHmd hmd, ovrEyeType eye) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return ovrPosef(); + return hmds->BeginEyeRender(eye); +} + +OVR_EXPORT void ovrHmd_EndEyeRender(ovrHmd hmd, ovrEyeType eye, + ovrPosef renderPose, ovrTexture* eyeTexture) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return; + hmds->EndEyeRender(eye, renderPose, eyeTexture); +} + + +//------------------------------------------------------------------------------------- +// ***** Frame Timing logic + + +OVR_EXPORT ovrFrameTiming ovrHmd_GetFrameTiming(ovrHmd hmd, unsigned int frameIndex) +{ + ovrFrameTiming f; + memset(&f, 0, sizeof(f)); + + HMDState* hmds = (HMDState*)hmd; + if (hmds) + { + FrameTimeManager::Timing frameTiming = hmds->TimeManager.GetFrameTiming(frameIndex); + + f.ThisFrameSeconds = frameTiming.ThisFrameTime; + f.NextFrameSeconds = frameTiming.NextFrameTime; + f.TimewarpPointSeconds = frameTiming.TimewarpPointTime; + f.ScanoutMidpointSeconds= frameTiming.MidpointTime; + f.EyeScanoutSeconds[0] = frameTiming.EyeRenderTimes[0]; + f.EyeScanoutSeconds[1] = frameTiming.EyeRenderTimes[1]; + + // Compute DeltaSeconds. + f.DeltaSeconds = (hmds->LastGetFrameTimeSeconds == 0.0f) ? 0.0f : + (float) (f.ThisFrameSeconds - hmds->LastFrameTimeSeconds); + hmds->LastGetFrameTimeSeconds = f.ThisFrameSeconds; + if (f.DeltaSeconds > 1.0f) + f.DeltaSeconds = 1.0f; + } + + return f; +} + +OVR_EXPORT ovrFrameTiming ovrHmd_BeginFrameTiming(ovrHmd hmd, unsigned int frameIndex) +{ + ovrFrameTiming f; + memset(&f, 0, sizeof(f)); + + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return f; + + // Check: Proper state for the call. + OVR_ASSERT_LOG(hmds->BeginFrameTimingCalled == false, + ("ovrHmd_BeginFrameTiming called multiple times.")); + hmds->BeginFrameTimingCalled = true; + + double thisFrameTime = hmds->TimeManager.BeginFrame(frameIndex); + + const FrameTimeManager::Timing &frameTiming = hmds->TimeManager.GetFrameTiming(); + + f.ThisFrameSeconds = thisFrameTime; + f.NextFrameSeconds = frameTiming.NextFrameTime; + f.TimewarpPointSeconds = frameTiming.TimewarpPointTime; + f.ScanoutMidpointSeconds= frameTiming.MidpointTime; + f.EyeScanoutSeconds[0] = frameTiming.EyeRenderTimes[0]; + f.EyeScanoutSeconds[1] = frameTiming.EyeRenderTimes[1]; + + // Compute DeltaSeconds. + f.DeltaSeconds = (hmds->LastFrameTimeSeconds == 0.0f) ? 0.0f : + (float) (thisFrameTime - hmds->LastFrameTimeSeconds); + hmds->LastFrameTimeSeconds = thisFrameTime; + if (f.DeltaSeconds > 1.0f) + f.DeltaSeconds = 1.0f; + + return f; +} + + +OVR_EXPORT void ovrHmd_EndFrameTiming(ovrHmd hmd) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return; + + // Debug state checks: Must be in BeginFrameTiming, on the same thread. + hmds->checkBeginFrameTimingScope("ovrHmd_EndTiming"); + // MA TBD: Correct chek or not? + // ThreadChecker::Scope checkScope(&hmds->RenderAPIThreadChecker, "ovrHmd_EndFrame"); + + hmds->TimeManager.EndFrame(); + hmds->BeginFrameTimingCalled = false; +} + + +OVR_EXPORT void ovrHmd_ResetFrameTiming(ovrHmd hmd, unsigned int frameIndex) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return; + + hmds->TimeManager.ResetFrameTiming(frameIndex, + false, + hmds->RenderingConfigured); + hmds->LastFrameTimeSeconds = 0.0; + hmds->LastGetFrameTimeSeconds = 0.0; +} + + + +OVR_EXPORT ovrPosef ovrHmd_GetEyePose(ovrHmd hmd, ovrEyeType eye) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmds) return ovrPosef(); + + hmds->checkBeginFrameTimingScope("ovrHmd_GetEyePose"); + return hmds->TimeManager.GetEyePredictionPose(hmd, eye); +} + + +OVR_EXPORT void ovrHmd_GetEyeTimewarpMatrices(ovrHmd hmd, ovrEyeType eye, + ovrPosef renderPose, ovrMatrix4f twmOut[2]) +{ + HMDState* hmds = (HMDState*)hmd; + if (!hmd) + return; + + // Debug checks: BeginFrame was called, on the same thread. + hmds->checkBeginFrameTimingScope("ovrHmd_GetTimewarpEyeMatrices"); + + hmds->TimeManager.GetTimewarpMatrices(hmd, eye, renderPose, twmOut); + + /* + // MA: Took this out because new latency test approach just sames + // the sample times in FrameTimeManager. + // TODO: if no timewarp, then test latency in begin eye render + if (eye == 0) + { + hmds->ProcessLatencyTest2(hmds->LatencyTest2DrawColor, -1.0f); + } + */ +} + + + +OVR_EXPORT ovrEyeRenderDesc ovrHmd_GetRenderDesc(ovrHmd hmd, + ovrEyeType eyeType, ovrFovPort fov) +{ + ovrEyeRenderDesc erd; + + HMDState* hmds = (HMDState*)hmd; + if (!hmds) + { + memset(&erd, 0, sizeof(erd)); + return erd; + } + + return hmds->RenderState.calcRenderDesc(eyeType, fov); +} + + + +#define OVR_OFFSET_OF(s, field) ((size_t)&((s*)0)->field) + + + +// Generate distortion mesh per eye. +// scaleAndOffsetOut - this will be needed for shader +OVR_EXPORT ovrBool ovrHmd_CreateDistortionMesh( ovrHmd hmd, + ovrEyeType eyeType, ovrFovPort fov, + unsigned int distortionCaps, + ovrDistortionMesh *meshData ) +{ + if (!meshData) + return 0; + HMDState* hmds = (HMDState*)hmd; + + // Not used now, but Chromatic flag or others could possibly be checked for in the future. + OVR_UNUSED1(distortionCaps); + +#if defined (OVR_OS_WIN32) + // TBD: We should probably be sharing some C API structures with C++ to avoid this mess... + OVR_COMPILER_ASSERT(sizeof(DistortionMeshVertexData) == sizeof(ovrDistortionVertex)); + OVR_COMPILER_ASSERT(OVR_OFFSET_OF(DistortionMeshVertexData, ScreenPosNDC) == OVR_OFFSET_OF(ovrDistortionVertex, Pos)); + OVR_COMPILER_ASSERT(OVR_OFFSET_OF(DistortionMeshVertexData, TimewarpLerp) == OVR_OFFSET_OF(ovrDistortionVertex, TimeWarpFactor)); + OVR_COMPILER_ASSERT(OVR_OFFSET_OF(DistortionMeshVertexData, Shade) == OVR_OFFSET_OF(ovrDistortionVertex, VignetteFactor)); + OVR_COMPILER_ASSERT(OVR_OFFSET_OF(DistortionMeshVertexData, TanEyeAnglesR) == OVR_OFFSET_OF(ovrDistortionVertex, TexR)); + OVR_COMPILER_ASSERT(OVR_OFFSET_OF(DistortionMeshVertexData, TanEyeAnglesG) == OVR_OFFSET_OF(ovrDistortionVertex, TexG)); + OVR_COMPILER_ASSERT(OVR_OFFSET_OF(DistortionMeshVertexData, TanEyeAnglesB) == OVR_OFFSET_OF(ovrDistortionVertex, TexB)); +#endif + + + // *** Calculate a part of "StereoParams" needed for mesh generation + + // Note that mesh distortion generation is invariant of RenderTarget UVs, allowing + // render target size and location to be changed after the fact dynamically. + // eyeToSourceUV is computed here for convenience, so that users don't need + // to call ovrHmd_GetRenderScaleAndOffset unless changing RT dynamically. + + const HmdRenderInfo& hmdri = hmds->RenderState.RenderInfo; + StereoEye stereoEye = (eyeType == ovrEye_Left) ? StereoEye_Left : StereoEye_Right; + + const DistortionRenderDesc& distortion = hmds->RenderState.Distortion[eyeType]; + + // Find the mapping from TanAngle space to target NDC space. + ScaleAndOffset2D eyeToSourceNDC = CreateNDCScaleAndOffsetFromFov(fov); + + int triangleCount = 0; + int vertexCount = 0; + + DistortionMeshCreate((DistortionMeshVertexData**)&meshData->pVertexData, (UInt16**)&meshData->pIndexData, + &vertexCount, &triangleCount, + (stereoEye == StereoEye_Right), + hmdri, distortion, eyeToSourceNDC); + + if (meshData->pVertexData) + { + // Convert to index + meshData->IndexCount = triangleCount * 3; + meshData->VertexCount = vertexCount; + return 1; + } + + return 0; +} + + +// Frees distortion mesh allocated by ovrHmd_GenerateDistortionMesh. meshData elements +// are set to null and 0s after the call. +OVR_EXPORT void ovrHmd_DestroyDistortionMesh(ovrDistortionMesh* meshData) +{ + if (meshData->pVertexData) + DistortionMeshDestroy((DistortionMeshVertexData*)meshData->pVertexData, + meshData->pIndexData); + meshData->pVertexData = 0; + meshData->pIndexData = 0; + meshData->VertexCount = 0; + meshData->IndexCount = 0; +} + + + +// Computes updated 'uvScaleOffsetOut' to be used with a distortion if render target size or +// viewport changes after the fact. This can be used to adjust render size every frame, if desired. +OVR_EXPORT void ovrHmd_GetRenderScaleAndOffset( ovrFovPort fov, + ovrSizei textureSize, ovrRecti renderViewport, + ovrVector2f uvScaleOffsetOut[2] ) +{ + // Find the mapping from TanAngle space to target NDC space. + ScaleAndOffset2D eyeToSourceNDC = CreateNDCScaleAndOffsetFromFov(fov); + // Find the mapping from TanAngle space to textureUV space. + ScaleAndOffset2D eyeToSourceUV = CreateUVScaleAndOffsetfromNDCScaleandOffset( + eyeToSourceNDC, + renderViewport, textureSize ); + + uvScaleOffsetOut[0] = eyeToSourceUV.Scale; + uvScaleOffsetOut[1] = eyeToSourceUV.Offset; +} + + +//------------------------------------------------------------------------------------- +// ***** Latency Test interface + +OVR_EXPORT ovrBool ovrHmd_GetLatencyTestDrawColor(ovrHmd hmd, unsigned char rgbColorOut[3]) +{ + HMDState* p = (HMDState*)hmd; + rgbColorOut[0] = p->LatencyTestDrawColor[0]; + rgbColorOut[1] = p->LatencyTestDrawColor[1]; + rgbColorOut[2] = p->LatencyTestDrawColor[2]; + return p->LatencyTestActive; +} + +OVR_EXPORT const char* ovrHmd_GetLatencyTestResult(ovrHmd hmd) +{ + HMDState* p = (HMDState*)hmd; + return p->LatencyUtil.GetResultsString(); +} + +OVR_EXPORT double ovrHmd_GetMeasuredLatencyTest2(ovrHmd hmd) +{ + HMDState* p = (HMDState*)hmd; + + // MA Test + float latencies[3]; + p->TimeManager.GetLatencyTimings(latencies); + return latencies[2]; + // return p->LatencyUtil2.GetMeasuredLatency(); +} + + +// ----------------------------------------------------------------------------------- +// ***** Property Access + +OVR_EXPORT float ovrHmd_GetFloat(ovrHmd hmd, const char* propertyName, float defaultVal) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds) + { + return hmds->getFloatValue(propertyName, defaultVal); + } + + return defaultVal; +} + +OVR_EXPORT ovrBool ovrHmd_SetFloat(ovrHmd hmd, const char* propertyName, float value) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds) + { + return hmds->setFloatValue(propertyName, value); + } + return false; +} + + + +OVR_EXPORT unsigned int ovrHmd_GetFloatArray(ovrHmd hmd, const char* propertyName, + float values[], unsigned int arraySize) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds) + { + return hmds->getFloatArray(propertyName, values, arraySize); + } + + return 0; +} + + +// Modify float[] property; false if property doesn't exist or is readonly. +OVR_EXPORT ovrBool ovrHmd_SetFloatArray(ovrHmd hmd, const char* propertyName, + float values[], unsigned int arraySize) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds) + { + return hmds->setFloatArray(propertyName, values, arraySize); + } + + return 0; +} + +OVR_EXPORT const char* ovrHmd_GetString(ovrHmd hmd, const char* propertyName, + const char* defaultVal) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds) + { + return hmds->getString(propertyName, defaultVal); + } + + return defaultVal; +} + +/* Not needed yet. + +// Get array of strings, i.e. const char* [] property. +// Returns the number of elements filled in, 0 if property doesn't exist. +// Maximum of arraySize elements will be written. +// String memory is guaranteed to exist until next call to GetString or GetStringArray, or HMD is destroyed. +OVR_EXPORT +unsigned int ovrHmd_GetStringArray(ovrHmd hmd, const char* propertyName, + const char* values[], unsigned int arraySize) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds && hmds->pHMD && arraySize) + { + Profile* p = hmds->pHMD->GetProfile(); + + hmds->LastGetStringValue[0] = 0; + if (p && p->GetValue(propertyName, hmds->LastGetStringValue, sizeof(hmds->LastGetStringValue))) + { + values[0] = hmds->LastGetStringValue; + return 1; + } + } + + return 0; +} +*/ + +// Returns array size of a property, 0 if property doesn't exist. +// Can be used to check existence of a property. +OVR_EXPORT unsigned int ovrHmd_GetArraySize(ovrHmd hmd, const char* propertyName) +{ + HMDState* hmds = (HMDState*)hmd; + if (hmds && hmds->pHMD) + { + // For now, just access the profile. + Profile* p = hmds->pHMD->GetProfile(); + + if (p) + return p->GetNumValues(propertyName); + } + return 0; +} + + +#ifdef __cplusplus +} // extern "C" +#endif + + +//------------------------------------------------------------------------------------- +// ****** Special access for VRConfig + +// Return the sensor fusion object for the purposes of magnetometer calibration. The +// function is private and is only exposed through VRConfig header declarations +OVR::SensorFusion* ovrHmd_GetSensorFusion(ovrHmd hmd) +{ + HMDState* p = (HMDState*)hmd; + return &p->SFusion; +} + + diff --git a/LibOVR/Src/OVR_CAPI.h b/LibOVR/Src/OVR_CAPI.h new file mode 100644 index 0000000..ec4708c --- /dev/null +++ b/LibOVR/Src/OVR_CAPI.h @@ -0,0 +1,790 @@ +/************************************************************************************ + +Filename : OVR_CAPI.h +Content : C Interface to Oculus sensors and rendering. +Created : November 23, 2013 +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. + +************************************************************************************/ +#ifndef OVR_CAPI_h +#define OVR_CAPI_h + +#include <stdint.h> + +typedef char ovrBool; + +//----------------------------------------------------------------------------------- +// ***** OVR_EXPORT definition + +#if !defined(OVR_EXPORT) + #if defined(WIN32) + #define OVR_EXPORT __declspec(dllexport) + #else + #define OVR_EXPORT + #endif +#endif + +//----------------------------------------------------------------------------------- +// ***** Simple Math Structures + +// 2D integer +typedef struct ovrVector2i_ +{ + int x, y; +} ovrVector2i; +typedef struct ovrSizei_ +{ + int w, h; +} ovrSizei; +typedef struct ovrRecti_ +{ + ovrVector2i Pos; + ovrSizei Size; +} ovrRecti; + +// 3D +typedef struct ovrQuatf_ +{ + float x, y, z, w; +} ovrQuatf; +typedef struct ovrVector2f_ +{ + float x, y; +} ovrVector2f; +typedef struct ovrVector3f_ +{ + float x, y, z; +} ovrVector3f; +typedef struct ovrMatrix4f_ +{ + float M[4][4]; +} ovrMatrix4f; +// Position and orientation together. +typedef struct ovrPosef_ +{ + ovrQuatf Orientation; + ovrVector3f Position; +} ovrPosef; + +// Full pose (rigid body) configuration with first and second derivatives. +typedef struct ovrPoseStatef_ +{ + ovrPosef Pose; + ovrVector3f AngularVelocity; + ovrVector3f LinearVelocity; + ovrVector3f AngularAcceleration; + ovrVector3f LinearAcceleration; + double TimeInSeconds; // Absolute time of this state sample. +} ovrPoseStatef; + +// Field Of View (FOV) in tangent of the angle units. +// As an example, for a standard 90 degree vertical FOV, we would +// have: { UpTan = tan(90 degrees / 2), DownTan = tan(90 degrees / 2) }. +typedef struct ovrFovPort_ +{ + float UpTan; + float DownTan; + float LeftTan; + float RightTan; +} ovrFovPort; + + +//----------------------------------------------------------------------------------- +// ***** HMD Types + +// Enumerates all HMD types that we support. +typedef enum +{ + ovrHmd_None = 0, + ovrHmd_DK1 = 3, + ovrHmd_DKHD = 4, + ovrHmd_CrystalCoveProto = 5, + ovrHmd_DK2 = 6, + ovrHmd_Other // Some HMD other then the one in the enumeration. +} ovrHmdType; + +// HMD capability bits reported by device. +// +typedef enum +{ + // Read-only flags. + ovrHmdCap_Present = 0x0001, // This HMD exists (as opposed to being unplugged). + ovrHmdCap_Available = 0x0002, // HMD and is sensor is available for use + // (if not owned by another app). + + // These flags are intended for use with the new driver display mode. + /* + ovrHmdCap_ExtendDesktop = 0x0004, // Read only, means display driver is in compatibility mode. + + ovrHmdCap_DisplayOff = 0x0040, // Turns off Oculus HMD screen and output. + ovrHmdCap_NoMirrorToWindow = 0x2000, // Disables mirrowing of HMD output to the window; + // may improve rendering performance slightly. + */ + + // Modifiable flags (through ovrHmd_SetEnabledCaps). + ovrHmdCap_LowPersistence = 0x0080, // Supports low persistence mode. + ovrHmdCap_LatencyTest = 0x0100, // Supports pixel reading for continuous latency testing. + ovrHmdCap_DynamicPrediction = 0x0200, // Adjust prediction dynamically based on DK2 Latency. + // Support rendering without VSync for debugging + ovrHmdCap_NoVSync = 0x1000, + ovrHmdCap_NoRestore = 0x4000, + + // These bits can be modified by ovrHmd_SetEnabledCaps. + ovrHmdCap_Writable_Mask = 0x1380 +} ovrHmdCaps; + + +// Sensor capability bits reported by device. +// Used with ovrHmd_StartSensor. +typedef enum +{ + ovrSensorCap_Orientation = 0x0010, // Supports orientation tracking (IMU). + ovrSensorCap_YawCorrection = 0x0020, // Supports yaw correction through magnetometer or other means. + ovrSensorCap_Position = 0x0040, // Supports positional tracking. + +} ovrSensorCaps; + +// Distortion capability bits reported by device. +// Used with ovrHmd_ConfigureRendering and ovrHmd_CreateDistortionMesh. +typedef enum +{ + ovrDistortionCap_Chromatic = 0x01, // Supports chromatic aberration correction. + ovrDistortionCap_TimeWarp = 0x02, // Supports timewarp. + ovrDistortionCap_Vignette = 0x08 // Supports vignetting around the edges of the view. +} ovrDistortionCaps; + + +// Specifies which eye is being used for rendering. +// This type explicitly does not include a third "NoStereo" option, as such is +// not required for an HMD-centered API. +typedef enum +{ + ovrEye_Left = 0, + ovrEye_Right = 1, + ovrEye_Count = 2 +} ovrEyeType; + + +// Handle to HMD; returned by ovrHmd_Create. +typedef struct ovrHmdStruct* ovrHmd; + +// This is a complete descriptor of the HMD. +typedef struct ovrHmdDesc_ +{ + ovrHmd Handle; // Handle of this HMD. + ovrHmdType Type; + + // Name string describing the product: "Oculus Rift DK1", etc. + const char* ProductName; + const char* Manufacturer; + + // Capability bits described by ovrHmdCaps. + unsigned int HmdCaps; + // Capability bits described by ovrSensorCaps. + unsigned int SensorCaps; + // Capability bits described by ovrDistortionCaps. + unsigned int DistortionCaps; + + // Resolution of the entire HMD screen (for both eyes) in pixels. + ovrSizei Resolution; + // Where monitor window should be on screen or (0,0). + ovrVector2i WindowsPos; + + // These define the recommended and maximum optical FOVs for the HMD. + ovrFovPort DefaultEyeFov[ovrEye_Count]; + ovrFovPort MaxEyeFov[ovrEye_Count]; + + // Preferred eye rendering order for best performance. + // Can help reduce latency on sideways-scanned screens. + ovrEyeType EyeRenderOrder[ovrEye_Count]; + + // Display that HMD should present on. + // TBD: It may be good to remove this information relying on WidowPos instead. + // Ultimately, we may need to come up with a more convenient alternative, + // such as a API-specific functions that return adapter, ot something that will + // work with our monitor driver. + + // Windows: "\\\\.\\DISPLAY3", etc. Can be used in EnumDisplaySettings/CreateDC. + const char* DisplayDeviceName; + // MacOS + int DisplayId; +} ovrHmdDesc; + +// Describes the type of positional tracking being done. +/* +typedef enum +{ + ovrPose_None, + ovrPose_HeadModel, + ovrPose_Positional +} ovrPoseType; +*/ + + +// Bit flags describing the current status of sensor tracking. +typedef enum +{ + ovrStatus_OrientationTracked = 0x0001, // Orientation is currently tracked (connected and in use). + ovrStatus_PositionTracked = 0x0002, // Position is currently tracked (FALSE if out of range). + ovrStatus_PositionConnected = 0x0020, // Position tracking HW is connected. + ovrStatus_HmdConnected = 0x0080 // HMD Display is available & connected. +} ovrStatusBits; + + +// State of the sensor at a given absolute time. +typedef struct ovrSensorState_ +{ + // Predicted pose configuration at requested absolute time. + // One can determine the time difference between predicted and actual + // readings by comparing ovrPoseState.TimeInSeconds. + ovrPoseStatef Predicted; + // Actual recorded pose configuration based on the sensor sample at a + // moment closest to the requested time. + ovrPoseStatef Recorded; + + // Sensor temperature reading, in degrees Celsius, as sample time. + float Temperature; + // Sensor status described by ovrStatusBits. + unsigned int StatusFlags; +} ovrSensorState; + +// For now. +// TBD: Decide if this becomes a part of HMDDesc +typedef struct ovrSensorDesc_ +{ + // HID Vendor and ProductId of the device. + short VendorId; + short ProductId; + // Sensor (and display) serial number. + char SerialNumber[24]; +} ovrSensorDesc; + + + +// Frame data reported by ovrHmd_BeginFrameTiming(). +typedef struct ovrFrameTiming_ +{ + // The amount of time that has passed since the previous frame returned + // BeginFrameSeconds value, usable for movement scaling. + // This will be clamped to no more than 0.1 seconds to prevent + // excessive movement after pauses for loading or initialization. + float DeltaSeconds; + + // It is generally expected that the following hold: + // ThisFrameSeconds < TimewarpPointSeconds < NextFrameSeconds < + // EyeScanoutSeconds[EyeOrder[0]] <= ScanoutMidpointSeconds <= EyeScanoutSeconds[EyeOrder[1]] + + // Absolute time value of when rendering of this frame began or is expected to + // begin; generally equal to NextFrameSeconds of the previous frame. Can be used + // for animation timing. + double ThisFrameSeconds; + // Absolute point when IMU expects to be sampled for this frame. + double TimewarpPointSeconds; + // Absolute time when frame Present + GPU Flush will finish, and the next frame starts. + double NextFrameSeconds; + + // Time when when half of the screen will be scanned out. Can be passes as a prediction + // value to ovrHmd_GetSensorState() go get general orientation. + double ScanoutMidpointSeconds; + // Timing points when each eye will be scanned out to display. Used for rendering each eye. + double EyeScanoutSeconds[2]; + +} ovrFrameTiming; + + + +// Rendering information for each eye, computed by either ovrHmd_ConfigureRendering(). +// or ovrHmd_GetRenderDesc() based on the specified Fov. +// Note that the rendering viewport is not included here as it can be +// specified separately and modified per frame though: +// (a) calling ovrHmd_GetRenderScaleAndOffset with game-rendered api, +// or (b) passing different values in ovrTexture in case of SDK-rendered distortion. +typedef struct ovrEyeRenderDesc_ +{ + ovrEyeType Eye; + ovrFovPort Fov; + ovrRecti DistortedViewport; // Distortion viewport + ovrVector2f PixelsPerTanAngleAtCenter; // How many display pixels will fit in tan(angle) = 1. + ovrVector3f ViewAdjust; // Translation to be applied to view matrix. +} ovrEyeRenderDesc; + + +//----------------------------------------------------------------------------------- +// ***** Platform-independent Rendering Configuration + +// These types are used to hide platform-specific details when passing +// render device, OS and texture data to the APIs. +// +// The benefit of having these wrappers vs. platform-specific API functions is +// that they allow game glue code to be portable. A typical example is an +// engine that has multiple back ends, say GL and D3D. Portable code that calls +// these back ends may also use LibOVR. To do this, back ends can be modified +// to return portable types such as ovrTexture and ovrRenderAPIConfig. + +typedef enum +{ + ovrRenderAPI_None, + ovrRenderAPI_OpenGL, + ovrRenderAPI_Android_GLES, // May include extra native window pointers, etc. + ovrRenderAPI_D3D9, + ovrRenderAPI_D3D10, + ovrRenderAPI_D3D11, + ovrRenderAPI_Count +} ovrRenderAPIType; + +// Platform-independent part of rendering API-configuration data. +// It is a part of ovrRenderAPIConfig, passed to ovrHmd_Configure. +typedef struct ovrRenderAPIConfigHeader_ +{ + ovrRenderAPIType API; + ovrSizei RTSize; + int Multisample; +} ovrRenderAPIConfigHeader; + +typedef struct ovrRenderAPIConfig_ +{ + ovrRenderAPIConfigHeader Header; + uintptr_t PlatformData[8]; +} ovrRenderAPIConfig; + +// Platform-independent part of eye texture descriptor. +// It is a part of ovrTexture, passed to ovrHmd_EndFrame. +// - If RenderViewport is all zeros, will be used. +typedef struct ovrTextureHeader_ +{ + ovrRenderAPIType API; + ovrSizei TextureSize; + ovrRecti RenderViewport; // Pixel viewport in texture that holds eye image. +} ovrTextureHeader; + +typedef struct ovrTexture_ +{ + ovrTextureHeader Header; + uintptr_t PlatformData[8]; +} ovrTexture; + + +// ----------------------------------------------------------------------------------- +// ***** API Interfaces + +// Basic steps to use the API: +// +// Setup: +// 1. ovrInitialize(); +// 2. ovrHMD hmd = ovrHmd_Create(0); ovrHmd_GetDesc(hmd, &hmdDesc); +// 3. Use hmdDesc and ovrHmd_GetFovTextureSize() to determine graphics configuration. +// 4. Call ovrHmd_StartSensor() to configure and initialize tracking. +// 5. Call ovrHmd_ConfigureRendering() to setup graphics for SDK rendering, +// which is the preferred approach. +// Please refer to "Game-Side Rendering" below if you prefer to do that instead. +// 5. Allocate textures as needed. +// +// Game Loop: +// 6. Call ovrHmd_BeginFrame() to get frame timing and orientation information. +// 7. Render each eye in between ovrHmd_BeginEyeRender and ovrHmd_EndEyeRender calls, +// providing the result texture to the API. +// 8. Call ovrHmd_EndFrame() to render distorted textures to the back buffer +// and present them on the Hmd. +// +// Shutdown: +// 9. ovrHmd_Destroy(hmd) +// 10. ovr_Shutdown() +// + +#ifdef __cplusplus +extern "C" { +#endif + +// Library init/shutdown, must be called around all other OVR code. +// No other functions calls are allowed before ovr_Initialize succeeds or after ovr_Shutdown. +OVR_EXPORT ovrBool ovr_Initialize(); +OVR_EXPORT void ovr_Shutdown(); + + +// Detects or re-detects HMDs and reports the total number detected. +// Users can get information about each HMD by calling ovrHmd_Create with an index. +OVR_EXPORT int ovrHmd_Detect(); + + +// Creates a handle to an HMD and optionally fills in data about it. +// Index can [0 .. ovrHmd_Detect()-1]; index mappings can cange after each ovrHmd_Detect call. +// If not null, returned handle must be freed with ovrHmd_Destroy. +OVR_EXPORT ovrHmd ovrHmd_Create(int index); +OVR_EXPORT void ovrHmd_Destroy(ovrHmd hmd); + +// Creates a "fake" HMD used for debugging only. This is not tied to specific hardware, +// but may be used to debug some of the related rendering. +OVR_EXPORT ovrHmd ovrHmd_CreateDebug(ovrHmdType type); + + +// Returns last error for HMD state. Returns null for no error. +// String is valid until next call or GetLastError or HMD is destroyed. +// Pass null hmd to get global error (for create, etc). +OVR_EXPORT const char* ovrHmd_GetLastError(ovrHmd hmd); + + +//------------------------------------------------------------------------------------- + +// Returns capability bits that are enabled at this time; described by ovrHmdCaps. +// Note that this value is different font ovrHmdDesc::HmdCaps, which describes what +// capabilities are available. +OVR_EXPORT unsigned int ovrHmd_GetEnabledCaps(ovrHmd hmd); + +// Modifies capability bits described by ovrHmdCaps that can be modified, +// such as ovrHmd_LowPersistance. +OVR_EXPORT void ovrHmd_SetEnabledCaps(ovrHmd hmd, unsigned int hmdCaps); + + +//------------------------------------------------------------------------------------- +// ***** Sensor Interface + +// All sensor interface functions are thread-safe, allowing sensor state to be sampled +// from different threads. +// Starts sensor sampling, enabling specified capabilities, described by ovrSensorCaps. +// - supportedSensorCaps specifies support that is requested. The function will succeed +// even if these caps are not available (i.e. sensor or camera is unplugged). Support +// will automatically be enabled if such device is plugged in later. Software should +// check ovrSensorState.StatusFlags for real-time status. +// - requiredSensorCaps specify sensor capabilities required at the time of the call. +// If they are not available, the function will fail. Pass 0 if only specifying +// supportedSensorCaps. +OVR_EXPORT ovrBool ovrHmd_StartSensor(ovrHmd hmd, unsigned int supportedSensorCaps, + unsigned int requiredSensorCaps); +// Stops sensor sampling, shutting down internal resources. +OVR_EXPORT void ovrHmd_StopSensor(ovrHmd hmd); +// Resets sensor orientation. +OVR_EXPORT void ovrHmd_ResetSensor(ovrHmd hmd); + +// Returns sensor state reading based on the specified absolute system time. +// Pass absTime value of 0.0 to request the most recent sensor reading; in this case +// both PredictedPose and SamplePose will have the same value. +// ovrHmd_GetEyePredictedSensorState relies on this internally. +// This may also be used for more refined timing of FrontBuffer rendering logic, etc. +OVR_EXPORT ovrSensorState ovrHmd_GetSensorState(ovrHmd hmd, double absTime); + +// Returns information about a sensor. +// Only valid after StartSensor. +OVR_EXPORT ovrBool ovrHmd_GetSensorDesc(ovrHmd hmd, ovrSensorDesc* descOut); + + +//------------------------------------------------------------------------------------- +// ***** Graphics Setup + +// Fills in description about HMD; this is the same as filled in by ovrHmd_Create. +OVR_EXPORT void ovrHmd_GetDesc(ovrHmd hmd, ovrHmdDesc* desc); + +// Calculates texture size recommended for rendering one eye within HMD, given FOV cone. +// Higher FOV will generally require larger textures to maintain quality. +// - pixelsPerDisplayPixel specifies that number of render target pixels per display +// pixel at center of distortion; 1.0 is the default value. Lower values +// can improve performance. +OVR_EXPORT ovrSizei ovrHmd_GetFovTextureSize(ovrHmd hmd, ovrEyeType eye, ovrFovPort fov, + float pixelsPerDisplayPixel); + + + +//------------------------------------------------------------------------------------- +// ***** Rendering API Thread Safety + +// All of rendering APIs, inclusing Configure and frame functions are *NOT +// Thread Safe*. It is ok to use ConfigureRendering on one thread and handle +// frames on another thread, but explicit synchronization must be done since +// functions that depend on configured state are not reentrant. +// +// As an extra requirement, any of the following calls must be done on +// the render thread, which is the same thread that calls ovrHmd_BeginFrame +// or ovrHmd_BeginFrameTiming. +// - ovrHmd_EndFrame +// - ovrHmd_BeginEyeRender +// - ovrHmd_EndEyeRender +// - ovrHmd_GetFramePointTime +// - ovrHmd_GetEyePose +// - ovrHmd_GetEyeTimewarpMatrices + + +//------------------------------------------------------------------------------------- +// ***** SDK-Rendering Functions + +// These functions support rendering of distortion by the SDK through direct +// access to the underlying rendering HW, such as D3D or GL. +// This is the recommended approach, as it allows for better support or future +// Oculus hardware and a range of low-level optimizations. + + +// Configures rendering; fills in computed render parameters. +// This function can be called multiple times to change rendering settings. +// The users pass in two eye view descriptors that are used to +// generate complete rendering information for each eye in eyeRenderDescOut[2]. +// +// - apiConfig provides D3D/OpenGL specific parameters. Pass null +// to shutdown rendering and release all resources. +// - distortionCaps describe distortion settings that will be applied. +// +OVR_EXPORT ovrBool ovrHmd_ConfigureRendering( ovrHmd hmd, + const ovrRenderAPIConfig* apiConfig, + unsigned int distortionCaps, + const ovrFovPort eyeFovIn[2], + ovrEyeRenderDesc eyeRenderDescOut[2] ); + + +// Begins a frame, returning timing and orientation information useful for simulation. +// This should be called in the beginning of game rendering loop (on render thread). +// This function relies on ovrHmd_BeginFrameTiming for some of its functionality. +// Pass 0 for frame index if not using GetFrameTiming. +OVR_EXPORT ovrFrameTiming ovrHmd_BeginFrame(ovrHmd hmd, unsigned int frameIndex); + +// Ends frame, rendering textures to frame buffer. This may perform distortion and scaling +// internally, assuming is it not delegated to another thread. +// Must be called on the same thread as BeginFrame. Calls ovrHmd_BeginEndTiming internally. +// *** This Function will to Present/SwapBuffers and potentially wait for GPU Sync ***. +OVR_EXPORT void ovrHmd_EndFrame(ovrHmd hmd); + + +// Marks beginning of eye rendering. Must be called on the same thread as BeginFrame. +// This function uses ovrHmd_GetEyePose to predict sensor state that should be +// used rendering the specified eye. +// This combines current absolute time with prediction that is appropriate for this HMD. +// It is ok to call ovrHmd_BeginEyeRender() on both eyes before calling ovrHmd_EndEyeRender. +// If rendering one eye at a time, it is best to render eye specified by +// HmdDesc.EyeRenderOrder[0] first. +OVR_EXPORT ovrPosef ovrHmd_BeginEyeRender(ovrHmd hmd, ovrEyeType eye); + +// Marks the end of eye rendering and submits the eye texture for display after it is ready. +// Rendering viewport within the texture can change per frame if necessary. +// Specified texture may be presented immediately or wait until ovrHmd_EndFrame based +// on the implementation. The API performs distortion and scaling internally. +// 'renderPose' will typically be the value returned from ovrHmd_BeginEyeRender, but can +// be different if a different pose was used for rendering. +OVR_EXPORT void ovrHmd_EndEyeRender(ovrHmd hmd, ovrEyeType eye, + ovrPosef renderPose, ovrTexture* eyeTexture); + + + +//------------------------------------------------------------------------------------- +// ***** Game-Side Rendering Functions + +// These functions provide distortion data and render timing support necessary to allow +// game rendering of distortion. Game-side rendering involves the following steps: +// +// 1. Setup ovrEyeDesc based on desired texture size and Fov. +// Call ovrHmd_GetRenderDesc to get the necessary rendering parameters for each eye. +// +// 2. Use ovrHmd_CreateDistortionMesh to generate distortion mesh. +// +// 3. Use ovrHmd_BeginFrameTiming, ovrHmd_GetEyePose and ovrHmd_BeginFrameTiming +// in the rendering loop to obtain timing and predicted view orientation for +// each eye. +// - If relying on timewarp, use ovr_WaitTillTime after rendering+flush, followed +// by ovrHmd_GetEyeTimewarpMatrices to obtain timewarp matrices used +// in distortion pixel shader to reduce latency. +// + +// Computes distortion viewport, view adjust and other rendering for the specified +// eye. This can be used instead of ovrHmd_ConfigureRendering to help setup rendering on +// the game side. +OVR_EXPORT ovrEyeRenderDesc ovrHmd_GetRenderDesc(ovrHmd hmd, + ovrEyeType eyeType, ovrFovPort fov); + + +// Describes a vertex used for distortion; this is intended to be converted into +// the engine-specific format. +// Some fields may be unused based on ovrDistortionCaps selected. TexG and TexB, for example, +// are not used if chromatic correction is not requested. +typedef struct ovrDistortionVertex_ +{ + ovrVector2f Pos; + float TimeWarpFactor; // Lerp factor between time-warp matrices. Can be encoded in Pos.z. + float VignetteFactor; // Vignette fade factor. Can be encoded in Pos.w. + ovrVector2f TexR; + ovrVector2f TexG; + ovrVector2f TexB; +} ovrDistortionVertex; + +// Describes a full set of distortion mesh data, filled in by ovrHmd_CreateDistortionMesh. +// Contents of this data structure, if not null, should be freed by ovrHmd_DestroyDistortionMesh. +typedef struct ovrDistortionMesh_ +{ + ovrDistortionVertex* pVertexData; + unsigned short* pIndexData; + unsigned int VertexCount; + unsigned int IndexCount; +} ovrDistortionMesh; + +// Generate distortion mesh per eye. +// Distortion capabilities will depend on 'distortionCaps' flags; user should rely on +// appropriate shaders based on their settings. +// Distortion mesh data will be allocated and stored into the ovrDistortionMesh data structure, +// which should be explicitly freed with ovrHmd_DestroyDistortionMesh. +// Users should call ovrHmd_GetRenderScaleAndOffset to get uvScale and Offset values for rendering. +// The function shouldn't fail unless theres is a configuration or memory error, in which case +// ovrDistortionMesh values will be set to null. +OVR_EXPORT ovrBool ovrHmd_CreateDistortionMesh( ovrHmd hmd, + ovrEyeType eyeType, ovrFovPort fov, + unsigned int distortionCaps, + ovrDistortionMesh *meshData ); + +// Frees distortion mesh allocated by ovrHmd_GenerateDistortionMesh. meshData elements +// are set to null and zeroes after the call. +OVR_EXPORT void ovrHmd_DestroyDistortionMesh( ovrDistortionMesh* meshData ); + +// Computes updated 'uvScaleOffsetOut' to be used with a distortion if render target size or +// viewport changes after the fact. This can be used to adjust render size every frame, if desired. +OVR_EXPORT void ovrHmd_GetRenderScaleAndOffset( ovrFovPort fov, + ovrSizei textureSize, ovrRecti renderViewport, + ovrVector2f uvScaleOffsetOut[2] ); + + +// Thread-safe timing function for the main thread. Caller should increment frameIndex +// with every frame and pass the index to RenderThread for processing. +OVR_EXPORT ovrFrameTiming ovrHmd_GetFrameTiming(ovrHmd hmd, unsigned int frameIndex); + +// Called at the beginning of the frame on the Render Thread. +// Pass frameIndex == 0 if ovrHmd_GetFrameTiming isn't being used. Otherwise, +// pass the same frame index as was used for GetFrameTiming on the main thread. +OVR_EXPORT ovrFrameTiming ovrHmd_BeginFrameTiming(ovrHmd hmd, unsigned int frameIndex); + +// Marks the end of game-rendered frame, tracking the necessary timing information. This +// function must be called immediately after Present/SwapBuffers + GPU sync. GPU sync is important +// before this call to reduce latency and ensure proper timing. +OVR_EXPORT void ovrHmd_EndFrameTiming(ovrHmd hmd); + +// Initializes and resets frame time tracking. This is typically not necessary, but +// is helpful if game changes vsync state or video mode. vsync is assumed to be on if this +// isn't called. Resets internal frame index to the specified number. +OVR_EXPORT void ovrHmd_ResetFrameTiming(ovrHmd hmd, unsigned int frameIndex); + + +// Predicts and returns Pose that should be used rendering the specified eye. +// Must be called between ovrHmd_BeginFrameTiming & ovrHmd_EndFrameTiming. +OVR_EXPORT ovrPosef ovrHmd_GetEyePose(ovrHmd hmd, ovrEyeType eye); + +// Computes timewarp matrices used by distortion mesh shader, these are used to adjust +// for orientation change since the last call to ovrHmd_GetEyePose for this eye. +// The ovrDistortionVertex::TimeWarpFactor is used to blend between the matrices, +// usually representing two different sides of the screen. +// Must be called on the same thread as ovrHmd_BeginFrameTiming. +OVR_EXPORT void ovrHmd_GetEyeTimewarpMatrices(ovrHmd hmd, ovrEyeType eye, + ovrPosef renderPose, ovrMatrix4f twmOut[2]); + + + +//------------------------------------------------------------------------------------- +// ***** Stateless math setup functions + +// Used to generate projection from ovrEyeDesc::Fov. +OVR_EXPORT ovrMatrix4f ovrMatrix4f_Projection( ovrFovPort fov, + float znear, float zfar, ovrBool rightHanded ); + +// Used for 2D rendering, Y is down +// orthoScale = 1.0f / pixelsPerTanAngleAtCenter +// orthoDistance = distance from camera, such as 0.8m +OVR_EXPORT ovrMatrix4f ovrMatrix4f_OrthoSubProjection(ovrMatrix4f projection, ovrVector2f orthoScale, + float orthoDistance, float eyeViewAdjustX); + +// Returns global, absolute high-resolution time in seconds. This is the same +// value as used in sensor messages. +OVR_EXPORT double ovr_GetTimeInSeconds(); + +// Waits until the specified absolute time. +OVR_EXPORT double ovr_WaitTillTime(double absTime); + + + +// ----------------------------------------------------------------------------------- +// ***** Latency Test interface + +// Does latency test processing and returns 'TRUE' if specified rgb color should +// be used to clear the screen. +OVR_EXPORT ovrBool ovrHmd_ProcessLatencyTest(ovrHmd hmd, unsigned char rgbColorOut[3]); + +// Returns non-null string once with latency test result, when it is available. +// Buffer is valid until next call. +OVR_EXPORT const char* ovrHmd_GetLatencyTestResult(ovrHmd hmd); + +// Returns latency for HMDs that support internal latency testing via the +// pixel-read back method (-1 for invalid or N/A) +OVR_EXPORT double ovrHmd_GetMeasuredLatencyTest2(ovrHmd hmd); + + +// ----------------------------------------------------------------------------------- +// ***** Property Access + +// NOTICE: This is experimental part of API that is likely to go away or change. + +// These allow accessing different properties of the HMD and profile. +// Some of the properties may go away with profile/HMD versions, so software should +// use defaults and/or proper fallbacks. +// + +// For now, access profile entries; this will change. +#if !defined(OVR_KEY_USER) + + #define OVR_KEY_USER "User" + #define OVR_KEY_NAME "Name" + #define OVR_KEY_GENDER "Gender" + #define OVR_KEY_PLAYER_HEIGHT "PlayerHeight" + #define OVR_KEY_EYE_HEIGHT "EyeHeight" + #define OVR_KEY_IPD "IPD" + #define OVR_KEY_NECK_TO_EYE_HORIZONTAL "NeckEyeHori" + #define OVR_KEY_NECK_TO_EYE_VERTICAL "NeckEyeVert" + + #define OVR_DEFAULT_GENDER "Male" + #define OVR_DEFAULT_PLAYER_HEIGHT 1.778f + #define OVR_DEFAULT_EYE_HEIGHT 1.675f + #define OVR_DEFAULT_IPD 0.064f + #define OVR_DEFAULT_NECK_TO_EYE_HORIZONTAL 0.12f + #define OVR_DEFAULT_NECK_TO_EYE_VERTICAL 0.12f +#endif + + +// Get float property. Returns first element if property is a float array. +// Returns defaultValue if property doesn't exist. +OVR_EXPORT float ovrHmd_GetFloat(ovrHmd hmd, const char* propertyName, float defaultVal); + +// Modify float property; false if property doesn't exist or is readonly. +OVR_EXPORT ovrBool ovrHmd_SetFloat(ovrHmd hmd, const char* propertyName, float value); + + +// Get float[] property. Returns the number of elements filled in, 0 if property doesn't exist. +// Maximum of arraySize elements will be written. +OVR_EXPORT unsigned int ovrHmd_GetFloatArray(ovrHmd hmd, const char* propertyName, + float values[], unsigned int arraySize); + +// Modify float[] property; false if property doesn't exist or is readonly. +OVR_EXPORT ovrBool ovrHmd_SetFloatArray(ovrHmd hmd, const char* propertyName, + float values[], unsigned int arraySize); + +// Get string property. Returns first element if property is a string array. +// Returns defaultValue if property doesn't exist. +// String memory is guaranteed to exist until next call to GetString or GetStringArray, or HMD is destroyed. +OVR_EXPORT const char* ovrHmd_GetString(ovrHmd hmd, const char* propertyName, + const char* defaultVal); + +// Returns array size of a property, 0 if property doesn't exist. +// Can be used to check existence of a property. +OVR_EXPORT unsigned int ovrHmd_GetArraySize(ovrHmd hmd, const char* propertyName); + + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif // OVR_CAPI_h diff --git a/LibOVR/Src/OVR_CAPI_GL.h b/LibOVR/Src/OVR_CAPI_GL.h new file mode 100644 index 0000000..ceabb74 --- /dev/null +++ b/LibOVR/Src/OVR_CAPI_GL.h @@ -0,0 +1,73 @@ +/************************************************************************************ + +Filename : OVR_CAPI_GL.h +Content : GL specific structures used by the CAPI interface. +Created : November 7, 2013 +Authors : Lee Cooper + +Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. + +Use of this software is subject to the terms of the Oculus Inc license +agreement provided at the time of installation or download, or which +otherwise accompanies this software in either electronic or hard copy form. + +************************************************************************************/ +#ifndef OVR_CAPI_GL_h +#define OVR_CAPI_GL_h + +#include "OVR_CAPI.h" + +//----------------------------------------------------------------------------------- +// ***** GL Specific + +#if defined(OVR_OS_WIN32) + #include <Windows.h> + #include <GL/gl.h> + #include <GL/glext.h> + #include <GL/wglext.h> +#elif defined(OVR_OS_MAC) + #include <OpenGL/gl3.h> + #include <OpenGL/gl3ext.h> + #include <OpenGL/OpenGL.h> +#else + #include <GL/gl.h> + #include <GL/glext.h> + #include <GL/glx.h> +#endif + + +// Used to configure slave GL rendering (i.e. for devices created externally). +typedef struct ovrGLConfigData_s +{ + // General device settings. + ovrRenderAPIConfigHeader Header; + +#if defined(OVR_OS_WIN32) + HWND Window; +#elif defined(OVR_OS_LINUX) + Display* Disp; + Window Win; +#endif +} ovrGLConfigData; + +union ovrGLConfig +{ + ovrRenderAPIConfig Config; + ovrGLConfigData OGL; +}; + +// Used to pass GL eye texture data to ovrHmd_EndFrame. +typedef struct ovrGLTextureData_s +{ + // General device settings. + ovrTextureHeader Header; + GLuint TexId; +} ovrGLTextureData; + +typedef union ovrGLTexture_s +{ + ovrTexture Texture; + ovrGLTextureData OGL; +} ovrGLTexture; + +#endif // OVR_CAPI_GL_h diff --git a/LibOVR/Src/OVR_Common_HMDDevice.cpp b/LibOVR/Src/OVR_Common_HMDDevice.cpp new file mode 100644 index 0000000..6bedcbe --- /dev/null +++ b/LibOVR/Src/OVR_Common_HMDDevice.cpp @@ -0,0 +1,384 @@ +/************************************************************************************ + +Filename : OVR_Common_HMDDevice.cpp +Content : +Created : +Authors : + +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. + +*************************************************************************************/ + +// Should be #included from the relevant OVR_YourPlatformHere_HMDDevice.cpp + +#include "Kernel/OVR_Alg.h" + +//------------------------------------------------------------------------------------- +// ***** HMDDeviceCreateDesc + +DeviceBase* HMDDeviceCreateDesc::NewDeviceInstance() +{ + return new HMDDevice(this); +} + +void HMDDeviceCreateDesc::SetScreenParameters(int x, int y, + int hres, int vres, + float hsize, float vsize, + float vCenterFromTopInMeters, float lensSeparationInMeters) +{ + Desktop.X = x; + Desktop.Y = y; + ResolutionInPixels = Sizei(hres, vres); + ScreenSizeInMeters = Sizef(hsize, vsize); + VCenterFromTopInMeters = vCenterFromTopInMeters; + LensSeparationInMeters = lensSeparationInMeters; + + Contents |= Contents_Screen; +} + + +void HMDDeviceCreateDesc::SetDistortion(const float* dks) +{ + for (int i = 0; i < 4; i++) + DistortionK[i] = dks[i]; + // TODO: add DistortionEqn + Contents |= Contents_Distortion; +} + +HmdTypeEnum HMDDeviceCreateDesc::GetHmdType() const +{ + // Determine the HMD model + // The closest thing we have to a dependable model indicator are the + // the screen characteristics. Additionally we can check the sensor + // (on attached devices) to further refine our guess + HmdTypeEnum hmdType = HmdType_Unknown; + + if ( ResolutionInPixels.w == 1280 ) + { + if ( ScreenSizeInMeters.w > 0.1497f && ScreenSizeInMeters.w < 0.1498f ) + hmdType = HmdType_DK1; + else + hmdType = HmdType_DKProto; + } + else if ( ResolutionInPixels.w == 1920 ) + { + // DKHD protoypes, all 1920x1080 + if ( ScreenSizeInMeters.w > 0.1209f && ScreenSizeInMeters.w < 0.1210f ) + { + // Screen size 0.12096 x 0.06804 + hmdType = HmdType_DKHDProto; + } + else if ( ScreenSizeInMeters.w > 0.1257f && ScreenSizeInMeters.w < 0.1258f ) + { + // Screen size 0.125 x 0.071 + // Could be a HmdType_DKHDProto566Mi, HmdType_CrystalCoveProto, or DK2 + // - most likely the latter. + hmdType = HmdType_DK2; + + // If available, check the sensor to determine exactly which variant this is + if (pDevice) + { + Ptr<SensorDevice> sensor = *((HMDDevice*)pDevice)->GetSensor(); + + SensorInfo sinfo; + if (sensor && sensor->GetDeviceInfo(&sinfo)) + { + if (sinfo.ProductId == 1) + { + hmdType = HmdType_DKHDProto566Mi; + } + else + { // Crystal Cove uses 0.# firmware, DK2 uses 1.# + int firm_major = Alg::DecodeBCD((sinfo.Version >> 8) & 0x00ff); + int firm_minor = Alg::DecodeBCD(sinfo.Version & 0xff); + OVR_UNUSED(firm_minor); + if (firm_major == 0) + hmdType = HmdType_CrystalCoveProto; + else + hmdType = HmdType_DK2; + } + } + } + } + else if (ScreenSizeInMeters.w > 0.1295f && ScreenSizeInMeters.w < 0.1297f) + { + // Screen size 0.1296 x 0.0729 + hmdType = HmdType_DKHD2Proto; + } + } + + OVR_ASSERT( hmdType != HmdType_Unknown ); + return hmdType; +} + +bool HMDDeviceCreateDesc::GetDeviceInfo(DeviceInfo* info) const +{ + if ((info->InfoClassType != Device_HMD) && + (info->InfoClassType != Device_None)) + return false; + + HmdTypeEnum hmdType = GetHmdType(); + char const* deviceName = "Oculus HMD"; + switch (hmdType) + { + case HmdType_DKProto: deviceName = "Oculus Rift Prototype"; break; + case HmdType_DK1: deviceName = "Oculus Rift DK1"; break; + case HmdType_DKHDProto: deviceName = "Oculus Rift DKHD"; break; + case HmdType_DKHD2Proto: deviceName = "Oculus Rift DKHD2"; break; + case HmdType_DKHDProto566Mi: deviceName = "Oculus Rift DKHD 566 Mi"; break; + case HmdType_CrystalCoveProto: deviceName = "Oculus Rift Crystal Cove"; break; + case HmdType_DK2: deviceName = "Oculus Rift DK2"; break; + default: deviceName = "Oculus HMD"; break; + } + + info->ProductName = deviceName; + info->Manufacturer = "Oculus VR"; + info->Type = Device_HMD; + info->Version = 0; + + // Display detection. + if (info->InfoClassType == Device_HMD) + { + HMDInfo* hmdInfo = static_cast<HMDInfo*>(info); + + hmdInfo->HmdType = hmdType; + hmdInfo->DesktopX = Desktop.X; + hmdInfo->DesktopY = Desktop.Y; + hmdInfo->ResolutionInPixels = ResolutionInPixels; + hmdInfo->ScreenSizeInMeters = ScreenSizeInMeters; // Includes ScreenGapSizeInMeters + hmdInfo->ScreenGapSizeInMeters = 0.0f; + hmdInfo->CenterFromTopInMeters = VCenterFromTopInMeters; + hmdInfo->LensSeparationInMeters = LensSeparationInMeters; + // TODO: any other information we get from the hardware itself should be added to this list + + switch ( hmdInfo->HmdType ) + { + case HmdType_DKProto: + // WARNING - estimated. + hmdInfo->Shutter.Type = HmdShutter_RollingTopToBottom; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.000052f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.016580f; + hmdInfo->Shutter.PixelSettleTime = 0.015f; // estimated. + hmdInfo->Shutter.PixelPersistence = hmdInfo->Shutter.VsyncToNextVsync; // Full persistence + break; + case HmdType_DK1: + // Data from specs. + hmdInfo->Shutter.Type = HmdShutter_RollingTopToBottom; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.00018226f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.01620089f; + hmdInfo->Shutter.PixelSettleTime = 0.017f; // estimated. + hmdInfo->Shutter.PixelPersistence = hmdInfo->Shutter.VsyncToNextVsync; // Full persistence + break; + case HmdType_DKHDProto: + // Data from specs. + hmdInfo->Shutter.Type = HmdShutter_RollingRightToLeft; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.0000859f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.0164948f; + hmdInfo->Shutter.PixelSettleTime = 0.012f; // estimated. + hmdInfo->Shutter.PixelPersistence = hmdInfo->Shutter.VsyncToNextVsync; // Full persistence + break; + case HmdType_DKHD2Proto: + // Data from specs. + hmdInfo->Shutter.Type = HmdShutter_RollingRightToLeft; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.000052f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.016580f; + hmdInfo->Shutter.PixelSettleTime = 0.015f; // estimated. + hmdInfo->Shutter.PixelPersistence = hmdInfo->Shutter.VsyncToNextVsync; // Full persistence + break; + case HmdType_DKHDProto566Mi: +#if 0 + // Low-persistence global shutter + hmdInfo->Shutter.Type = HmdShutter_Global; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.0000273f + 0.0131033f; // Global shutter - first visible scan line is actually the last! + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.000f; // Global shutter - all visible at once. + hmdInfo->Shutter.PixelSettleTime = 0.0f; // <100us + hmdInfo->Shutter.PixelPersistence = 0.18f * hmdInfo->Shutter.VsyncToNextVsync; // Confgurable - currently set to 18% of total frame. +#else + // Low-persistence rolling shutter + hmdInfo->Shutter.Type = HmdShutter_RollingRightToLeft; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.0000273f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.0131033f; + hmdInfo->Shutter.PixelSettleTime = 0.0f; // <100us + hmdInfo->Shutter.PixelPersistence = 0.18f * hmdInfo->Shutter.VsyncToNextVsync; // Confgurable - currently set to 18% of total frame. +#endif + break; + case HmdType_CrystalCoveProto: + // Low-persistence rolling shutter + hmdInfo->Shutter.Type = HmdShutter_RollingRightToLeft; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.0000273f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.0131033f; + hmdInfo->Shutter.PixelSettleTime = 0.0f; // <100us + hmdInfo->Shutter.PixelPersistence = 0.18f * hmdInfo->Shutter.VsyncToNextVsync; // Confgurable - currently set to 18% of total frame. + break; + case HmdType_DK2: + // Low-persistence rolling shutter + hmdInfo->Shutter.Type = HmdShutter_RollingRightToLeft; + hmdInfo->Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + hmdInfo->Shutter.VsyncToFirstScanline = 0.0000273f; + hmdInfo->Shutter.FirstScanlineToLastScanline = 0.0131033f; + hmdInfo->Shutter.PixelSettleTime = 0.0f; // <100us + hmdInfo->Shutter.PixelPersistence = 0.18f * hmdInfo->Shutter.VsyncToNextVsync; // Confgurable - currently set to 18% of total frame. + break; + default: OVR_ASSERT ( false ); break; + } + + + OVR_strcpy(hmdInfo->DisplayDeviceName, sizeof(hmdInfo->DisplayDeviceName), + DisplayDeviceName.ToCStr()); +#if defined(OVR_OS_WIN32) + // Nothing special for Win32. +#elif defined(OVR_OS_MAC) + hmdInfo->DisplayId = DisplayId; +#elif defined(OVR_OS_LINUX) + hmdInfo->DisplayId = DisplayId; +#elif defined(OVR_OS_ANDROID) + hmdInfo->DisplayId = DisplayId; +#else +#error Unknown platform +#endif + + } + + return true; +} + + + + + +//------------------------------------------------------------------------------------- +// ***** HMDDevice + +HMDDevice::HMDDevice(HMDDeviceCreateDesc* createDesc) + : OVR::DeviceImpl<OVR::HMDDevice>(createDesc, 0) +{ +} +HMDDevice::~HMDDevice() +{ +} + +bool HMDDevice::Initialize(DeviceBase* parent) +{ + pParent = parent; + return true; +} +void HMDDevice::Shutdown() +{ + ProfileName.Clear(); + pCachedProfile.Clear(); + pParent.Clear(); +} + +Profile* HMDDevice::GetProfile() +{ + // Loads and returns a cached profile based on this device and current user + if (pCachedProfile == NULL) + { + ProfileManager* mgr = GetManager()->GetProfileManager(); + const char* profile_name = GetProfileName(); + if (profile_name && profile_name[0]) + pCachedProfile = *mgr->GetProfile(this, profile_name); + + if (pCachedProfile == NULL) + pCachedProfile = *mgr->GetDefaultProfile(this); + + } + return pCachedProfile.GetPtr(); +} + +const char* HMDDevice::GetProfileName() +{ + if (ProfileName.IsEmpty()) + { // If the profile name has not been initialized then + // retrieve the stored default user for this specific device + ProfileManager* mgr = GetManager()->GetProfileManager(); + const char* name = mgr->GetDefaultUser(this); + ProfileName = name; + } + + return ProfileName.ToCStr(); +} + +bool HMDDevice::SetProfileName(const char* name) +{ + if (ProfileName == name) + return true; // already set + + // Flush the old profile + pCachedProfile.Clear(); + if (!name) + { + ProfileName.Clear(); + return false; + } + + // Set the name and attempt to cache the profile + ProfileName = name; + if (GetProfile()) + { + return true; + } + else + { + ProfileName.Clear(); + return false; + } +} + +OVR::SensorDevice* HMDDevice::GetSensor() +{ + // Just return first sensor found since we have no way to match it yet. + + // Create DK2 sensor if it exists otherwise create first DK1 sensor. + SensorDevice* sensor = NULL; + + DeviceEnumerator<SensorDevice> enumerator = GetManager()->EnumerateDevices<SensorDevice>(); + + while(enumerator.GetType() != Device_None) + { + SensorInfo info; + enumerator.GetDeviceInfo(&info); + + if (info.ProductId == Device_Tracker2_ProductId) + { + sensor = enumerator.CreateDevice(); + break; + } + + enumerator.Next(); + } + + if (sensor == NULL) + { + sensor = GetManager()->EnumerateDevices<SensorDevice>().CreateDevice(); + } + + if (sensor) + { + sensor->SetCoordinateFrame(SensorDevice::Coord_HMD); + } + + return sensor; +} diff --git a/LibOVR/Src/OVR_Device.h b/LibOVR/Src/OVR_Device.h new file mode 100644 index 0000000..52a41f9 --- /dev/null +++ b/LibOVR/Src/OVR_Device.h @@ -0,0 +1,1135 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Device.h +Content : Definition of HMD-related Device interfaces +Created : September 21, 2012 +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. + +*************************************************************************************/ + +#ifndef OVR_Device_h +#define OVR_Device_h + +#include "OVR_DeviceConstants.h" +#include "OVR_DeviceHandle.h" +#include "OVR_DeviceMessages.h" +#include "OVR_HIDDeviceBase.h" + +#include "Kernel/OVR_Atomic.h" +#include "Kernel/OVR_RefCount.h" +#include "Kernel/OVR_String.h" + + +namespace OVR { + +// Declared externally +class Profile; +class ProfileManager; // << Should be renamed for consistency + +// Forward declarations +class SensorDevice; +class DeviceCommon; +class DeviceManager; + +// MessageHandler is a base class from which users derive to receive messages, +// its OnMessage handler will be called for messages once it is installed on +// a device. Same message handler can be installed on multiple devices. +class MessageHandler +{ + friend class MessageHandlerImpl; +public: + MessageHandler(); + virtual ~MessageHandler(); + + // Returns 'true' if handler is currently installed on any devices. + bool IsHandlerInstalled() const; + + // Should be called from derived class destructor to avoid handler + // being called after it exits. + void RemoveHandlerFromDevices(); + + // Returns a pointer to the internal lock object that is locked by a + // background thread while OnMessage() is called. + // This lock guaranteed to survive until ~MessageHandler. + Lock* GetHandlerLock() const; + + + virtual void OnMessage(const Message&) { } + + // Determines if handler supports a specific message type. Can + // be used to filter out entire message groups. The result + // returned by this function shouldn't change after handler creation. + virtual bool SupportsMessageType(MessageType) const { return true; } + +private: + UPInt Internal[8]; +}; + + +//------------------------------------------------------------------------------------- +// ***** DeviceBase + +// DeviceBase is the base class for all OVR Devices. It provides the following basic +// functionality: +// - Reports device type, manager, and associated parent (if any). +// - Supports installable message handlers, which are notified of device events. +// - Device objects are created through DeviceHandle::CreateDevice or more commonly +// through DeviceEnumerator<>::CreateDevice. +// - Created devices are reference counted, starting with RefCount of 1. +// - Device is resources are cleaned up when it is Released, although its handles +// may survive longer if referenced. + +class DeviceBase : public NewOverrideBase +{ + friend class DeviceHandle; + friend class DeviceManagerImpl; +public: + + // Enumerating DeviceBase enumerates all devices. + enum { EnumDeviceType = Device_All }; + + virtual ~DeviceBase() { } + virtual void AddRef(); + virtual void Release(); + + virtual DeviceBase* GetParent() const; + virtual DeviceManager* GetManager() const; + + virtual void AddMessageHandler(MessageHandler* handler); + + virtual DeviceType GetType() const; + virtual bool GetDeviceInfo(DeviceInfo* info) const; + + // Returns true if device is connected and usable + virtual bool IsConnected(); + + // returns the MessageHandler's lock + Lock* GetHandlerLock() const; +protected: + // Internal + virtual DeviceCommon* getDeviceCommon() const = 0; +}; + + +//------------------------------------------------------------------------------------- +// ***** DeviceInfo + +// DeviceInfo describes a device and its capabilities, obtained by calling +// GetDeviceInfo. This base class only contains device-independent functionality; +// users will normally use a derived HMDInfo or SensorInfo classes for more +// extensive device info. + +class DeviceInfo +{ +public: + DeviceInfo() : InfoClassType(Device_None), Type(Device_None), Version(0) + {} + + // Type of device for which DeviceInfo is intended. + // This will be set to Device_HMD for HMDInfo structure, note that this may be + // different form the actual device type since (Device_None) is valid. + const DeviceType InfoClassType; + // Type of device this describes. This must be the same as InfoClassType when + // InfoClassType != Device_None. + DeviceType Type; + // Name string describing the product: "Oculus Rift DK1", etc. + String ProductName; + String Manufacturer; + unsigned Version; + +protected: + DeviceInfo(DeviceType type) : InfoClassType(type), Type(type), Version(0) + {} + void operator = (const DeviceInfo&) { OVR_ASSERT(0); } // Assignment not allowed. +}; + + +//------------------------------------------------------------------------------------- +// DeviceEnumerationArgs provides device enumeration argumenrs for DeviceManager::EnumerateDevicesEx. +class DeviceEnumerationArgs +{ +public: + DeviceEnumerationArgs(DeviceType enumType, bool availableOnly) + : EnumType(enumType), AvailableOnly(availableOnly) + { } + + // Helper; returns true if args match our enumeration criteria. + bool MatchRule(DeviceType type, bool available) const + { + return ((EnumType == type) || (EnumType == Device_All)) && + (available || !AvailableOnly); + } + +protected: + DeviceType EnumType; + bool AvailableOnly; +}; + + +// DeviceEnumerator<> is used to enumerate and create devices of specified class, +// it is returned by calling MeviceManager::EnumerateDevices. Initially, the enumerator will +// refer to the first device of specified type. Additional devices can be accessed by +// calling Next(). + +template<class T = DeviceBase> +class DeviceEnumerator : public DeviceHandle +{ + friend class DeviceManager; + friend class DeviceManagerImpl; +public: + DeviceEnumerator() + : DeviceHandle(), EnumArgs(Device_None, true) { } + + // Next advances enumeration to the next device that first criteria. + // Returns false if no more devices exist that match enumeration criteria. + bool Next() { return enumerateNext(EnumArgs); } + + // Creates an instance of the device referenced by enumerator; returns null + // if enumerator does not refer to a valid device or device is unavailable. + // If device was already created, the same object with incremented ref-count is returned. + T* CreateDevice() { return static_cast<T*>(DeviceHandle::CreateDevice()); } + +protected: + DeviceEnumerator(const DeviceHandle &dev, const DeviceEnumerationArgs& args) + : DeviceHandle(dev), EnumArgs(args) + { } + + DeviceEnumerationArgs EnumArgs; +}; + +//------------------------------------------------------------------------------------- +// ***** DeviceManager + +// DeviceManager maintains and provides access to devices supported by OVR, such as +// HMDs and sensors. A single instance of DeviceManager is normally created at +// program startup, allowing devices to be enumerated and created. DeviceManager is +// reference counted and is AddRefed by its created child devices, causing it to +// always be the last object that is released. +// +// Install MessageHandler on DeviceManager to detect when devices are inserted or removed. +// +// The following code will create the manager and its first available HMDDevice, +// and then release it when not needed: +// +// DeviceManager* manager = DeviceManager::Create(); +// HMDDevice* hmd = manager->EnumerateDevices<HMDDevice>().CreateDevice(); +// +// if (hmd) hmd->Release(); +// if (manager) manager->Release(); + + +class DeviceManager : public DeviceBase +{ +public: + + DeviceManager() + { } + + // DeviceBase implementation. + virtual DeviceType GetType() const { return Device_Manager; } + virtual DeviceManager* GetManager() const { return const_cast<DeviceManager*>(this); } + + // Every DeviceManager has an associated profile manager, which us used to store + // user settings that may affect device behavior. + virtual ProfileManager* GetProfileManager() const = 0; + + + // EnumerateDevices enumerates all of the available devices of the specified class, + // returning an enumerator that references the first device. An empty enumerator is + // returned if no devices are available. The following APIs are exposed through + // DeviceEnumerator: + // DeviceEnumerator::GetType() - Check device type. Returns Device_None + // if no device was found/pointed to. + // DeviceEnumerator::GetDeviceInfo() - Get more information on device. + // DeviceEnumerator::CreateDevice() - Create an instance of device. + // DeviceEnumerator::Next() - Move onto next device. + template<class D> + DeviceEnumerator<D> EnumerateDevices(bool availableOnly = true) + { + // TBD: A cleaner (but less efficient) alternative is though enumeratorFromHandle. + DeviceEnumerator<> e = EnumerateDevicesEx(DeviceEnumerationArgs((DeviceType)D::EnumDeviceType, availableOnly)); + return *reinterpret_cast<DeviceEnumerator<D>*>(&e); + } + + // EnumerateDevicesEx provides internal implementation for device enumeration, enumerating + // devices based on dynamically specified DeviceType in DeviceEnumerationArgs. + // End users should call DeumerateDevices<>() instead. + virtual DeviceEnumerator<> EnumerateDevicesEx(const DeviceEnumerationArgs& args) = 0; + + // Creates a new DeviceManager. Only one instance of DeviceManager should be created at a time. + static DeviceManager* Create(); + + // Static constant for this device type, used in template cast type checks. + enum { EnumDeviceType = Device_Manager }; + + + + // Adds a device (DeviceCreateDesc*) into Devices. Returns NULL, + // if unsuccessful or device is already in the list. + virtual Ptr<DeviceCreateDesc> AddDevice_NeedsLock(const DeviceCreateDesc& createDesc) = 0; + +protected: + DeviceEnumerator<> enumeratorFromHandle(const DeviceHandle& h, const DeviceEnumerationArgs& args) + { return DeviceEnumerator<>(h, args); } + + DeviceManager* getThis() { return this; } +}; + + + +//------------------------------------------------------------------------------------- +// ***** HMDInfo + +// This structure describes various aspects of the HMD allowing us to configure rendering. +// +// Currently included data: +// - Physical screen dimensions, resolution, and eye distances. +// (some of these will be configurable with a tool in the future). +// These arguments allow us to properly setup projection across HMDs. +// - DisplayDeviceName for identifying HMD screen; system-specific interpretation. +// +// TBD: +// - Power on/ off? +// - Sensor rates and capabilities +// - Distortion radius/variables +// - Screen update frequency +// - Distortion needed flag +// - Update modes: +// Set update mode: Stereo (both sides together), mono (same in both eyes), +// Alternating, Alternating scan-lines. + +class HMDInfo : public DeviceInfo +{ +public: + // Characteristics of the HMD screen and enclosure + HmdTypeEnum HmdType; + Size<int> ResolutionInPixels; + Size<float> ScreenSizeInMeters; + float ScreenGapSizeInMeters; + float CenterFromTopInMeters; + float LensSeparationInMeters; + + // Timing & shutter data. All values in seconds. + struct ShutterInfo + { + HmdShutterTypeEnum Type; + float VsyncToNextVsync; // 1/framerate + float VsyncToFirstScanline; // for global shutter, vsync->shutter open. + float FirstScanlineToLastScanline; // for global shutter, will be zero. + float PixelSettleTime; // estimated. + float PixelPersistence; // Full persistence = 1/framerate. + } Shutter; + + // Desktop coordinate position of the screen (can be negative; may not be present on all platforms) + int DesktopX; + int DesktopY; + + // Windows: + // "\\\\.\\DISPLAY3", etc. Can be used in EnumDisplaySettings/CreateDC. + char DisplayDeviceName[32]; + + // MacOS: + int DisplayId; + + + // Constructor initializes all values to 0s. + // To create a "virtualized" HMDInfo, use CreateDebugHMDInfo instead. + HMDInfo() + : DeviceInfo(Device_HMD), + HmdType(HmdType_None), + ResolutionInPixels(0), + ScreenSizeInMeters(0.0f), + ScreenGapSizeInMeters(0.0f), + CenterFromTopInMeters(0), + LensSeparationInMeters(0), + DisplayId(0) + { + DesktopX = 0; + DesktopY = 0; + DisplayDeviceName[0] = 0; + Shutter.Type = HmdShutter_LAST; + Shutter.VsyncToNextVsync = 0.0f; + Shutter.VsyncToFirstScanline = 0.0f; + Shutter.FirstScanlineToLastScanline = 0.0f; + Shutter.PixelSettleTime = 0.0f; + Shutter.PixelPersistence = 0.0f; + } + + // Operator = copies local fields only (base class must be correct already) + void operator = (const HMDInfo& src) + { + HmdType = src.HmdType; + ResolutionInPixels = src.ResolutionInPixels; + ScreenSizeInMeters = src.ScreenSizeInMeters; + ScreenGapSizeInMeters = src.ScreenGapSizeInMeters; + CenterFromTopInMeters = src.CenterFromTopInMeters; + LensSeparationInMeters = src.LensSeparationInMeters; + DesktopX = src.DesktopX; + DesktopY = src.DesktopY; + Shutter = src.Shutter; + memcpy(DisplayDeviceName, src.DisplayDeviceName, sizeof(DisplayDeviceName)); + + DisplayId = src.DisplayId; + } + + bool IsSameDisplay(const HMDInfo& o) const + { + return DisplayId == o.DisplayId && + String::CompareNoCase(DisplayDeviceName, + o.DisplayDeviceName) == 0; + } + +}; + + +// HMDDevice represents an Oculus HMD device unit. An instance of this class +// is typically created from the DeviceManager. +// After HMD device is created, we its sensor data can be obtained by +// first creating a Sensor object and then. + +// TBD: +// - Configure Sensor +// - APIs to set On-Screen message, other states? + +class HMDDevice : public DeviceBase +{ +public: + HMDDevice() + { } + + // Static constant for this device type, used in template cast type checks. + enum { EnumDeviceType = Device_HMD }; + + virtual DeviceType GetType() const { return Device_HMD; } + + // Creates a sensor associated with this HMD. + virtual SensorDevice* GetSensor() = 0; + + + // Requests the currently used profile. This profile affects the + // settings reported by HMDInfo. + virtual Profile* GetProfile() = 0; + // Obtains the currently used profile name. This is initialized to the default + // profile name, if any; it can then be changed per-device by SetProfileName. + virtual const char* GetProfileName() = 0; + // Sets the profile user name, changing the data returned by GetProfileInfo. + virtual bool SetProfileName(const char* name) = 0; + + + // Disconnects from real HMD device. This HMDDevice remains as 'fake' HMD. + // SensorDevice ptr is used to restore the 'fake' HMD (can be NULL). + HMDDevice* Disconnect(SensorDevice*); + + // Returns 'true' if HMD device is a 'fake' HMD (was created this way or + // 'Disconnect' method was called). + bool IsDisconnected() const; +}; + + +//------------------------------------------------------------------------------------- +// ***** SensorRange & SensorInfo + +// SensorRange specifies maximum value ranges that SensorDevice hardware is configured +// to detect. Although this range doesn't affect the scale of MessageBodyFrame values, +// physical motions whose positive or negative magnitude is outside the specified range +// may get clamped or misreported. Setting lower values may result in higher precision +// tracking. +struct SensorRange +{ + SensorRange(float maxAcceleration = 0.0f, float maxRotationRate = 0.0f, + float maxMagneticField = 0.0f) + : MaxAcceleration(maxAcceleration), MaxRotationRate(maxRotationRate), + MaxMagneticField(maxMagneticField) + { } + + // Maximum detected acceleration in m/s^2. Up to 8*G equivalent support guaranteed, + // where G is ~9.81 m/s^2. + // Oculus DK1 HW has thresholds near: 2, 4 (default), 8, 16 G. + float MaxAcceleration; + // Maximum detected angular velocity in rad/s. Up to 8*Pi support guaranteed. + // Oculus DK1 HW thresholds near: 1, 2, 4, 8 Pi (default). + float MaxRotationRate; + // Maximum detectable Magnetic field strength in Gauss. Up to 2.5 Gauss support guaranteed. + // Oculus DK1 HW thresholds near: 0.88, 1.3, 1.9, 2.5 gauss. + float MaxMagneticField; +}; + +// SensorInfo describes capabilities of the sensor device. +class SensorInfo : public DeviceInfo +{ +public: + SensorInfo() : DeviceInfo(Device_Sensor), VendorId(0), ProductId(0) + { + } + + // HID Vendor and ProductId of the device. + UInt16 VendorId; + UInt16 ProductId; + // MaxRanges report maximum sensor range values supported by HW. + SensorRange MaxRanges; + // Sensor (and display) serial number. + String SerialNumber; + +private: + void operator = (const SensorInfo&) { OVR_ASSERT(0); } // Assignment not allowed. +}; + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Serial Number feature report. (DK1) +struct SerialReport +{ + static const int SERIAL_NUMBER_SIZE = 12; // Serial Number size = 12 bytes. (Refer 'Tracker Firmware Specification Section 4.9, Pg 18) + + SerialReport() + : CommandId(0) + { + memset(SerialNumberValue, 0, sizeof(SerialNumberValue)); + } + + SerialReport(UInt16 commandId, + UByte SNo[SERIAL_NUMBER_SIZE]) + : CommandId(commandId) + { + for (int i=0; i < SERIAL_NUMBER_SIZE; i++) + { + SerialNumberValue[i] = SNo[i]; + } + } + + UInt16 CommandId; + UByte SerialNumberValue[SERIAL_NUMBER_SIZE]; // See 'Tracker Firmware Specification' document for + // a description of Serial Report. +}; + + +//////////////////////////////////////////////////////////////////////////////////////////////// +//Added Serial Report Implementation. + +struct SerialImpl +{ + enum { PacketSize = 15 }; + UByte Buffer[PacketSize]; + + SerialReport Settings; + + SerialImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 10; + } + + SerialImpl(const SerialReport& settings) + :Settings(settings) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 10; + Alg::EncodeUInt16(Buffer+1, Settings.CommandId); + for (int i = 0; i < Settings.SERIAL_NUMBER_SIZE; ++i) + Buffer[3 + i] = Settings.SerialNumberValue[i]; + } + + void Unpack() + { + Settings.CommandId = Alg::DecodeUInt16(Buffer+1); + for (int i = 0; i < Settings.SERIAL_NUMBER_SIZE; ++i) + Settings.SerialNumberValue[i] = Buffer[3 + i]; + } + +}; + + +// Tracking settings (DK2). +struct TrackingReport +{ + TrackingReport() + : CommandId(0), Pattern(0), + Enable(0), Autoincrement(0), UseCarrier(0), + SyncInput(0), VsyncLock(0), CustomPattern(0), + ExposureLength(0), FrameInterval(0), + VsyncOffset(0), DutyCycle(0) + {} + + TrackingReport( UInt16 commandId, + UByte pattern, + bool enable, + bool autoincrement, + bool useCarrier, + bool syncInput, + bool vsyncLock, + bool customPattern, + UInt16 exposureLength, + UInt16 frameInterval, + UInt16 vsyncOffset, + UByte dutyCycle) + : CommandId(commandId), Pattern(pattern), + Enable(enable), Autoincrement(autoincrement), UseCarrier(useCarrier), + SyncInput(syncInput), VsyncLock(vsyncLock), CustomPattern(customPattern), + ExposureLength(exposureLength), FrameInterval(frameInterval), + VsyncOffset(vsyncOffset), DutyCycle(dutyCycle) + { } + + UInt16 CommandId; + UByte Pattern; // Tracking LED pattern index. + bool Enable; // Enables the tracking LED exposure and updating. + bool Autoincrement; // Autoincrement pattern after each exposure. + bool UseCarrier; // Modulate tracking LEDs at 85kHz. + bool SyncInput; // Trigger LED exposure from wired sync signal. + bool VsyncLock; // Trigger LED exposure from panel Vsync. + bool CustomPattern; // Use custom LED sequence. + UInt16 ExposureLength; // Tracking LED illumination (and exposure) length in microseconds. + UInt16 FrameInterval; // LED exposure interval in microseconds when in + // 'internal timer' mode (when SyncInput = VsyncLock = false). + UInt16 VsyncOffset; // Exposure offset in microseconds from vsync when in + // 'vsync lock' mode (when VsyncLock = true). + UByte DutyCycle; // Duty cycle of 85kHz modulation when in 'use carrier' mode + // (when UseCarrier = true). 128 = 50% duty cycle. +}; + +// Display settings (DK2). +struct DisplayReport +{ + enum ShutterTypeEnum + { + // These are not yet defined. + ShutterType_Default = 0, + }; + + enum CurrentLimitEnum + { + // These are not yet defined. + CurrentLimit_Default = 0, + }; + + DisplayReport() + : CommandId(0), Brightness(0), + ShutterType(ShutterType_Default), CurrentLimit(CurrentLimit_Default), UseRolling(0), + ReverseRolling(0), HighBrightness(0), SelfRefresh(0), + ReadPixel(0), DirectPentile(0), + Persistence(0), LightingOffset(0), + PixelSettle(0), TotalRows(0) + {} + + DisplayReport( UInt16 commandId, + UByte brightness, + ShutterTypeEnum shutterType, + CurrentLimitEnum currentLimit, + bool useRolling, + bool reverseRolling, + bool highBrightness, + bool selfRefresh, + bool readPixel, + bool directPentile, + UInt16 persistence, + UInt16 lightingOffset, + UInt16 pixelSettle, + UInt16 totalRows) + : CommandId(commandId), Brightness(brightness), + ShutterType(shutterType), CurrentLimit(currentLimit), UseRolling(useRolling), + ReverseRolling(reverseRolling), HighBrightness(highBrightness), SelfRefresh(selfRefresh), + ReadPixel(readPixel), DirectPentile(directPentile), + Persistence(persistence), LightingOffset(lightingOffset), + PixelSettle(pixelSettle), TotalRows(totalRows) + { } + + UInt16 CommandId; + UByte Brightness; // See 'DK2 Firmware Specification' document for a description of + ShutterTypeEnum ShutterType; // display settings. + CurrentLimitEnum CurrentLimit; + bool UseRolling; + bool ReverseRolling; + bool HighBrightness; + bool SelfRefresh; + bool ReadPixel; + bool DirectPentile; + UInt16 Persistence; + UInt16 LightingOffset; + UInt16 PixelSettle; + UInt16 TotalRows; +}; + +// MagCalibration matrix (DK2). +struct MagCalibrationReport +{ + MagCalibrationReport() + : CommandId(0), Version(0), Calibration() + {} + + MagCalibrationReport( UInt16 commandId, + UByte version, + const Matrix4f& calibration) + : CommandId(commandId), Version(version), Calibration(calibration) + { } + + UInt16 CommandId; + UByte Version; // Version of the calibration procedure used to generate the calibration matrix. + Matrix4f Calibration; // Calibration matrix. Note only the first three rows are used by the feature report. +}; + +// PositionCalibration values (DK2). +// - Sensor interface versions before 5 do not support Normal and Rotation. +struct PositionCalibrationReport +{ + enum PositionTypeEnum + { + PositionType_LED = 0, + PositionType_IMU = 1 + }; + + PositionCalibrationReport() + : CommandId(0), Version(0), + Position(0), Normal(0), Angle(0), + PositionIndex(0), NumPositions(0), PositionType(PositionType_LED) + {} + + PositionCalibrationReport(UInt16 commandId, + UByte version, + const Vector3d& position, + const Vector3d& normal, + double rotation, + UInt16 positionIndex, + UInt16 numPositions, + PositionTypeEnum positionType) + : CommandId(commandId), Version(version), + Position(position), Normal(normal), Angle(rotation), + PositionIndex(positionIndex), NumPositions(numPositions), PositionType(positionType) + { + } + + UInt16 CommandId; + UByte Version; // The version of the calibration procedure used to generate the stored positions. + Vector3d Position; // Position of the LED or inertial tracker in meters. This is relative to the + // center of the emitter plane of the display at nominal focus. + Vector3d Normal; // Normal of the LED or inertial tracker. This is a signed integer in + // meters. The normal is relative to the position. + double Angle; // The rotation about the normal. This is in radians. + UInt16 PositionIndex; // The current position being read or written to. Autoincrements on reads, gets set + // to the written value on writes. + UInt16 NumPositions; // The read-only number of items with positions stored. The last position is that of + // the inertial tracker, all others are LED positions. + PositionTypeEnum PositionType; // The type of the item which has its position reported in the current report +}; + +// CustomPattern values (DK2). +struct CustomPatternReport +{ + CustomPatternReport() + : CommandId(0), SequenceLength(0), Sequence(0), + LEDIndex(0), NumLEDs(0) + {} + + CustomPatternReport(UInt16 commandId, + UByte sequenceLength, + UInt32 sequence, + UInt16 ledIndex, + UInt16 numLEDs) + : CommandId(commandId), SequenceLength(sequenceLength), Sequence(sequence), + LEDIndex(ledIndex), NumLEDs(numLEDs) + { } + + UInt16 CommandId; + UByte SequenceLength; // See 'DK2 Firmware Specification' document for a description of + UInt32 Sequence; // LED custom patterns. + UInt16 LEDIndex; + UInt16 NumLEDs; +}; + +// KeepAliveMux settings (DK2). +struct KeepAliveMuxReport +{ + KeepAliveMuxReport() + : CommandId(0), INReport(0), Interval(0) + {} + + KeepAliveMuxReport( UInt16 commandId, + UByte inReport, + UInt16 interval) + : CommandId(commandId), INReport(inReport), Interval(interval) + { } + + UInt16 CommandId; + UByte INReport; // Requested IN report type (1 = DK1, 11 = DK2). + UInt16 Interval; // Keep alive period in milliseconds. +}; + +// Manufacturing test result (DK2). +struct ManufacturingReport +{ + ManufacturingReport() + : CommandId(0), NumStages(0), Stage(0), + StageVersion(0), StageLocation(0), StageTime(0), Result(0) + {} + + ManufacturingReport( UInt16 commandId, + UByte numStages, + UByte stage, + UByte version, + UInt16 stageLocation, + UInt32 stageTime, + UInt32 result) + : CommandId(commandId), NumStages(numStages), Stage(stage), + StageVersion(version), StageLocation(stageLocation), StageTime(stageTime), Result(result) + { } + + UInt16 CommandId; + UByte NumStages; // See 'DK2 Firmware Specification' document for a description of + UByte Stage; // manufacturing test results. + UByte StageVersion; + UInt16 StageLocation; + UInt32 StageTime; + UInt32 Result; +}; + +// UUID (DK2). +struct UUIDReport +{ + static const int UUID_SIZE = 20; + + UUIDReport() + : CommandId(0) + { + memset(UUIDValue, 0, sizeof(UUIDValue)); + } + + UUIDReport( UInt16 commandId, + UByte uuid[UUID_SIZE]) + : CommandId(commandId) + { + for (int i=0; i<UUID_SIZE; i++) + { + UUIDValue[i] = uuid[i]; + } + } + + UInt16 CommandId; + UByte UUIDValue[UUID_SIZE]; // See 'DK2 Firmware Specification' document for + // a description of UUID. +}; + +// Lens Distortion (DK2). +struct LensDistortionReport +{ + LensDistortionReport() + : CommandId(0), + NumDistortions(0), + DistortionIndex(0), + Bitmask(0), + LensType(0), + Version(0), + EyeRelief(0), + MaxR(0), + MetersPerTanAngleAtCenter(0) + {} + + LensDistortionReport( UInt16 commandId, + UByte numDistortions, + UByte distortionIndex, + UByte bitmask, + UInt16 lensType, + UInt16 version, + UInt16 eyeRelief, + UInt16 kCoefficients[11], + UInt16 maxR, + UInt16 metersPerTanAngleAtCenter, + UInt16 chromaticAberration[4]) + : CommandId(commandId), + NumDistortions(numDistortions), + DistortionIndex(distortionIndex), + Bitmask(bitmask), + LensType(lensType), + Version(version), + EyeRelief(eyeRelief), + MaxR(maxR), + MetersPerTanAngleAtCenter(metersPerTanAngleAtCenter) + { + memcpy(KCoefficients, kCoefficients, sizeof(KCoefficients)); + memcpy(ChromaticAberration, chromaticAberration, sizeof(ChromaticAberration)); + } + + UInt16 CommandId; + UByte NumDistortions; + UByte DistortionIndex; + UByte Bitmask; + UInt16 LensType; + UInt16 Version; + UInt16 EyeRelief; + UInt16 KCoefficients[11]; + UInt16 MaxR; + UInt16 MetersPerTanAngleAtCenter; + UInt16 ChromaticAberration[4]; +}; + +// Temperature calibration result (DK2). +struct TemperatureReport +{ + TemperatureReport() + : CommandId(0), Version(0), + NumBins(0), Bin(0), NumSamples(0), Sample(0), + TargetTemperature(0), ActualTemperature(0), + Time(0), Offset(0) + {} + + TemperatureReport( UInt16 commandId, + UByte version, + UByte numBins, + UByte bin, + UByte numSamples, + UByte sample, + double targetTemperature, + double actualTemperature, + UInt32 time, + Vector3d offset) + : CommandId(commandId), Version(version), + NumBins(numBins), Bin(bin), NumSamples(numSamples), Sample(sample), + TargetTemperature(targetTemperature), ActualTemperature(actualTemperature), + Time(time), Offset(offset) + { } + + UInt16 CommandId; + UByte Version; // See 'DK2 Firmware Specification' document for a description of + UByte NumBins; // temperature calibration data. + UByte Bin; + UByte NumSamples; + UByte Sample; + double TargetTemperature; + double ActualTemperature; + UInt32 Time; // Better hope nobody tries to use this in 2038 + Vector3d Offset; +}; + +// Gyro autocalibration result (DK2). +struct GyroOffsetReport +{ + enum VersionEnum + { + // These are not yet defined. + Version_NoOffset = 0, + Version_ShortAvg = 1, + Version_LongAvg = 2 + }; + + GyroOffsetReport() + : CommandId(0), Version(Version_NoOffset), + Offset(0), Temperature(0) + {} + + GyroOffsetReport( UInt16 commandId, + VersionEnum version, + Vector3d offset, + double temperature) + : CommandId(commandId), Version(version), + Offset(offset), Temperature(temperature) + {} + + UInt16 CommandId; + VersionEnum Version; + Vector3d Offset; + double Temperature; +}; + + + +//------------------------------------------------------------------------------------- +// ***** SensorDevice + +// SensorDevice is an interface to sensor data. +// Install a MessageHandler of SensorDevice instance to receive MessageBodyFrame +// notifications. +// +// TBD: Add Polling API? More HID interfaces? + +class SensorDevice : public HIDDeviceBase, public DeviceBase +{ +public: + SensorDevice() + { } + + // Static constant for this device type, used in template cast type checks. + enum { EnumDeviceType = Device_Sensor }; + + virtual DeviceType GetType() const { return Device_Sensor; } + + virtual UByte GetDeviceInterfaceVersion() = 0; + + + // CoordinateFrame defines whether messages come in the coordinate frame + // of the sensor device or HMD, which has a different internal sensor. + // Sensors obtained form the HMD will automatically use HMD coordinates. + enum CoordinateFrame + { + Coord_Sensor = 0, + Coord_HMD = 1 + }; + + virtual void SetCoordinateFrame(CoordinateFrame coordframe) = 0; + virtual CoordinateFrame GetCoordinateFrame() const = 0; + + // Sets report rate (in Hz) of MessageBodyFrame messages (delivered through MessageHandler::OnMessage call). + // Currently supported maximum rate is 1000Hz. If the rate is set to 500 or 333 Hz then OnMessage will be + // called twice or thrice at the same 'tick'. + // If the rate is < 333 then the OnMessage / MessageBodyFrame will be called three + // times for each 'tick': the first call will contain averaged values, the second + // and third calls will provide with most recent two recorded samples. + virtual void SetReportRate(unsigned rateHz) = 0; + // Returns currently set report rate, in Hz. If 0 - error occurred. + // Note, this value may be different from the one provided for SetReportRate. The return + // value will contain the actual rate. + virtual unsigned GetReportRate() const = 0; + + // Sets maximum range settings for the sensor described by SensorRange. + // The function will fail if you try to pass values outside Maximum supported + // by the HW, as described by SensorInfo. + // Pass waitFlag == true to wait for command completion. For waitFlag == true, + // returns true if the range was applied successfully (no HW error). + // For waitFlag = false, return 'true' means that command was enqueued successfully. + virtual bool SetRange(const SensorRange& range, bool waitFlag = false) = 0; + + // Return the current sensor range settings for the device. These may not exactly + // match the values applied through SetRange. + virtual void GetRange(SensorRange* range) const = 0; + + // Return the factory calibration parameters for the IMU + virtual void GetFactoryCalibration(Vector3f* AccelOffset, Vector3f* GyroOffset, + Matrix4f* AccelMatrix, Matrix4f* GyroMatrix, + float* Temperature) = 0; + // Enable/disable onboard IMU calibration + // If set to false, the device will return raw values + virtual void SetOnboardCalibrationEnabled(bool enabled) = 0; + // Return true if the mag is calibrated + virtual bool IsMagCalibrated() { return false; } + + // Get/set feature reports from DK1 added to DK2. See 'Tracker Firmware Specification' document for details. + virtual bool SetSerialReport(const SerialReport&) { return false; } + virtual bool GetSerialReport(SerialReport*) { return false; } + + // Get/set feature reports added to DK2. See 'DK2 Firmware Specification' document for details. + virtual bool SetTrackingReport(const TrackingReport&) { return false; } + virtual bool GetTrackingReport(TrackingReport*) { return false; } + + virtual bool SetDisplayReport(const DisplayReport&) { return false; } + virtual bool GetDisplayReport(DisplayReport*) { return false; } + + virtual bool SetMagCalibrationReport(const MagCalibrationReport&) { return false; } + virtual bool GetMagCalibrationReport(MagCalibrationReport*) { return false; } + + virtual bool SetPositionCalibrationReport(const PositionCalibrationReport&) { return false; } + virtual bool GetAllPositionCalibrationReports(Array<PositionCalibrationReport>*) { return false; } + + virtual bool SetCustomPatternReport(const CustomPatternReport&) { return false; } + virtual bool GetCustomPatternReport(CustomPatternReport*) { return false; } + + virtual bool SetKeepAliveMuxReport(const KeepAliveMuxReport&) { return false; } + virtual bool GetKeepAliveMuxReport(KeepAliveMuxReport*) { return false; } + + virtual bool SetManufacturingReport(const ManufacturingReport&) { return false; } + virtual bool GetManufacturingReport(ManufacturingReport*) { return false; } + + virtual bool SetUUIDReport(const UUIDReport&) { return false; } + virtual bool GetUUIDReport(UUIDReport*) { return false; } + + virtual bool SetTemperatureReport(const TemperatureReport&) { return false; } + virtual bool GetAllTemperatureReports(Array<Array<TemperatureReport> >*) { return false; } + + virtual bool GetGyroOffsetReport(GyroOffsetReport*) { return false; } + + virtual bool SetLensDistortionReport(const LensDistortionReport&) { return false; } + virtual bool GetLensDistortionReport(LensDistortionReport*) { return false; } +}; + +//------------------------------------------------------------------------------------- +// ***** LatencyTestConfiguration +// LatencyTestConfiguration specifies configuration information for the Oculus Latency Tester device. +struct LatencyTestConfiguration +{ + LatencyTestConfiguration(const Color& threshold, bool sendSamples = false) + : Threshold(threshold), SendSamples(sendSamples) + { + } + + // The color threshold for triggering a detected display change. + Color Threshold; + // Flag specifying whether we wish to receive a stream of color values from the sensor. + bool SendSamples; +}; + +//------------------------------------------------------------------------------------- +// ***** LatencyTestDisplay +// LatencyTestDisplay sets the mode and contents of the Latency Tester LED display. +// See the 'Latency Tester Specification' document for more details. +struct LatencyTestDisplay +{ + LatencyTestDisplay(UByte mode, UInt32 value) + : Mode(mode), Value(value) + { + } + + UByte Mode; // The display mode that we wish to select. + UInt32 Value; // The value to display. +}; + +//------------------------------------------------------------------------------------- +// ***** LatencyTestDevice + +// LatencyTestDevice provides an interface to the Oculus Latency Tester which is used to test 'motion to photon' latency. +class LatencyTestDevice : public HIDDeviceBase, public DeviceBase +{ +public: + LatencyTestDevice() + { } + + // Static constant for this device type, used in template cast type checks. + enum { EnumDeviceType = Device_LatencyTester }; + + virtual DeviceType GetType() const { return Device_LatencyTester; } + + // Specifies configuration information including the threshold for triggering a detected color change, + // and a flag to enable a stream of sensor values (typically used for debugging). + virtual bool SetConfiguration(const LatencyTestConfiguration& configuration, bool waitFlag = false) = 0; + + // Get configuration information from device. + virtual bool GetConfiguration(LatencyTestConfiguration* configuration) = 0; + + // Used to calibrate the latency tester at the start of a test. Display the specified color on the screen + // beneath the latency tester and then call this method. Calibration information is lost + // when power is removed from the device. + virtual bool SetCalibrate(const Color& calibrationColor, bool waitFlag = false) = 0; + + // Triggers the start of a measurement. This starts the millisecond timer on the device and + // causes it to respond with the 'MessageLatencyTestStarted' message. + virtual bool SetStartTest(const Color& targetColor, bool waitFlag = false) = 0; + + // Used to set the value displayed on the LED display panel. + virtual bool SetDisplay(const LatencyTestDisplay& display, bool waitFlag = false) = 0; + + virtual DeviceBase* GetDevice() { return this; } +}; + +} // namespace OVR + + + + +#endif diff --git a/LibOVR/Src/OVR_DeviceConstants.h b/LibOVR/Src/OVR_DeviceConstants.h new file mode 100644 index 0000000..6b40b7d --- /dev/null +++ b/LibOVR/Src/OVR_DeviceConstants.h @@ -0,0 +1,142 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_DeviceConstants.h +Content : Device constants +Created : February 5, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_DeviceConstants_h +#define OVR_DeviceConstants_h + +namespace OVR { + + +//------------------------------------------------------------------------------------- +// Different device types supported by OVR; this type is reported by DeviceBase::GetType. +// +enum DeviceType +{ + Device_None = 0, + Device_Manager = 1, + Device_HMD = 2, + Device_Sensor = 3, + Device_LatencyTester = 4, + Device_BootLoader = 5, + Device_Camera = 6, + Device_Display = 7, + Device_All = 0xFF // Set for enumeration only, to enumerate all device types. +}; + + + +//------------------------------------------------------------------------------------- +// Different lens distortion types supported by devices. +// +enum DistortionEqnType +{ + Distortion_No_Override = -1, + // These two are leagcy and deprecated. + Distortion_Poly4 = 0, // scale = (K0 + K1*r^2 + K2*r^4 + K3*r^6) + Distortion_RecipPoly4 = 1, // scale = 1/(K0 + K1*r^2 + K2*r^4 + K3*r^6) + + // CatmullRom10 is the preferred distortion format. + Distortion_CatmullRom10 = 2, // scale = Catmull-Rom spline through points (1.0, K[1]...K[9]) + + Distortion_LAST // For ease of enumeration. +}; + + +//------------------------------------------------------------------------------------- +// HMD types. +// +enum HmdTypeEnum +{ + HmdType_None, + + HmdType_DKProto, // First duct-tape model, never sold. + HmdType_DK1, // DevKit1 - on sale to developers. + HmdType_DKHDProto, // DKHD - shown at various shows, never sold. + HmdType_DKHD2Proto, // DKHD2, 5.85-inch panel, never sold. + HmdType_DKHDProto566Mi, // DKHD, 5.66-inch panel, never sold. + HmdType_CrystalCoveProto, // Crystal Cove, 5.66-inch panel, shown at shows but never sold. + HmdType_DK2, + + // Reminder - this header file is public - codenames only! + + HmdType_Unknown, // Used for unnamed HW lab experiments. + + HmdType_LAST +}; + + +//------------------------------------------------------------------------------------- +// HMD shutter types. +// +enum HmdShutterTypeEnum +{ + HmdShutter_Global, + HmdShutter_RollingTopToBottom, + HmdShutter_RollingLeftToRight, + HmdShutter_RollingRightToLeft, + // TODO: + // color-sequential e.g. LCOS? + // alternate eyes? + // alternate columns? + // outside-in? + + HmdShutter_LAST +}; + + + +//------------------------------------------------------------------------------------- +// For headsets that use eye cups +// +enum EyeCupType +{ + // Public lenses + EyeCup_DK1A = 0, + EyeCup_DK1B = 1, + EyeCup_DK1C = 2, + + EyeCup_DK2A = 3, + + // Internal R&D codenames. + // Reminder - this header file is public - codenames only! + EyeCup_DKHD2A, + EyeCup_OrangeA, + EyeCup_RedA, + EyeCup_PinkA, + EyeCup_BlueA, + EyeCup_Delilah1A, + EyeCup_Delilah2A, + EyeCup_JamesA, + EyeCup_SunMandalaA, + + EyeCup_LAST +}; + + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_DeviceHandle.cpp b/LibOVR/Src/OVR_DeviceHandle.cpp new file mode 100644 index 0000000..cf6f05f --- /dev/null +++ b/LibOVR/Src/OVR_DeviceHandle.cpp @@ -0,0 +1,185 @@ +/************************************************************************************ + +Filename : OVR_DeviceHandle.cpp +Content : Implementation of device handle class +Created : February 5, 2013 +Authors : Lee Cooper + +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 "OVR_DeviceHandle.h" + +#include "OVR_DeviceImpl.h" + +namespace OVR { + +//------------------------------------------------------------------------------------- +// ***** DeviceHandle + +DeviceHandle::DeviceHandle(DeviceCreateDesc* impl) : pImpl(impl) +{ + if (pImpl) + pImpl->AddRef(); +} + +DeviceHandle::DeviceHandle(const DeviceHandle& src) : pImpl(src.pImpl) +{ + if (pImpl) + pImpl->AddRef(); +} + +DeviceHandle::~DeviceHandle() +{ + if (pImpl) + pImpl->Release(); +} + +void DeviceHandle::operator = (const DeviceHandle& src) +{ + if (src.pImpl) + src.pImpl->AddRef(); + if (pImpl) + pImpl->Release(); + pImpl = src.pImpl; +} + +DeviceBase* DeviceHandle::GetDevice_AddRef() const +{ + if (pImpl && pImpl->pDevice) + { + pImpl->pDevice->AddRef(); + return pImpl->pDevice; + } + return NULL; +} + +// Returns true, if the handle contains the same device ptr +// as specified in the parameter. +bool DeviceHandle::IsDevice(DeviceBase* pdev) const +{ + return (pdev && pImpl && pImpl->pDevice) ? + pImpl->pDevice == pdev : false; +} + +DeviceType DeviceHandle::GetType() const +{ + return pImpl ? pImpl->Type : Device_None; +} + +bool DeviceHandle::GetDeviceInfo(DeviceInfo* info) const +{ + return pImpl ? pImpl->GetDeviceInfo(info) : false; +} +bool DeviceHandle::IsAvailable() const +{ + // This isn't "atomically safe", but the function only returns the + // recent state that may change. + return pImpl ? (pImpl->Enumerated && pImpl->pLock->pManager) : false; +} + +bool DeviceHandle::IsCreated() const +{ + return pImpl ? (pImpl->pDevice != 0) : false; +} + +DeviceBase* DeviceHandle::CreateDevice() +{ + if (!pImpl) + return 0; + + DeviceBase* device = 0; + Ptr<DeviceManagerImpl> manager= 0; + + // Since both manager and device pointers can only be destroyed during a lock, + // hold it while checking for availability. + // AddRef to manager so that it doesn't get released on us. + { + Lock::Locker deviceLockScope(pImpl->GetLock()); + + if (pImpl->pDevice) + { + pImpl->pDevice->AddRef(); + return pImpl->pDevice; + } + manager = pImpl->GetManagerImpl(); + } + + if (manager) + { + if (manager->GetThreadId() != OVR::GetCurrentThreadId()) + { + // Queue up a CreateDevice request. This fills in '&device' with AddRefed value, + // or keep it at null. + manager->GetThreadQueue()->PushCallAndWaitResult( + manager.GetPtr(), &DeviceManagerImpl::CreateDevice_MgrThread, + &device, pImpl, (DeviceBase*)0); + } + else + device = manager->CreateDevice_MgrThread(pImpl, (DeviceBase*)0); + } + return device; +} + +void DeviceHandle::Clear() +{ + if (pImpl) + { + pImpl->Release(); + pImpl = 0; + } +} + +bool DeviceHandle::enumerateNext(const DeviceEnumerationArgs& args) +{ + if (GetType() == Device_None) + return false; + + Ptr<DeviceManagerImpl> managerKeepAlive; + Lock::Locker lockScope(pImpl->GetLock()); + + DeviceCreateDesc* next = pImpl; + // If manager was destroyed, we get removed from the list. + if (!pImpl->pNext) + return false; + + managerKeepAlive = next->GetManagerImpl(); + OVR_ASSERT(managerKeepAlive); + + do { + next = next->pNext; + + if (managerKeepAlive->Devices.IsNull(next)) + { + pImpl->Release(); + pImpl = 0; + return false; + } + + } while(!args.MatchRule(next->Type, next->Enumerated)); + + next->AddRef(); + pImpl->Release(); + pImpl = next; + + return true; +} + +} // namespace OVR + diff --git a/LibOVR/Src/OVR_DeviceHandle.h b/LibOVR/Src/OVR_DeviceHandle.h new file mode 100644 index 0000000..dd3e92b --- /dev/null +++ b/LibOVR/Src/OVR_DeviceHandle.h @@ -0,0 +1,108 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_DeviceHandle.h +Content : Handle to a device that was enumerated +Created : February 5, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_DeviceHandle_h +#define OVR_DeviceHandle_h + +#include "OVR_DeviceConstants.h" + +namespace OVR { + +class DeviceBase; +class DeviceInfo; + +// Internal +class DeviceCreateDesc; +class DeviceEnumerationArgs; + + +//------------------------------------------------------------------------------------- +// ***** DeviceHandle + +// DeviceHandle references a specific device that was enumerated; it can be assigned +// directly from DeviceEnumerator. +// +// Devices represented by DeviceHandle are not necessarily created or available. +// A device may become unavailable if, for example, it its unplugged. If the device +// is available, it can be created by calling CreateDevice. +// + +class DeviceHandle +{ + friend class DeviceManager; + friend class DeviceManagerImpl; + template<class B> friend class HIDDeviceImpl; + +public: + DeviceHandle() : pImpl(0) { } + DeviceHandle(const DeviceHandle& src); + ~DeviceHandle(); + + void operator = (const DeviceHandle& src); + + bool operator == (const DeviceHandle& other) const { return pImpl == other.pImpl; } + bool operator != (const DeviceHandle& other) const { return pImpl != other.pImpl; } + + // operator bool() returns true if Handle/Enumerator points to a valid device. + operator bool () const { return GetType() != Device_None; } + + // Returns existing device, or NULL if !IsCreated. The returned ptr is + // addref-ed. + DeviceBase* GetDevice_AddRef() const; + DeviceType GetType() const; + bool GetDeviceInfo(DeviceInfo* info) const; + bool IsAvailable() const; + bool IsCreated() const; + // Returns true, if the handle contains the same device ptr + // as specified in the parameter. + bool IsDevice(DeviceBase*) const; + + // Creates a device, or returns AddRefed pointer if one is already created. + // New devices start out with RefCount of 1. + DeviceBase* CreateDevice(); + + // Creates a device, or returns AddRefed pointer if one is already created. + // New devices start out with RefCount of 1. DeviceT is used to cast the + // DeviceBase* to a concreete type. + template <class DeviceT> + DeviceT* CreateDeviceTyped() const + { + return static_cast<DeviceT*>(DeviceHandle(*this).CreateDevice()); + } + + // Resets the device handle to uninitialized state. + void Clear(); + +protected: + explicit DeviceHandle(DeviceCreateDesc* impl); + bool enumerateNext(const DeviceEnumerationArgs& args); + DeviceCreateDesc* pImpl; +}; + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_DeviceImpl.cpp b/LibOVR/Src/OVR_DeviceImpl.cpp new file mode 100644 index 0000000..5b77708 --- /dev/null +++ b/LibOVR/Src/OVR_DeviceImpl.cpp @@ -0,0 +1,794 @@ +/************************************************************************************ + +Filename : OVR_DeviceImpl.h +Content : Partial back-end independent implementation of Device interfaces +Created : October 10, 2012 +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 "OVR_DeviceImpl.h" +#include "Kernel/OVR_Atomic.h" +#include "Kernel/OVR_Log.h" +#include "Kernel/OVR_System.h" + +#include "OVR_DeviceImpl.h" +#include "OVR_SensorImpl.h" +#include "OVR_Profile.h" + +namespace OVR { + + +//------------------------------------------------------------------------------------- +// ***** MessageHandler + +// Threading notes: +// The OnMessage() handler and SetMessageHandler are currently synchronized +// through a separately stored shared Lock object to avoid calling the handler +// from background thread while it's being removed. + +static SharedLock MessageHandlerSharedLock; + + +class MessageHandlerImpl +{ +public: + enum + { + MaxHandlerRefsCount = 4 + }; + + MessageHandlerImpl() + : pLock(MessageHandlerSharedLock.GetLockAddRef()), HandlerRefsCount(0) + { + } + ~MessageHandlerImpl() + { + MessageHandlerSharedLock.ReleaseLock(pLock); + pLock = 0; + } + + static MessageHandlerImpl* FromHandler(MessageHandler* handler) + { return (MessageHandlerImpl*)&handler->Internal; } + static const MessageHandlerImpl* FromHandler(const MessageHandler* handler) + { return (const MessageHandlerImpl*)&handler->Internal; } + + // This lock is held while calling a handler and when we are applied/ + // removed from a device. + Lock* pLock; + // List of devices we are applied to. + int HandlerRefsCount; + MessageHandlerRef* pHandlerRefs[MaxHandlerRefsCount]; +}; + + +MessageHandlerRef::MessageHandlerRef(DeviceBase* device) + : pLock(MessageHandlerSharedLock.GetLockAddRef()), pDevice(device), HandlersCount(0) +{ +} + +MessageHandlerRef::~MessageHandlerRef() +{ + { + Lock::Locker lockScope(pLock); + + while (HandlersCount > 0) + removeHandler(0); + } + MessageHandlerSharedLock.ReleaseLock(pLock); + pLock = 0; +} + +void MessageHandlerRef::Call(const Message& msg) +{ + Lock::Locker lockScope(pLock); + + for (int i = 0; i < HandlersCount; i++) + pHandlers[i]->OnMessage(msg); +} + +void MessageHandlerRef::AddHandler(MessageHandler* handler) +{ + OVR_ASSERT(!handler || + MessageHandlerImpl::FromHandler(handler)->pLock == pLock); + Lock::Locker lockScope(pLock); + AddHandler_NTS(handler); +} + +void MessageHandlerRef::AddHandler_NTS(MessageHandler* handler) +{ + OVR_ASSERT(handler != NULL); + + OVR_ASSERT(HandlersCount < MaxHandlersCount); + for (int i = 0; i < HandlersCount; i++) + if (pHandlers[i] == handler) + // handler already installed - do nothing + return; + pHandlers[HandlersCount] = handler; + HandlersCount++; + + MessageHandlerImpl* handlerImpl = MessageHandlerImpl::FromHandler(handler); + OVR_ASSERT(handlerImpl->HandlerRefsCount < MessageHandlerImpl::MaxHandlerRefsCount); + handlerImpl->pHandlerRefs[handlerImpl->HandlerRefsCount] = this; + handlerImpl->HandlerRefsCount++; + + // TBD: Call notifier on device? +} + +bool MessageHandlerRef::RemoveHandler(MessageHandler* handler) +{ + Lock::Locker lockScope(pLock); + + for (int i = 0; i < HandlersCount; i++) + { + if (pHandlers[i] == handler) + return removeHandler(i); + } + return false; +} + +bool MessageHandlerRef::removeHandler(int idx) +{ + OVR_ASSERT(idx < HandlersCount); + + MessageHandlerImpl* handlerImpl = MessageHandlerImpl::FromHandler(pHandlers[idx]); + for (int i = 0; i < handlerImpl->HandlerRefsCount; i++) + if (handlerImpl->pHandlerRefs[i] == this) + { + handlerImpl->pHandlerRefs[i] = handlerImpl->pHandlerRefs[handlerImpl->HandlerRefsCount - 1]; + handlerImpl->HandlerRefsCount--; + + pHandlers[idx] = pHandlers[HandlersCount - 1]; + HandlersCount--; + + return true; + } + + // couldn't find a link in the opposite direction, assert in Debug + OVR_ASSERT(0); + + pHandlers[idx] = pHandlers[HandlersCount - 1]; + HandlersCount--; + + return true; +} + +MessageHandler::MessageHandler() +{ + OVR_COMPILER_ASSERT(sizeof(Internal) > sizeof(MessageHandlerImpl)); + Construct<MessageHandlerImpl>(Internal); +} + +MessageHandler::~MessageHandler() +{ + MessageHandlerImpl* handlerImpl = MessageHandlerImpl::FromHandler(this); + { + Lock::Locker lockedScope(handlerImpl->pLock); + OVR_ASSERT_LOG(handlerImpl->HandlerRefsCount == 0, + ("~MessageHandler %p - Handler still active; call RemoveHandlerFromDevices", this)); + } + + Destruct<MessageHandlerImpl>(handlerImpl); +} + +bool MessageHandler::IsHandlerInstalled() const +{ + const MessageHandlerImpl* handlerImpl = MessageHandlerImpl::FromHandler(this); + Lock::Locker lockedScope(handlerImpl->pLock); + + return handlerImpl->HandlerRefsCount > 0; +} + +void MessageHandler::RemoveHandlerFromDevices() +{ + MessageHandlerImpl* handlerImpl = MessageHandlerImpl::FromHandler(this); + Lock::Locker lockedScope(handlerImpl->pLock); + + while (handlerImpl->HandlerRefsCount > 0) + { + MessageHandlerRef* use = handlerImpl->pHandlerRefs[0]; + use->RemoveHandler(this); + } +} + +Lock* MessageHandler::GetHandlerLock() const +{ + const MessageHandlerImpl* handlerImpl = MessageHandlerImpl::FromHandler(this); + return handlerImpl->pLock; +} + + +//------------------------------------------------------------------------------------- +// ***** DeviceBase + + +// Delegate relevant implementation to DeviceRectord to avoid re-implementation in +// every derived Device. +void DeviceBase::AddRef() +{ + getDeviceCommon()->DeviceAddRef(); +} +void DeviceBase::Release() +{ + getDeviceCommon()->DeviceRelease(); +} +DeviceBase* DeviceBase::GetParent() const +{ + return getDeviceCommon()->pParent.GetPtr(); +} +DeviceManager* DeviceBase::GetManager() const +{ + return getDeviceCommon()->pCreateDesc->GetManagerImpl(); +} + +void DeviceBase::AddMessageHandler(MessageHandler* handler) +{ + getDeviceCommon()->HandlerRef.AddHandler(handler); +} + +DeviceType DeviceBase::GetType() const +{ + return getDeviceCommon()->pCreateDesc->Type; +} + +bool DeviceBase::GetDeviceInfo(DeviceInfo* info) const +{ + return getDeviceCommon()->pCreateDesc->GetDeviceInfo(info); + //info->Name[0] = 0; + //return false; +} + +// Returns true if device is connected and usable +bool DeviceBase::IsConnected() +{ + return getDeviceCommon()->ConnectedFlag; +} + +// returns the MessageHandler's lock +Lock* DeviceBase::GetHandlerLock() const +{ + return getDeviceCommon()->HandlerRef.GetLock(); +} + +// Derive DeviceManagerCreateDesc to provide abstract function implementation. +class DeviceManagerCreateDesc : public DeviceCreateDesc +{ +public: + DeviceManagerCreateDesc(DeviceFactory* factory) + : DeviceCreateDesc(factory, Device_Manager) { } + + // We don't need there on Manager since it isn't assigned to DeviceHandle. + virtual DeviceCreateDesc* Clone() const { return 0; } + virtual MatchResult MatchDevice(const DeviceCreateDesc&, + DeviceCreateDesc**) const { return Match_None; } + virtual DeviceBase* NewDeviceInstance() { return 0; } + virtual bool GetDeviceInfo(DeviceInfo*) const { return false; } +}; + +//------------------------------------------------------------------------------------- +// ***** DeviceManagerImpl + +DeviceManagerImpl::DeviceManagerImpl() + : DeviceImpl<OVR::DeviceManager>(CreateManagerDesc(), 0) + //,DeviceCreateDescList(pCreateDesc ? pCreateDesc->pLock : 0) +{ + if (pCreateDesc) + { + pCreateDesc->pLock->pManager = this; + } +} + +DeviceManagerImpl::~DeviceManagerImpl() +{ + // Shutdown must've been called. + OVR_ASSERT(!pCreateDesc->pDevice); + + // Remove all factories + while(!Factories.IsEmpty()) + { + DeviceFactory* factory = Factories.GetFirst(); + factory->RemovedFromManager(); + factory->RemoveNode(); + } +} + +DeviceCreateDesc* DeviceManagerImpl::CreateManagerDesc() +{ + DeviceCreateDesc* managerDesc = new DeviceManagerCreateDesc(0); + if (managerDesc) + { + managerDesc->pLock = *new DeviceManagerLock; + } + return managerDesc; +} + +bool DeviceManagerImpl::Initialize(DeviceBase* parent) +{ + OVR_UNUSED(parent); + if (!pCreateDesc || !pCreateDesc->pLock) + return false; + + pProfileManager = *ProfileManager::Create(); + + return true; +} + +void DeviceManagerImpl::Shutdown() +{ + // Remove all device descriptors from list while the lock is held. + // Some descriptors may survive longer due to handles. + while(!Devices.IsEmpty()) + { + DeviceCreateDesc* devDesc = Devices.GetFirst(); + OVR_ASSERT(!devDesc->pDevice); // Manager shouldn't be dying while Device exists. + devDesc->Enumerated = false; + devDesc->RemoveNode(); + devDesc->pNext = devDesc->pPrev = 0; + + if (devDesc->HandleCount == 0) + { + delete devDesc; + } + } + Devices.Clear(); + + // These must've been cleared by caller. + OVR_ASSERT(pCreateDesc->pDevice == 0); + OVR_ASSERT(pCreateDesc->pLock->pManager == 0); + + pProfileManager.Clear(); +} + + +// Callbacks for DeviceCreation/Release +DeviceBase* DeviceManagerImpl::CreateDevice_MgrThread(DeviceCreateDesc* createDesc, DeviceBase* parent) +{ + // Calls to DeviceManagerImpl::CreateDevice are enqueued with wait while holding pManager, + // so 'this' must remain valid. + OVR_ASSERT(createDesc->pLock->pManager); + + Lock::Locker devicesLock(GetLock()); + + // If device already exists, just AddRef to it. + if (createDesc->pDevice) + { + createDesc->pDevice->AddRef(); + return createDesc->pDevice; + } + + if (!parent) + parent = this; + + DeviceBase* device = createDesc->NewDeviceInstance(); + + if (device) + { + if (device->getDeviceCommon()->Initialize(parent)) + { + createDesc->pDevice = device; + } + else + { + // Don't go through Release() to avoid PushCall behaviour, + // as it is not needed here. + delete device; + device = 0; + } + } + + return device; +} + +Void DeviceManagerImpl::ReleaseDevice_MgrThread(DeviceBase* device) +{ + // descKeepAlive will keep ManagerLock object alive as well, + // allowing us to exit gracefully. + Ptr<DeviceCreateDesc> descKeepAlive; + Lock::Locker devicesLock(GetLock()); + DeviceCommon* devCommon = device->getDeviceCommon(); + + while(1) + { + UInt32 refCount = devCommon->RefCount; + + if (refCount > 1) + { + if (devCommon->RefCount.CompareAndSet_NoSync(refCount, refCount-1)) + { + // We decreented from initial count higher then 1; + // nothing else to do. + return 0; + } + } + else if (devCommon->RefCount.CompareAndSet_NoSync(1, 0)) + { + // { 1 -> 0 } decrement succeded. Destroy this device. + break; + } + } + + // At this point, may be releasing the device manager itself. + // This does not matter, however, since shutdown logic is the same + // in both cases. DeviceManager::Shutdown with begin shutdown process for + // the internal manager thread, which will eventually destroy itself. + // TBD: Clean thread shutdown. + descKeepAlive = devCommon->pCreateDesc; + descKeepAlive->pDevice = 0; + devCommon->Shutdown(); + delete device; + return 0; +} + + + +Void DeviceManagerImpl::EnumerateAllFactoryDevices() +{ + // 1. Mark matching devices as NOT enumerated. + // 2. Call factory to enumerate all HW devices, adding any device that + // was not matched. + // 3. Remove non-matching devices. + + Lock::Locker deviceLock(GetLock()); + + DeviceCreateDesc* devDesc, *nextdevDesc; + + // 1. + for(devDesc = Devices.GetFirst(); + !Devices.IsNull(devDesc); devDesc = devDesc->pNext) + { + //if (devDesc->pFactory == factory) + devDesc->Enumerated = false; + } + + // 2. + DeviceFactory* factory = Factories.GetFirst(); + while(!Factories.IsNull(factory)) + { + EnumerateFactoryDevices(factory); + factory = factory->pNext; + } + + + // 3. + for(devDesc = Devices.GetFirst(); + !Devices.IsNull(devDesc); devDesc = nextdevDesc) + { + // In case 'devDesc' gets removed. + nextdevDesc = devDesc->pNext; + + // Note, device might be not enumerated since it is opened and + // in use! Do NOT notify 'device removed' in this case (!AB) + if (!devDesc->Enumerated) + { + // This deletes the devDesc for HandleCount == 0 due to Release in DeviceHandle. + CallOnDeviceRemoved(devDesc); + + /* + if (devDesc->HandleCount == 0) + { + // Device must be dead if it ever existed, since it AddRefs to us. + // ~DeviceCreateDesc removes its node from list. + OVR_ASSERT(!devDesc->pDevice); + delete devDesc; + } + */ + } + } + + return 0; +} + +Ptr<DeviceCreateDesc> DeviceManagerImpl::AddDevice_NeedsLock( + const DeviceCreateDesc& createDesc) +{ + // If found, mark as enumerated and we are done. + DeviceCreateDesc* descCandidate = 0; + + for(DeviceCreateDesc* devDesc = Devices.GetFirst(); + !Devices.IsNull(devDesc); devDesc = devDesc->pNext) + { + DeviceCreateDesc::MatchResult mr = devDesc->MatchDevice(createDesc, &descCandidate); + if (mr == DeviceCreateDesc::Match_Found) + { + devDesc->Enumerated = true; + if (!devDesc->pDevice) + CallOnDeviceAdded(devDesc); + return devDesc; + } + } + + // Update candidate (this may involve writing fields to HMDDevice createDesc). + if (descCandidate) + { + bool newDevice = false; + if (descCandidate->UpdateMatchedCandidate(createDesc, &newDevice)) + { + descCandidate->Enumerated = true; + if (!descCandidate->pDevice || newDevice) + CallOnDeviceAdded(descCandidate); + return descCandidate; + } + } + + // If not found, add new device. + // - This stores a new descriptor with + // {pDevice = 0, HandleCount = 1, Enumerated = true} + DeviceCreateDesc* desc = createDesc.Clone(); + desc->pLock = pCreateDesc->pLock; + Devices.PushBack(desc); + desc->Enumerated = true; + + CallOnDeviceAdded(desc); + + return desc; +} + +Ptr<DeviceCreateDesc> DeviceManagerImpl::FindDevice( + const String& path, + DeviceType deviceType) +{ + Lock::Locker deviceLock(GetLock()); + DeviceCreateDesc* devDesc; + + for (devDesc = Devices.GetFirst(); + !Devices.IsNull(devDesc); devDesc = devDesc->pNext) + { + if ((deviceType == Device_None || deviceType == devDesc->Type) && + devDesc->MatchDevice(path)) + return devDesc; + } + return NULL; +} + +Ptr<DeviceCreateDesc> DeviceManagerImpl::FindHIDDevice(const HIDDeviceDesc& hidDevDesc, bool created) +{ + Lock::Locker deviceLock(GetLock()); + DeviceCreateDesc* devDesc; + + for (devDesc = Devices.GetFirst(); + !Devices.IsNull(devDesc); devDesc = devDesc->pNext) + { + if (created) + { // Search for matching device that is created + if (devDesc->MatchHIDDevice(hidDevDesc) && devDesc->pDevice) + return devDesc; + } + else + { // Search for any matching device + if (devDesc->MatchHIDDevice(hidDevDesc)) + return devDesc; + } + } + return NULL; +} + +void DeviceManagerImpl::DetectHIDDevice(const HIDDeviceDesc& hidDevDesc) +{ + Lock::Locker deviceLock(GetLock()); + DeviceFactory* factory = Factories.GetFirst(); + while(!Factories.IsNull(factory)) + { + if (factory->DetectHIDDevice(this, hidDevDesc)) + break; + factory = factory->pNext; + } + +} + +// Enumerates devices for a particular factory. +Void DeviceManagerImpl::EnumerateFactoryDevices(DeviceFactory* factory) +{ + + class FactoryEnumerateVisitor : public DeviceFactory::EnumerateVisitor + { + DeviceManagerImpl* pManager; + DeviceFactory* pFactory; + public: + FactoryEnumerateVisitor(DeviceManagerImpl* manager, DeviceFactory* factory) + : pManager(manager), pFactory(factory) { } + + virtual void Visit(const DeviceCreateDesc& createDesc) + { + pManager->AddDevice_NeedsLock(createDesc); + } + }; + + FactoryEnumerateVisitor newDeviceVisitor(this, factory); + factory->EnumerateDevices(newDeviceVisitor); + + + return 0; +} + + +DeviceEnumerator<> DeviceManagerImpl::EnumerateDevicesEx(const DeviceEnumerationArgs& args) +{ + Lock::Locker deviceLock(GetLock()); + + if (Devices.IsEmpty()) + return DeviceEnumerator<>(); + + DeviceCreateDesc* firstDeviceDesc = Devices.GetFirst(); + DeviceEnumerator<> e = enumeratorFromHandle(DeviceHandle(firstDeviceDesc), args); + + if (!args.MatchRule(firstDeviceDesc->Type, firstDeviceDesc->Enumerated)) + { + e.Next(); + } + + return e; +} + +//------------------------------------------------------------------------------------- +// ***** DeviceCommon + +void DeviceCommon::DeviceAddRef() +{ + RefCount++; +} + +void DeviceCommon::DeviceRelease() +{ + while(1) + { + UInt32 refCount = RefCount; + OVR_ASSERT(refCount > 0); + + if (refCount == 1) + { + DeviceManagerImpl* manager = pCreateDesc->GetManagerImpl(); + ThreadCommandQueue* queue = manager->GetThreadQueue(); + + // Enqueue ReleaseDevice for {1 -> 0} transition with no wait. + // We pass our reference ownership into the queue to destroy. + // It's in theory possible for another thread to re-steal our device reference, + // but that is checked for atomically in DeviceManagerImpl::ReleaseDevice. + if (!queue->PushCall(manager, &DeviceManagerImpl::ReleaseDevice_MgrThread, + pCreateDesc->pDevice)) + { + // PushCall shouldn't fail because background thread runs while manager is + // alive and we are holding Manager alive through pParent chain. + OVR_ASSERT(false); + } + + // Warning! At his point everything, including manager, may be dead. + break; + } + else if (RefCount.CompareAndSet_NoSync(refCount, refCount-1)) + { + break; + } + } +} + + + +//------------------------------------------------------------------------------------- +// ***** DeviceCreateDesc + + +void DeviceCreateDesc::AddRef() +{ + // Technically, HandleCount { 0 -> 1 } transition can only happen during Lock, + // but we leave this to caller to worry about (happens during enumeration). + HandleCount++; +} + +void DeviceCreateDesc::Release() +{ + while(1) + { + UInt32 handleCount = HandleCount; + // HandleCount must obviously be >= 1, since we are releasing it. + OVR_ASSERT(handleCount > 0); + + // {1 -> 0} transition may cause us to be destroyed, so require a lock. + if (handleCount == 1) + { + Ptr<DeviceManagerLock> lockKeepAlive; + Lock::Locker deviceLockScope(GetLock()); + + if (!HandleCount.CompareAndSet_NoSync(handleCount, 0)) + continue; + + OVR_ASSERT(pDevice == 0); + + // Destroy *this if the manager was destroyed already, or Enumerated + // is false (device no longer available). + if (!GetManagerImpl() || !Enumerated) + { + lockKeepAlive = pLock; + + // Remove from manager list (only matters for !Enumerated). + if (pNext) + { + RemoveNode(); + pNext = pPrev = 0; + } + + delete this; + } + + // Available DeviceCreateDesc may survive with { HandleCount == 0 }, + // in case it might be enumerated again later. + break; + } + else if (HandleCount.CompareAndSet_NoSync(handleCount, handleCount-1)) + { + break; + } + } +} + +HMDDevice* HMDDevice::Disconnect(SensorDevice* psensor) +{ + if (!psensor) + return NULL; + + OVR::DeviceManager* manager = GetManager(); + if (manager) + { + //DeviceManagerImpl* mgrImpl = static_cast<DeviceManagerImpl*>(manager); + Ptr<DeviceCreateDesc> desc = getDeviceCommon()->pCreateDesc; + if (desc) + { + class Visitor : public DeviceFactory::EnumerateVisitor + { + Ptr<DeviceCreateDesc> Desc; + public: + Visitor(DeviceCreateDesc* desc) : Desc(desc) {} + virtual void Visit(const DeviceCreateDesc& createDesc) + { + Lock::Locker lock(Desc->GetLock()); + Desc->UpdateMatchedCandidate(createDesc); + } + } visitor(desc); + //SensorDeviceImpl* sImpl = static_cast<SensorDeviceImpl*>(psensor); + + SensorDisplayInfoImpl displayInfo; + + if (psensor->GetFeatureReport(displayInfo.Buffer, SensorDisplayInfoImpl::PacketSize)) + { + displayInfo.Unpack(); + + // If we got display info, try to match / create HMDDevice as well + // so that sensor settings give preference. + if (displayInfo.DistortionType & SensorDisplayInfoImpl::Mask_BaseFmt) + { + SensorDeviceImpl::EnumerateHMDFromSensorDisplayInfo(displayInfo, visitor); + } + } + } + } + return this; +} + +bool HMDDevice::IsDisconnected() const +{ + OVR::HMDInfo info; + GetDeviceInfo(&info); + // if strlen(info.DisplayDeviceName) == 0 then + // this HMD is 'fake' (created using sensor). + return (strlen(info.DisplayDeviceName) == 0); +} + + +} // namespace OVR + diff --git a/LibOVR/Src/OVR_DeviceImpl.h b/LibOVR/Src/OVR_DeviceImpl.h new file mode 100644 index 0000000..8e737a5 --- /dev/null +++ b/LibOVR/Src/OVR_DeviceImpl.h @@ -0,0 +1,428 @@ +/************************************************************************************ + +Filename : OVR_DeviceImpl.h +Content : Partial back-end independent implementation of Device interfaces +Created : October 10, 2012 +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. + +*************************************************************************************/ + +#ifndef OVR_DeviceImpl_h +#define OVR_DeviceImpl_h + +#include "OVR_Device.h" +#include "Kernel/OVR_Atomic.h" +#include "Kernel/OVR_Log.h" +#include "Kernel/OVR_System.h" + +#include "Kernel/OVR_Threads.h" +#include "OVR_ThreadCommandQueue.h" +#include "OVR_HIDDevice.h" + +namespace OVR { + +class DeviceManagerImpl; +class DeviceFactory; + +enum +{ + Oculus_VendorId = 0x2833, + Device_Tracker_ProductId = 0x0001, + Device_Tracker2_ProductId = 0x0021, + Device_KTracker_ProductId = 0x0010, +}; + + +// Wrapper for MessageHandler that includes synchronization logic. +class MessageHandlerRef +{ + enum + { + MaxHandlersCount = 4 + }; +public: + MessageHandlerRef(DeviceBase* device); + ~MessageHandlerRef(); + + bool HasHandlers() const { return HandlersCount > 0; }; + void AddHandler(MessageHandler* handler); + // returns false if the handler is not found + bool RemoveHandler(MessageHandler* handler); + // Not-thread-safe version + void AddHandler_NTS(MessageHandler* handler); + + void Call(const Message& msg); + + Lock* GetLock() const { return pLock; } + DeviceBase* GetDevice() const { return pDevice; } + +private: + Lock* pLock; // Cached global handler lock. + DeviceBase* pDevice; + + int HandlersCount; + MessageHandler* pHandlers[MaxHandlersCount]; + + bool removeHandler(int idx); +}; + + +//------------------------------------------------------------------------------------- + +// DeviceManagerLock is a synchronization lock used by DeviceManager for Devices +// and is allocated separately for potentially longer lifetime. +// +// DeviceManagerLock is used for all of the following: +// - Adding/removing devices +// - Reporting manager lifetime (pManager != 0) for DeviceHandles +// - Protecting device creation/shutdown. + +class DeviceManagerLock : public RefCountBase<DeviceManagerLock> +{ +public: + Lock CreateLock; + DeviceManagerImpl* pManager; + + DeviceManagerLock() : pManager(0) { } +}; + + +// DeviceCreateDesc provides all of the information needed to create any device, a derived +// instance of this class is created by DeviceFactory during enumeration. +// - DeviceCreateDesc may or may not be a part of DeviceManager::Devices list (check pNext != 0). +// - Referenced and kept alive by DeviceHandle. + +class DeviceCreateDesc : public ListNode<DeviceCreateDesc>, public NewOverrideBase +{ + void operator = (const DeviceCreateDesc&) { } // Assign not supported; suppress MSVC warning. +public: + DeviceCreateDesc(DeviceFactory* factory, DeviceType type) + : pFactory(factory), Type(type), pLock(0), HandleCount(0), pDevice(0), Enumerated(true) + { + pNext = pPrev = 0; + } + + virtual ~DeviceCreateDesc() + { + OVR_ASSERT(!pDevice); + if (pNext) + RemoveNode(); + } + + DeviceManagerImpl* GetManagerImpl() const { return pLock->pManager; } + Lock* GetLock() const { return &pLock->CreateLock; } + + // DeviceCreateDesc reference counting is tied to Devices list management, + // see comments for HandleCount. + void AddRef(); + void Release(); + + + // *** Device creation/matching Interface + + + // Cloning copies us to an allocated object when new device is enumerated. + virtual DeviceCreateDesc* Clone() const = 0; + // Creates a new device instance without Initializing it; the + // later is done my Initialize()/Shutdown() methods of the device itself. + virtual DeviceBase* NewDeviceInstance() = 0; + // Override to return device-specific info. + virtual bool GetDeviceInfo(DeviceInfo* info) const = 0; + + + enum MatchResult + { + Match_None, + Match_Found, + Match_Candidate + }; + + // Override to return Match_Found if descriptor matches our device. + // Match_Candidate can be returned, with pcandicate update, if this may be a match + // but more searching is necessary. If this is the case UpdateMatchedCandidate will be called. + virtual MatchResult MatchDevice(const DeviceCreateDesc& other, + DeviceCreateDesc** pcandidate) const = 0; + + // Called for matched candidate after all potential matches are iterated. + // Used to update HMDevice creation arguments from Sensor. + // Optional return param 'newDeviceFlag' will be set to true if the + // 'desc' refers to a new device; false, otherwise. + // Return 'false' to create new object, 'true' if done with this argument. + virtual bool UpdateMatchedCandidate( + const DeviceCreateDesc& desc, bool* newDeviceFlag = NULL) + { OVR_UNUSED2(desc, newDeviceFlag); return false; } + + // Matches HID device to the descriptor. + virtual bool MatchHIDDevice(const HIDDeviceDesc&) const { return false; } + + // Matches device by path. + virtual bool MatchDevice(const String& /*path*/) { return false; } +//protected: + DeviceFactory* const pFactory; + const DeviceType Type; + + // List in which this descriptor lives. pList->CreateLock required if added/removed. + Ptr<DeviceManagerLock> pLock; + + // Strong references to us: Incremented by Device, DeviceHandles & Enumerators. + // May be 0 if device not created and there are no handles. + // Following transitions require pList->CreateLock: + // {1 -> 0}: May delete & remove handle if no longer available. + // {0 -> 1}: Device creation is only possible if manager is still alive. + AtomicInt<UInt32> HandleCount; + // If not null, points to our created device instance. Modified during lock only. + DeviceBase* pDevice; + // True if device is marked as available during enumeration. + bool Enumerated; +}; + + + +// Common data present in the implementation of every DeviceBase. +// Injected by DeviceImpl. +class DeviceCommon +{ +public: + AtomicInt<UInt32> RefCount; + Ptr<DeviceCreateDesc> pCreateDesc; + Ptr<DeviceBase> pParent; + volatile bool ConnectedFlag; + MessageHandlerRef HandlerRef; + + DeviceCommon(DeviceCreateDesc* createDesc, DeviceBase* device, DeviceBase* parent) + : RefCount(1), pCreateDesc(createDesc), pParent(parent), + ConnectedFlag(true), HandlerRef(device) + { + } + virtual ~DeviceCommon() {} + + // Device reference counting delegates to Manager thread to actually kill devices. + void DeviceAddRef(); + void DeviceRelease(); + + Lock* GetLock() const { return pCreateDesc->GetLock(); } + + virtual bool Initialize(DeviceBase* parent) = 0; + virtual void Shutdown() = 0; +}; + + +//------------------------------------------------------------------------------------- +// DeviceImpl address DeviceRecord implementation to a device base class B. +// B must be derived form DeviceBase. + +template<class B> +class DeviceImpl : public B, public DeviceCommon +{ +public: + DeviceImpl(DeviceCreateDesc* createDesc, DeviceBase* parent) + : DeviceCommon(createDesc, getThis(), parent) + { + } + + // Convenience method to avoid manager access typecasts. + DeviceManagerImpl* GetManagerImpl() const { return pCreateDesc->pLock->pManager; } + + // Inline to avoid warnings. + DeviceImpl* getThis() { return this; } + + // Common implementation delegate to avoid virtual inheritance and dynamic casts. + virtual DeviceCommon* getDeviceCommon() const { return (DeviceCommon*)this; } + + /* + virtual void AddRef() { pCreateDesc->DeviceAddRef(); } + virtual void Release() { pCreateDesc->DeviceRelease(); } + virtual DeviceBase* GetParent() const { return pParent.GetPtr(); } + virtual DeviceManager* GetManager() const { return pCreateDesc->pLock->pManager;} + virtual void SetMessageHandler(MessageHandler* handler) { HanderRef.SetHandler(handler); } + virtual MessageHandler* GetMessageHandler() const { return HanderRef.GetHandler(); } + virtual DeviceType GetType() const { return pCreateDesc->Type; } + virtual DeviceType GetType() const { return pCreateDesc->Type; } + */ +}; + + +//------------------------------------------------------------------------------------- +// ***** DeviceFactory + +// DeviceFactory is maintained in DeviceManager for each separately-enumerable +// device type; factories allow separation of unrelated enumeration code. + +class DeviceFactory : public ListNode<DeviceFactory>, public NewOverrideBase +{ +public: + + DeviceFactory() : pManager(0) + { + pNext = pPrev = 0; + } + virtual ~DeviceFactory() { } + + DeviceManagerImpl* GetManagerImpl() { return pManager; } + + // Notifiers called when we are added to/removed from a device. + virtual bool AddedToManager(DeviceManagerImpl* manager) + { + OVR_ASSERT(pManager == 0); + pManager = manager; + return true; + } + + virtual void RemovedFromManager() + { + pManager = 0; + } + + + // *** Device Enumeration/Creation Support + + // Passed to EnumerateDevices to be informed of every device detected. + class EnumerateVisitor + { + public: + virtual void Visit(const DeviceCreateDesc& createDesc) = 0; + }; + + // Enumerates factory devices by notifying EnumerateVisitor about every + // device that is present. + virtual void EnumerateDevices(EnumerateVisitor& visitor) = 0; + + // Matches vendorId/productId pair with the factory; returns 'true' + // if the factory can handle the device. + virtual bool MatchVendorProduct(UInt16 vendorId, UInt16 productId) const + { + OVR_UNUSED2(vendorId, productId); + return false; + } + + // Detects the HID device and adds the DeviceCreateDesc into Devices list, if + // the device belongs to this factory. Returns 'false', if not. + virtual bool DetectHIDDevice(DeviceManager* pdevMgr, const HIDDeviceDesc& desc) + { + OVR_UNUSED2(pdevMgr, desc); + return false; + } + +protected: + DeviceManagerImpl* pManager; +}; + + +//------------------------------------------------------------------------------------- +// ***** DeviceManagerImpl + +// DeviceManagerImpl is a partial default DeviceManager implementation that +// maintains a list of devices and supports their enumeration. + +class DeviceManagerImpl : public DeviceImpl<OVR::DeviceManager>, public ThreadCommandQueue +{ +public: + DeviceManagerImpl(); + ~DeviceManagerImpl(); + + // Constructor helper function to create Descriptor and manager lock during initialization. + static DeviceCreateDesc* CreateManagerDesc(); + + // DeviceManagerImpl provides partial implementation of Initialize/Shutdown that must + // be called by the platform-specific derived class. + virtual bool Initialize(DeviceBase* parent); + virtual void Shutdown(); + + + // Every DeviceManager has an associated profile manager, which is used to store + // user settings that may affect device behavior. + virtual ProfileManager* GetProfileManager() const { return pProfileManager.GetPtr(); } + + // Override to return ThreadCommandQueue implementation used to post commands + // to the background device manager thread (that must be created by Initialize). + virtual ThreadCommandQueue* GetThreadQueue() = 0; + + // Returns the thread id of the DeviceManager. + virtual ThreadId GetThreadId() const = 0; + + virtual DeviceEnumerator<> EnumerateDevicesEx(const DeviceEnumerationArgs& args); + + + // + void AddFactory(DeviceFactory* factory) + { + // This lock is only needed if we call AddFactory after manager thread creation. + Lock::Locker scopeLock(GetLock()); + Factories.PushBack(factory); + factory->AddedToManager(this); + } + + void CallOnDeviceAdded(DeviceCreateDesc* desc) + { + HandlerRef.Call(MessageDeviceStatus(Message_DeviceAdded, this, DeviceHandle(desc))); + } + void CallOnDeviceRemoved(DeviceCreateDesc* desc) + { + HandlerRef.Call(MessageDeviceStatus(Message_DeviceRemoved, this, DeviceHandle(desc))); + } + + // Helper to access Common data for a device. + static DeviceCommon* GetDeviceCommon(DeviceBase* device) + { + return device->getDeviceCommon(); + } + + + // Background-thread callbacks for DeviceCreation/Release. These + DeviceBase* CreateDevice_MgrThread(DeviceCreateDesc* createDesc, DeviceBase* parent = 0); + Void ReleaseDevice_MgrThread(DeviceBase* device); + + + // Calls EnumerateDevices() on all factories + virtual Void EnumerateAllFactoryDevices(); + // Enumerates devices for a particular factory. + virtual Void EnumerateFactoryDevices(DeviceFactory* factory); + + virtual HIDDeviceManager* GetHIDDeviceManager() const + { + return HidDeviceManager; + } + + // Adds device (DeviceCreateDesc*) into Devices. Returns NULL, + // if unsuccessful or device is already in the list. + virtual Ptr<DeviceCreateDesc> AddDevice_NeedsLock(const DeviceCreateDesc& createDesc); + + // Finds a device descriptor by path and optional type. + Ptr<DeviceCreateDesc> FindDevice(const String& path, DeviceType = Device_None); + + // Finds HID device by HIDDeviceDesc. + Ptr<DeviceCreateDesc> FindHIDDevice(const HIDDeviceDesc&, bool created); + void DetectHIDDevice(const HIDDeviceDesc&); + + // Manager Lock-protected list of devices. + List<DeviceCreateDesc> Devices; + + // Factories used to detect and manage devices. + List<DeviceFactory> Factories; + +protected: + Ptr<HIDDeviceManager> HidDeviceManager; + Ptr<ProfileManager> pProfileManager; +}; + + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_DeviceMessages.h b/LibOVR/Src/OVR_DeviceMessages.h new file mode 100644 index 0000000..c182404 --- /dev/null +++ b/LibOVR/Src/OVR_DeviceMessages.h @@ -0,0 +1,273 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_DeviceMessages.h +Content : Definition of messages generated by devices +Created : February 5, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_DeviceMessages_h +#define OVR_DeviceMessages_h + +#include "OVR_DeviceConstants.h" +#include "OVR_DeviceHandle.h" + +#include "Kernel/OVR_Math.h" +#include "Kernel/OVR_Array.h" +#include "Kernel/OVR_Color.h" +#include "Kernel/OVR_String.h" + +namespace OVR { + +class DeviceBase; +class DeviceHandle; +class String; + + +#define OVR_MESSAGETYPE(devName, msgIndex) ((Device_##devName << 8) | msgIndex) + +// MessageType identifies the structure of the Message class; based on the message, +// casting can be used to obtain the exact value. +enum MessageType +{ + // Used for unassigned message types. + Message_None = 0, + + // Device Manager Messages + Message_DeviceAdded = OVR_MESSAGETYPE(Manager, 0), // A new device is detected by manager. + Message_DeviceRemoved = OVR_MESSAGETYPE(Manager, 1), // Existing device has been plugged/unplugged. + // Sensor Messages + Message_BodyFrame = OVR_MESSAGETYPE(Sensor, 0), // Emitted by sensor at regular intervals. + Message_ExposureFrame = OVR_MESSAGETYPE(Sensor, 1), + Message_PixelRead = OVR_MESSAGETYPE(Sensor, 2), + + // Latency Tester Messages + Message_LatencyTestSamples = OVR_MESSAGETYPE(LatencyTester, 0), + Message_LatencyTestColorDetected = OVR_MESSAGETYPE(LatencyTester, 1), + Message_LatencyTestStarted = OVR_MESSAGETYPE(LatencyTester, 2), + Message_LatencyTestButton = OVR_MESSAGETYPE(LatencyTester, 3), + + Message_CameraFrame = OVR_MESSAGETYPE(Camera, 0), + Message_CameraAdded = OVR_MESSAGETYPE(Camera, 1), +}; + +//------------------------------------------------------------------------------------- +// Base class for all messages. +class Message +{ +public: + Message(MessageType type = Message_None, + DeviceBase* pdev = 0) : Type(type), pDevice(pdev) + { } + + MessageType Type; // What kind of message this is. + DeviceBase* pDevice; // Device that emitted the message. +}; + + +// Sensor BodyFrame notification. +// Sensor uses Right-Handed coordinate system to return results, with the following +// axis definitions: +// - Y Up positive +// - X Right Positive +// - Z Back Positive +// Rotations a counter-clockwise (CCW) while looking in the negative direction +// of the axis. This means they are interpreted as follows: +// - Roll is rotation around Z, counter-clockwise (tilting left) in XY plane. +// - Yaw is rotation around Y, positive for turning left. +// - Pitch is rotation around X, positive for pitching up. + +//------------------------------------------------------------------------------------- +// ***** Sensor + +class MessageBodyFrame : public Message +{ +public: + MessageBodyFrame(DeviceBase* dev) + : Message(Message_BodyFrame, dev), Temperature(0.0f), TimeDelta(0.0f) + { + } + + Vector3f Acceleration; // Acceleration in m/s^2. + Vector3f RotationRate; // Angular velocity in rad/s. + Vector3f MagneticField; // Magnetic field strength in Gauss. + float Temperature; // Temperature reading on sensor surface, in degrees Celsius. + float TimeDelta; // Time passed since last Body Frame, in seconds. + + // The absolute time from the host computers perspective that the message should be + // interpreted as. This is based on incoming timestamp and processed by a filter + // that syncs the clocks while attempting to keep the distance between messages + // device clock matching. + // + // Integration should use TimeDelta, but prediction into the future should derive + // the delta time from PredictToSeconds - AbsoluteTimeSeconds. + // + // This value will generally be <= the return from a call to ovr_GetTimeInSeconds(), + // but could be greater by under 1 ms due to system time update interrupt delays. + // + double AbsoluteTimeSeconds; +}; + +// Sent when we receive a device status changes (e.g.: +// Message_DeviceAdded, Message_DeviceRemoved). +class MessageDeviceStatus : public Message +{ +public: + MessageDeviceStatus(MessageType type, DeviceBase* dev, const DeviceHandle &hdev) + : Message(type, dev), Handle(hdev) { } + + DeviceHandle Handle; +}; + +class MessageExposureFrame : public Message +{ +public: + MessageExposureFrame(DeviceBase* dev) + : Message(Message_ExposureFrame, dev), + CameraPattern(0), CameraFrameCount(0), CameraTimeSeconds(0) { } + + UByte CameraPattern; + UInt32 CameraFrameCount; + double CameraTimeSeconds; +}; + +class MessagePixelRead : public Message +{ +public: + MessagePixelRead(DeviceBase* dev) + : Message(Message_PixelRead, dev), + PixelReadValue(0), SensorTimeSeconds(0), FrameTimeSeconds(0) { } + + UByte PixelReadValue; + UInt32 RawSensorTime; + UInt32 RawFrameTime; + double SensorTimeSeconds; + double FrameTimeSeconds; +}; + +//------------------------------------------------------------------------------------- +// ***** Latency Tester + +// Sent when we receive Latency Tester samples. +class MessageLatencyTestSamples : public Message +{ +public: + MessageLatencyTestSamples(DeviceBase* dev) + : Message(Message_LatencyTestSamples, dev) + { + } + + Array<Color> Samples; +}; + +// Sent when a Latency Tester 'color detected' event occurs. +class MessageLatencyTestColorDetected : public Message +{ +public: + MessageLatencyTestColorDetected(DeviceBase* dev) + : Message(Message_LatencyTestColorDetected, dev) + { + } + + UInt16 Elapsed; + Color DetectedValue; + Color TargetValue; +}; + +// Sent when a Latency Tester 'change color' event occurs. +class MessageLatencyTestStarted : public Message +{ +public: + MessageLatencyTestStarted(DeviceBase* dev) + : Message(Message_LatencyTestStarted, dev) + { + } + + Color TargetValue; +}; + +// Sent when a Latency Tester 'button' event occurs. +class MessageLatencyTestButton : public Message +{ +public: + MessageLatencyTestButton(DeviceBase* dev) + : Message(Message_LatencyTestButton, dev) + { + } + +}; + +//------------------------------------------------------------------------------------- +// ***** Camera + +// Sent by camera, frame. +class MessageCameraFrame : public Message +{ +public: + MessageCameraFrame(DeviceBase* dev) + : Message(Message_CameraFrame, dev), CameraHandle(NULL), pFrameData(NULL) + { + LostFrames = 0; + } + + void SetInfo(UInt32 frameNumber, double timeSeconds, UInt32 width, UInt32 height, UInt32 format) + { + FrameNumber = frameNumber; + ArrivalTimeSeconds = timeSeconds; + Width = width; + Height = height; + Format = format; + } + + void SetData(const UByte* pdata, UInt32 sizeInBytes) + { + pFrameData = pdata; + FrameSizeInBytes = sizeInBytes; + } + + UInt32 FrameNumber; // an index of the frame + double ArrivalTimeSeconds; // frame time in seconds, as recorded by the host computer + const UByte* pFrameData; // a ptr to frame data. + UInt32 FrameSizeInBytes; // size of the data in the pFrameData. + UInt32 Width, Height; // width & height in pixels. + UInt32 Format; // format of pixel, see CameraDevice::PixelFormat enum + UInt32 LostFrames; // number of lost frames before this frame + String DeviceIdentifier; // identifies the device sensing the message + UInt32* CameraHandle; // Identifies the camera object associated with this frame +}; + +// Sent when a new camera is connected +class MessageCameraAdded : public Message +{ +public: + MessageCameraAdded(DeviceBase* dev) + : Message(Message_CameraAdded, dev) { } + + MessageCameraAdded(UInt32* cam) + : Message(Message_CameraAdded, NULL), CameraHandle(cam) { } + + UInt32* CameraHandle; // Identifies the camera object associated with this frame +}; + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_HIDDevice.h b/LibOVR/Src/OVR_HIDDevice.h new file mode 100644 index 0000000..24bfcfa --- /dev/null +++ b/LibOVR/Src/OVR_HIDDevice.h @@ -0,0 +1,154 @@ +/************************************************************************************ + +Filename : OVR_HIDDevice.h +Content : Cross platform HID device interface. +Created : February 22, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_HIDDevice_h +#define OVR_HIDDevice_h + +#include "OVR_HIDDeviceBase.h" + +#include "Kernel/OVR_RefCount.h" +#include "Kernel/OVR_String.h" +#include "Kernel/OVR_Timer.h" + +namespace OVR { + +class HIDDevice; +class DeviceManager; + +// HIDDeviceDesc contains interesting attributes of a HID device, including a Path +// that can be used to create it. +struct HIDDeviceDesc +{ + UInt16 VendorId; + UInt16 ProductId; + UInt16 VersionNumber; + UInt16 Usage; + UInt16 UsagePage; + String Path; // Platform specific. + String Manufacturer; + String Product; + String SerialNumber; +}; + +// HIDEnumerateVisitor exposes a Visit interface called for every detected device +// by HIDDeviceManager::Enumerate. +class HIDEnumerateVisitor +{ +public: + + // Should return true if we are interested in supporting + // this HID VendorId and ProductId pair. + virtual bool MatchVendorProduct(UInt16 vendorId, UInt16 productId) + { OVR_UNUSED2(vendorId, productId); return true; } + + // Override to get notified about available device. Will only be called for + // devices that matched MatchVendorProduct. + virtual void Visit(HIDDevice&, const HIDDeviceDesc&) { } +}; + + +//------------------------------------------------------------------------------------- +// ***** HIDDeviceManager + +// Internal manager for enumerating and opening HID devices. +// If an OVR::DeviceManager is created then an OVR::HIDDeviceManager will automatically be created and can be accessed from the +// DeviceManager by calling 'GetHIDDeviceManager()'. When using HIDDeviceManager in standalone mode, the client must call +// 'Create' below. +class HIDDeviceManager : public RefCountBase<HIDDeviceManager> +{ +public: + + // Creates a new HIDDeviceManager. Only one instance of HIDDeviceManager should be created at a time. + static HIDDeviceManager* Create(Ptr<OVR::DeviceManager>& deviceManager); + + // Enumerate HID devices using a HIDEnumerateVisitor derived visitor class. + virtual bool Enumerate(HIDEnumerateVisitor* enumVisitor) = 0; + + // Open a HID device with the specified path. + virtual HIDDevice* Open(const String& path) = 0; + +protected: + HIDDeviceManager() + { } +}; + +//------------------------------------------------------------------------------------- +// ***** HIDDevice + +// HID device object. This is designed to be operated in synchronous +// and asynchronous modes. With no handler set, input messages will be +// stored and can be retrieved by calling 'Read' or 'ReadBlocking'. +class HIDDevice : public RefCountBase<HIDDevice>, public HIDDeviceBase +{ +public: + + HIDDevice() + : Handler(NULL) + { + } + + virtual ~HIDDevice() {} + + virtual bool SetFeatureReport(UByte* data, UInt32 length) = 0; + virtual bool GetFeatureReport(UByte* data, UInt32 length) = 0; + +// Not yet implemented. +/* + virtual bool Write(UByte* data, UInt32 length) = 0; + + virtual bool Read(UByte* pData, UInt32 length, UInt32 timeoutMilliS) = 0; + virtual bool ReadBlocking(UByte* pData, UInt32 length) = 0; +*/ + + class HIDHandler + { + public: + virtual void OnInputReport(UByte* pData, UInt32 length) + { OVR_UNUSED2(pData, length); } + + virtual double OnTicks(double tickSeconds) + { OVR_UNUSED1(tickSeconds); return 1000.0 ; } + + enum HIDDeviceMessageType + { + HIDDeviceMessage_DeviceAdded = 0, + HIDDeviceMessage_DeviceRemoved = 1 + }; + + virtual void OnDeviceMessage(HIDDeviceMessageType messageType) + { OVR_UNUSED1(messageType); } + }; + + void SetHandler(HIDHandler* handler) + { Handler = handler; } + +protected: + HIDHandler* Handler; +}; + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_HIDDeviceBase.h b/LibOVR/Src/OVR_HIDDeviceBase.h new file mode 100644 index 0000000..7dfd6b4 --- /dev/null +++ b/LibOVR/Src/OVR_HIDDeviceBase.h @@ -0,0 +1,51 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_HIDDeviceBase.h +Content : Definition of HID device interface. +Created : March 11, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_HIDDeviceBase_h +#define OVR_HIDDeviceBase_h + +#include "Kernel/OVR_Types.h" + +namespace OVR { + +//------------------------------------------------------------------------------------- +// ***** HIDDeviceBase + +// Base interface for HID devices. +class HIDDeviceBase +{ +public: + + virtual ~HIDDeviceBase() { } + + virtual bool SetFeatureReport(UByte* data, UInt32 length) = 0; + virtual bool GetFeatureReport(UByte* data, UInt32 length) = 0; +}; + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_HIDDeviceImpl.h b/LibOVR/Src/OVR_HIDDeviceImpl.h new file mode 100644 index 0000000..1399da6 --- /dev/null +++ b/LibOVR/Src/OVR_HIDDeviceImpl.h @@ -0,0 +1,201 @@ +/************************************************************************************ + +Filename : OVR_HIDDeviceImpl.h +Content : Implementation of HIDDevice. +Created : March 7, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_HIDDeviceImpl_h +#define OVR_HIDDeviceImpl_h + +//#include "OVR_Device.h" +#include "OVR_DeviceImpl.h" + +namespace OVR { + +//------------------------------------------------------------------------------------- +class HIDDeviceCreateDesc : public DeviceCreateDesc +{ +public: + HIDDeviceCreateDesc(DeviceFactory* factory, DeviceType type, const HIDDeviceDesc& hidDesc) + : DeviceCreateDesc(factory, type), HIDDesc(hidDesc) { } + HIDDeviceCreateDesc(const HIDDeviceCreateDesc& other) + : DeviceCreateDesc(other.pFactory, other.Type), HIDDesc(other.HIDDesc) { } + + virtual bool MatchDevice(const String& path) + { + // should it be case insensitive? + return HIDDesc.Path.CompareNoCase(path) == 0; + } + + HIDDeviceDesc HIDDesc; +}; + +//------------------------------------------------------------------------------------- +template<class B> +class HIDDeviceImpl : public DeviceImpl<B>, public HIDDevice::HIDHandler +{ +public: + HIDDeviceImpl(HIDDeviceCreateDesc* createDesc, DeviceBase* parent) + : DeviceImpl<B>(createDesc, parent) + { + } + + // HIDDevice::Handler interface. + virtual void OnDeviceMessage(HIDDeviceMessageType messageType) + { + MessageType handlerMessageType; + switch (messageType) { + case HIDDeviceMessage_DeviceAdded: + handlerMessageType = Message_DeviceAdded; + DeviceImpl<B>::ConnectedFlag = true; + break; + + case HIDDeviceMessage_DeviceRemoved: + handlerMessageType = Message_DeviceRemoved; + DeviceImpl<B>::ConnectedFlag = false; + break; + + default: OVR_ASSERT(0); return; + } + + // Do device notification. + MessageDeviceStatus status(handlerMessageType, this, OVR::DeviceHandle(this->pCreateDesc)); + this->HandlerRef.Call(status); + + // Do device manager notification. + DeviceManagerImpl* manager = this->GetManagerImpl(); + switch (handlerMessageType) { + case Message_DeviceAdded: + manager->CallOnDeviceAdded(this->pCreateDesc); + break; + + case Message_DeviceRemoved: + manager->CallOnDeviceRemoved(this->pCreateDesc); + break; + + default:; + } + } + + virtual bool Initialize(DeviceBase* parent) + { + // Open HID device. + HIDDeviceDesc& hidDesc = *getHIDDesc(); + HIDDeviceManager* pManager = GetHIDDeviceManager(); + + + HIDDevice* device = pManager->Open(hidDesc.Path); + if (!device) + { + return false; + } + + InternalDevice = *device; + InternalDevice->SetHandler(this); + + // AddRef() to parent, forcing chain to stay alive. + DeviceImpl<B>::pParent = parent; + + return true; + } + + virtual void Shutdown() + { + InternalDevice->SetHandler(NULL); + + DeviceImpl<B>::pParent.Clear(); + } + + DeviceManager* GetDeviceManager() + { + return DeviceImpl<B>::pCreateDesc->GetManagerImpl(); + } + + HIDDeviceManager* GetHIDDeviceManager() + { + return DeviceImpl<B>::pCreateDesc->GetManagerImpl()->GetHIDDeviceManager(); + } + + bool SetFeatureReport(UByte* data, UInt32 length) + { + // Push call with wait. + bool result = false; + + ThreadCommandQueue* pQueue = this->GetManagerImpl()->GetThreadQueue(); + if (!pQueue->PushCallAndWaitResult(this, &HIDDeviceImpl::setFeatureReport, &result, data, length)) + return false; + + return result; + } + + bool setFeatureReport(UByte* data, UInt32 length) + { + return InternalDevice->SetFeatureReport(data, length); + } + + bool GetFeatureReport(UByte* data, UInt32 length) + { + bool result = false; + + ThreadCommandQueue* pQueue = this->GetManagerImpl()->GetThreadQueue(); + if (!pQueue->PushCallAndWaitResult(this, &HIDDeviceImpl::getFeatureReport, &result, data, length)) + return false; + + return result; + } + + bool getFeatureReport(UByte* data, UInt32 length) + { + return InternalDevice->GetFeatureReport(data, length); + } + + UByte GetDeviceInterfaceVersion() + { + UInt16 versionNumber = getHIDDesc()->VersionNumber; + + // Our interface and hardware versions are represented as two BCD digits each. + // Interface version is in the last two digits. + UByte interfaceVersion = (UByte) ((versionNumber & 0x000F) >> 0) * 1 + + ((versionNumber & 0x00F0) >> 4) * 10; + return interfaceVersion; + } + +protected: + HIDDevice* GetInternalDevice() const + { + return InternalDevice; + } + + HIDDeviceDesc* getHIDDesc() const + { return &getCreateDesc()->HIDDesc; } + + HIDDeviceCreateDesc* getCreateDesc() const + { return (HIDDeviceCreateDesc*) &(*DeviceImpl<B>::pCreateDesc); } + +private: + Ptr<HIDDevice> InternalDevice; +}; + +} // namespace OVR + +#endif diff --git a/LibOVR/Src/OVR_JSON.cpp b/LibOVR/Src/OVR_JSON.cpp new file mode 100644 index 0000000..262a0d9 --- /dev/null +++ b/LibOVR/Src/OVR_JSON.cpp @@ -0,0 +1,1185 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_JSON.h +Content : JSON format reader and writer +Created : April 9, 2013 +Author : Brant Lewis +Notes : + The code is a derivative of the cJSON library written by Dave Gamble and subject + to the following permissive copyright. + + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +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 <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <float.h> +#include <limits.h> +#include <ctype.h> +#include "OVR_JSON.h" +#include "Kernel/OVR_SysFile.h" +#include "Kernel/OVR_Log.h" + +namespace OVR { + + +//----------------------------------------------------------------------------- +// Create a new copy of a string +static char* JSON_strdup(const char* str) +{ + UPInt len = OVR_strlen(str) + 1; + char* copy = (char*)OVR_ALLOC(len); + if (!copy) + return 0; + memcpy(copy, str, len); + return copy; +} + + +//----------------------------------------------------------------------------- +// Render the number from the given item into a string. +static char* PrintNumber(double d) +{ + char *str; + //double d=item->valuedouble; + int valueint = (int)d; + if (fabs(((double)valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) + { + str=(char*)OVR_ALLOC(21); // 2^64+1 can be represented in 21 chars. + if (str) + OVR_sprintf(str, 21, "%d", valueint); + } + else + { + str=(char*)OVR_ALLOC(64); // This is a nice tradeoff. + if (str) + { + if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60) + OVR_sprintf(str, 64, "%.0f", d); + else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) + OVR_sprintf(str, 64, "%e", d); + else + OVR_sprintf(str, 64, "%f", d); + } + } + return str; +} + +// Parse the input text into an un-escaped cstring, and populate item. +static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +// Helper to assign error sting and return 0. +const char* AssignError(const char** perror, const char *errorMessage) +{ + if (perror) + *perror = errorMessage; + return 0; +} + +//----------------------------------------------------------------------------- +// ***** JSON Node class + +JSON::JSON(JSONItemType itemType) + : Type(itemType), dValue(0.0) +{ +} + +JSON::~JSON() +{ + JSON* child = Children.GetFirst(); + while (!Children.IsNull(child)) + { + child->RemoveNode(); + child->Release(); + child = Children.GetFirst(); + } +} + +//----------------------------------------------------------------------------- +// Parse the input text to generate a number, and populate the result into item +// Returns the text position after the parsed number +const char* JSON::parseNumber(const char *num) +{ + const char* num_start = num; + double n=0, sign=1, scale=0; + int subscale = 0, + signsubscale = 1; + + // Could use sscanf for this? + if (*num=='-') + sign=-1,num++; // Has sign? + if (*num=='0') + num++; // is zero + + if (*num>='1' && *num<='9') + { + do + { + n=(n*10.0)+(*num++ -'0'); + } + while (*num>='0' && *num<='9'); // Number? + } + + if (*num=='.' && num[1]>='0' && num[1]<='9') + { + num++; + do + { + n=(n*10.0)+(*num++ -'0'); + scale--; + } + while (*num>='0' && *num<='9'); // Fractional part? + } + + if (*num=='e' || *num=='E') // Exponent? + { + num++; + if (*num=='+') + num++; + else if (*num=='-') + { + signsubscale=-1; + num++; // With sign? + } + + while (*num>='0' && *num<='9') + subscale=(subscale*10)+(*num++ - '0'); // Number? + } + + // Number = +/- number.fraction * 10^+/- exponent + n = sign*n*pow(10.0,(scale+subscale*signsubscale)); + + // Assign parsed value. + Type = JSON_Number; + dValue = n; + Value.AssignString(num_start, num - num_start); + + return num; +} + +// Parses a hex string up to the specified number of digits. +// Returns the first character after the string. +const char* ParseHex(unsigned* val, unsigned digits, const char* str) +{ + *val = 0; + + for(unsigned digitCount = 0; digitCount < digits; digitCount++, str++) + { + unsigned v = *str; + + if ((v >= '0') && (v <= '9')) + v -= '0'; + else if ((v >= 'a') && (v <= 'f')) + v = 10 + v - 'a'; + else if ((v >= 'A') && (v <= 'F')) + v = 10 + v - 'A'; + else + break; + + *val = *val * 16 + v; + } + + return str; +} + +//----------------------------------------------------------------------------- +// Parses the input text into a string item and returns the text position after +// the parsed string +const char* JSON::parseString(const char* str, const char** perror) +{ + const char* ptr = str+1; + const char* p; + char* ptr2; + char* out; + int len=0; + unsigned uc, uc2; + + if (*str!='\"') + { + return AssignError(perror, "Syntax Error: Missing quote"); + } + + while (*ptr!='\"' && *ptr && ++len) + { + if (*ptr++ == '\\') ptr++; // Skip escaped quotes. + } + + // This is how long we need for the string, roughly. + out=(char*)OVR_ALLOC(len+1); + if (!out) + return 0; + + ptr = str+1; + ptr2= out; + + while (*ptr!='\"' && *ptr) + { + if (*ptr!='\\') + { + *ptr2++ = *ptr++; + } + else + { + ptr++; + switch (*ptr) + { + case 'b': *ptr2++ = '\b'; break; + case 'f': *ptr2++ = '\f'; break; + case 'n': *ptr2++ = '\n'; break; + case 'r': *ptr2++ = '\r'; break; + case 't': *ptr2++ = '\t'; break; + + // Transcode utf16 to utf8. + case 'u': + + // Get the unicode char. + p = ParseHex(&uc, 4, ptr + 1); + if (ptr != p) + ptr = p - 1; + + if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) + break; // Check for invalid. + + // UTF16 surrogate pairs. + if (uc>=0xD800 && uc<=0xDBFF) + { + if (ptr[1]!='\\' || ptr[2]!='u') + break; // Missing second-half of surrogate. + + p= ParseHex(&uc2, 4, ptr + 3); + if (ptr != p) + ptr = p - 1; + + if (uc2<0xDC00 || uc2>0xDFFF) + break; // Invalid second-half of surrogate. + + uc = 0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); + } + + len=4; + + if (uc<0x80) + len=1; + else if (uc<0x800) + len=2; + else if (uc<0x10000) + len=3; + + ptr2+=len; + + switch (len) + { + case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 = (char)(uc | firstByteMark[len]); + } + ptr2+=len; + break; + + default: + *ptr2++ = *ptr; + break; + } + ptr++; + } + } + + *ptr2 = 0; + if (*ptr=='\"') + ptr++; + + // Make a copy of the string + Value=out; + OVR_FREE(out); + Type=JSON_String; + + return ptr; +} + +//----------------------------------------------------------------------------- +// Render the string provided to an escaped version that can be printed. +char* PrintString(const char* str) +{ + const char *ptr; + char *ptr2,*out; + int len=0; + unsigned char token; + + if (!str) + return JSON_strdup(""); + ptr=str; + + token=*ptr; + while (token && ++len)\ + { + if (strchr("\"\\\b\f\n\r\t",token)) + len++; + else if (token<32) + len+=5; + ptr++; + token=*ptr; + } + + int buff_size = len+3; + out=(char*)OVR_ALLOC(buff_size); + if (!out) + return 0; + + ptr2 = out; + ptr = str; + *ptr2++ = '\"'; + + while (*ptr) + { + if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') + *ptr2++=*ptr++; + else + { + *ptr2++='\\'; + switch (token=*ptr++) + { + case '\\': *ptr2++='\\'; break; + case '\"': *ptr2++='\"'; break; + case '\b': *ptr2++='b'; break; + case '\f': *ptr2++='f'; break; + case '\n': *ptr2++='n'; break; + case '\r': *ptr2++='r'; break; + case '\t': *ptr2++='t'; break; + default: + OVR_sprintf(ptr2, buff_size - (ptr2-out), "u%04x",token); + ptr2+=5; + break; // Escape and print. + } + } + } + *ptr2++='\"'; + *ptr2++=0; + return out; +} + +//----------------------------------------------------------------------------- +// Utility to jump whitespace and cr/lf +static const char* skip(const char* in) +{ + while (in && *in && (unsigned char)*in<=' ') + in++; + return in; +} + +//----------------------------------------------------------------------------- +// Parses the supplied buffer of JSON text and returns a JSON object tree +// The returned object must be Released after use +JSON* JSON::Parse(const char* buff, const char** perror) +{ + const char* end = 0; + JSON* json = new JSON(); + + if (!json) + { + AssignError(perror, "Error: Failed to allocate memory"); + return 0; + } + + end = json->parseValue(skip(buff), perror); + if (!end) + { + json->Release(); + return NULL; + } // parse failure. ep is set. + + return json; +} + +//----------------------------------------------------------------------------- +// This version works for buffers that are not null terminated strings. +JSON* JSON::ParseBuffer(const char *buff, int len, const char** perror) +{ + // Our JSON parser does not support length-based parsing, + // so ensure it is null-terminated. + char *termStr = new char[len + 1]; + memcpy(termStr, buff, len); + termStr[len] = '\0'; + + JSON *objJson = Parse(termStr, perror); + + delete[]termStr; + + return objJson; +} + +//----------------------------------------------------------------------------- +// Parser core - when encountering text, process appropriately. +const char* JSON::parseValue(const char* buff, const char** perror) +{ + if (perror) + *perror = 0; + + if (!buff) + return NULL; // Fail on null. + + if (!strncmp(buff,"null",4)) + { + Type = JSON_Null; + return buff+4; + } + if (!strncmp(buff,"false",5)) + { + Type = JSON_Bool; + Value = "false"; + dValue = 0; + return buff+5; + } + if (!strncmp(buff,"true",4)) + { + Type = JSON_Bool; + Value = "true"; + dValue = 1; + return buff+4; + } + if (*buff=='\"') + { + return parseString(buff, perror); + } + if (*buff=='-' || (*buff>='0' && *buff<='9')) + { + return parseNumber(buff); + } + if (*buff=='[') + { + return parseArray(buff, perror); + } + if (*buff=='{') + { + return parseObject(buff, perror); + } + + return AssignError(perror, "Syntax Error: Invalid syntax"); +} + + +//----------------------------------------------------------------------------- +// Render a value to text. +char* JSON::PrintValue(int depth, bool fmt) +{ + char *out=0; + + switch (Type) + { + case JSON_Null: out = JSON_strdup("null"); break; + case JSON_Bool: + if (dValue == 0) + out = JSON_strdup("false"); + else + out = JSON_strdup("true"); + break; + case JSON_Number: out = PrintNumber(dValue); break; + case JSON_String: out = PrintString(Value); break; + case JSON_Array: out = PrintArray(depth, fmt); break; + case JSON_Object: out = PrintObject(depth, fmt); break; + case JSON_None: OVR_ASSERT_LOG(false, ("Bad JSON type.")); break; + } + return out; +} + +//----------------------------------------------------------------------------- +// Build an array object from input text and returns the text position after +// the parsed array +const char* JSON::parseArray(const char* buff, const char** perror) +{ + JSON *child; + if (*buff!='[') + { + return AssignError(perror, "Syntax Error: Missing opening bracket"); + } + + Type=JSON_Array; + buff=skip(buff+1); + + if (*buff==']') + return buff+1; // empty array. + + child = new JSON(); + if (!child) + return 0; // memory fail + Children.PushBack(child); + + buff=skip(child->parseValue(skip(buff), perror)); // skip any spacing, get the buff. + if (!buff) + return 0; + + while (*buff==',') + { + JSON *new_item = new JSON(); + if (!new_item) + return AssignError(perror, "Error: Failed to allocate memory"); + + Children.PushBack(new_item); + + buff=skip(new_item->parseValue(skip(buff+1), perror)); + if (!buff) + return AssignError(perror, "Error: Failed to allocate memory"); + } + + if (*buff==']') + return buff+1; // end of array + + return AssignError(perror, "Syntax Error: Missing ending bracket"); +} + +//----------------------------------------------------------------------------- +// Render an array to text. The returned text must be freed +char* JSON::PrintArray(int depth, bool fmt) +{ + char **entries; + char * out = 0,*ptr,*ret; + SPInt len = 5; + + bool fail = false; + + // How many entries in the array? + int numentries = GetItemCount(); + if (!numentries) + { + out=(char*)OVR_ALLOC(3); + if (out) + OVR_strcpy(out, 3, "[]"); + return out; + } + // Allocate an array to hold the values for each + entries=(char**)OVR_ALLOC(numentries*sizeof(char*)); + if (!entries) + return 0; + memset(entries,0,numentries*sizeof(char*)); + + //// Retrieve all the results: + JSON* child = Children.GetFirst(); + for (int i=0; i<numentries; i++) + { + //JSON* child = Children[i]; + ret=child->PrintValue(depth+1, fmt); + entries[i]=ret; + if (ret) + len+=OVR_strlen(ret)+2+(fmt?1:0); + else + { + fail = true; + break; + } + child = Children.GetNext(child); + } + + // If we didn't fail, try to malloc the output string + if (!fail) + out=(char*)OVR_ALLOC(len); + // If that fails, we fail. + if (!out) + fail = true; + + // Handle failure. + if (fail) + { + for (int i=0; i<numentries; i++) + { + if (entries[i]) + OVR_FREE(entries[i]); + } + OVR_FREE(entries); + return 0; + } + + // Compose the output array. + *out='['; + ptr=out+1; + *ptr=0; + for (int i=0; i<numentries; i++) + { + OVR_strcpy(ptr, len - (ptr-out), entries[i]); + ptr+=OVR_strlen(entries[i]); + if (i!=numentries-1) + { + *ptr++=','; + if (fmt) + *ptr++=' '; + *ptr=0; + } + OVR_FREE(entries[i]); + } + OVR_FREE(entries); + *ptr++=']'; + *ptr++=0; + return out; +} + +//----------------------------------------------------------------------------- +// Build an object from the supplied text and returns the text position after +// the parsed object +const char* JSON::parseObject(const char* buff, const char** perror) +{ + if (*buff!='{') + { + return AssignError(perror, "Syntax Error: Missing opening brace"); + } + + Type=JSON_Object; + buff=skip(buff+1); + if (*buff=='}') + return buff+1; // empty array. + + JSON* child = new JSON(); + Children.PushBack(child); + + buff=skip(child->parseString(skip(buff), perror)); + if (!buff) + return 0; + child->Name = child->Value; + child->Value.Clear(); + + if (*buff!=':') + { + return AssignError(perror, "Syntax Error: Missing colon"); + } + + buff=skip(child->parseValue(skip(buff+1), perror)); // skip any spacing, get the value. + if (!buff) + return 0; + + while (*buff==',') + { + child = new JSON(); + if (!child) + return 0; // memory fail + + Children.PushBack(child); + + buff=skip(child->parseString(skip(buff+1), perror)); + if (!buff) + return 0; + + child->Name=child->Value; + child->Value.Clear(); + + if (*buff!=':') + { + return AssignError(perror, "Syntax Error: Missing colon"); + } // fail! + + // Skip any spacing, get the value. + buff=skip(child->parseValue(skip(buff+1), perror)); + if (!buff) + return 0; + } + + if (*buff=='}') + return buff+1; // end of array + + return AssignError(perror, "Syntax Error: Missing closing brace"); +} + +//----------------------------------------------------------------------------- +// Render an object to text. The returned string must be freed +char* JSON::PrintObject(int depth, bool fmt) +{ + char** entries = 0, **names = 0; + char* out = 0; + char* ptr, *ret, *str; + SPInt len = 7, i = 0, j; + bool fail = false; + + // Count the number of entries. + int numentries = GetItemCount(); + + // Explicitly handle empty object case + if (numentries == 0) + { + out=(char*)OVR_ALLOC(fmt?depth+3:3); + if (!out) + return 0; + ptr=out; + *ptr++='{'; + + if (fmt) + { + *ptr++='\n'; + for (i=0;i<depth-1;i++) + *ptr++='\t'; + } + *ptr++='}'; + *ptr++=0; + return out; + } + // Allocate space for the names and the objects + entries=(char**)OVR_ALLOC(numentries*sizeof(char*)); + if (!entries) + return 0; + names=(char**)OVR_ALLOC(numentries*sizeof(char*)); + + if (!names) + { + OVR_FREE(entries); + return 0; + } + memset(entries,0,sizeof(char*)*numentries); + memset(names,0,sizeof(char*)*numentries); + + // Collect all the results into our arrays: + depth++; + if (fmt) + len+=depth; + + JSON* child = Children.GetFirst(); + while (!Children.IsNull(child)) + { + names[i] = str = PrintString(child->Name); + entries[i++] = ret = child->PrintValue(depth, fmt); + + if (str && ret) + { + len += OVR_strlen(ret)+OVR_strlen(str)+2+(fmt?2+depth:0); + } + else + { + fail = true; + break; + } + + child = Children.GetNext(child); + } + + // Try to allocate the output string + if (!fail) + out=(char*)OVR_ALLOC(len); + if (!out) + fail=true; + + // Handle failure + if (fail) + { + for (i=0;i<numentries;i++) + { + if (names[i]) + OVR_FREE(names[i]); + + if (entries[i]) + OVR_FREE(entries[i]);} + + OVR_FREE(names); + OVR_FREE(entries); + return 0; + } + + // Compose the output: + *out = '{'; + ptr = out+1; + if (fmt) + *ptr++='\n'; + *ptr = 0; + + for (i=0; i<numentries; i++) + { + if (fmt) + { + for (j=0; j<depth; j++) + *ptr++ = '\t'; + } + OVR_strcpy(ptr, len - (ptr-out), names[i]); + ptr += OVR_strlen(names[i]); + *ptr++ =':'; + + if (fmt) + *ptr++='\t'; + + OVR_strcpy(ptr, len - (ptr-out), entries[i]); + ptr+=OVR_strlen(entries[i]); + + if (i!=numentries-1) + *ptr++ = ','; + + if (fmt) + *ptr++ = '\n'; + *ptr = 0; + + OVR_FREE(names[i]); + OVR_FREE(entries[i]); + } + + OVR_FREE(names); + OVR_FREE(entries); + + if (fmt) + { + for (i=0;i<depth-1;i++) + *ptr++='\t'; + } + *ptr++='}'; + *ptr++=0; + + return out; +} + + + +// Returns the number of child items in the object +// Counts the number of items in the object. +unsigned JSON::GetItemCount() const +{ + unsigned count = 0; + for(const JSON* p = Children.GetFirst(); !Children.IsNull(p); p = p->pNext) + count++; + return count; +} + +JSON* JSON::GetItemByIndex(unsigned index) +{ + unsigned i = 0; + JSON* child = 0; + + if (!Children.IsEmpty()) + { + child = Children.GetFirst(); + + while (i < index) + { + if (Children.IsNull(child->pNext)) + { + child = 0; + break; + } + child = child->pNext; + i++; + } + } + + return child; +} + +// Returns the child item with the given name or NULL if not found +JSON* JSON::GetItemByName(const char* name) +{ + JSON* child = 0; + + if (!Children.IsEmpty()) + { + child = Children.GetFirst(); + + while (OVR_strcmp(child->Name, name) != 0) + { + if (Children.IsNull(child->pNext)) + { + child = 0; + break; + } + child = child->pNext; + } + } + + return child; +} + +//----------------------------------------------------------------------------- +// Adds a new item to the end of the child list +void JSON::AddItem(const char *string, JSON *item) +{ + if (!item) + return; + + item->Name = string; + Children.PushBack(item); +} + +/* + +// Removes and frees the items at the given index +void JSON::DeleteItem(unsigned int index) +{ + unsigned int num_items = 0; + JSON* child = Children.GetFirst(); + while (!Children.IsNull(child) && num_items < index) + { + num_items++; + child = Children.GetNext(child); + } + + if (!Children.IsNull(child)) + + child->RemoveNode(); + child->Release(); + } +} + +// Replaces and frees the item at the give index with the new item +void JSON::ReplaceItem(unsigned int index, JSON* new_item) +{ + unsigned int num_items = 0; + JSON* child = Children.GetFirst(); + while (!Children.IsNull(child) && num_items < index) + { + num_items++; + child = Children.GetNext(child); + } + + if (!Children.IsNull(child)) + { + child->ReplaceNodeWith(new_item); + child->Release(); + } +} +*/ + +// Removes and frees the last child item +void JSON::RemoveLast() +{ + JSON* child = Children.GetLast(); + if (!Children.IsNull(child)) + { + child->RemoveNode(); + child->Release(); + } +} + +// Helper function to simplify creation of a typed object +JSON* JSON::createHelper(JSONItemType itemType, double dval, const char* strVal) +{ + JSON *item = new JSON(itemType); + if (item) + { + item->dValue = dval; + if (strVal) + item->Value = strVal; + } + return item; +} + +//----------------------------------------------------------------------------- +// Get elements by name +double JSON::GetNumberByName(const char *name, double defValue) +{ + JSON* item = GetItemByName(name); + if (!item || item->Type != JSON_Number) { + return defValue; + } + else { + return item->dValue; + } +} + +int JSON::GetIntByName(const char *name, int defValue) +{ + JSON* item = GetItemByName(name); + if (!item || item->Type != JSON_Number) { + return defValue; + } + else { + return (int)item->dValue; + } +} + +bool JSON::GetBoolByName(const char *name, bool defValue) +{ + JSON* item = GetItemByName(name); + if (!item || item->Type != JSON_Bool) { + return defValue; + } + else { + return (int)item->dValue != 0; + } +} + +String JSON::GetStringByName(const char *name, const String &defValue) +{ + JSON* item = GetItemByName(name); + if (!item || item->Type != JSON_String) { + return defValue; + } + else { + return item->Value; + } +} + +//----------------------------------------------------------------------------- +// Adds an element to an array object type +void JSON::AddArrayElement(JSON *item) +{ + if (!item) + return; + + Children.PushBack(item); +} + +// Inserts an element into a valid array position +void JSON::InsertArrayElement(int index, JSON *item) +{ + if (!item) + return; + + if (index == 0) + { + Children.PushFront(item); + return; + } + + JSON* iter = Children.GetFirst(); + int i=0; + while (iter && i<index) + { + iter = Children.GetNext(iter); + i++; + } + + if (iter) + iter->InsertNodeBefore(item); + else + Children.PushBack(item); +} + +// Returns the size of an array +int JSON::GetArraySize() +{ + if (Type == JSON_Array) + return GetItemCount(); + else + return 0; +} + +// Returns the number value an the give array index +double JSON::GetArrayNumber(int index) +{ + if (Type == JSON_Array) + { + JSON* number = GetItemByIndex(index); + return number ? number->dValue : 0.0; + } + else + { + return 0; + } +} + +// Returns the string value at the given array index +const char* JSON::GetArrayString(int index) +{ + if (Type == JSON_Array) + { + JSON* number = GetItemByIndex(index); + return number ? number->Value : 0; + } + else + { + return 0; + } +} + +JSON* JSON::Copy() +{ + JSON* copy = new JSON(Type); + copy->Name = Name; + copy->Value = Value; + copy->dValue = dValue; + + JSON* child = Children.GetFirst(); + while (!Children.IsNull(child)) + { + copy->Children.PushBack(child->Copy()); + child = Children.GetNext(child); + } + + return copy; +} + +//----------------------------------------------------------------------------- +// Loads and parses the given JSON file pathname and returns a JSON object tree. +// The returned object must be Released after use. +JSON* JSON::Load(const char* path, const char** perror) +{ + SysFile f; + if (!f.Open(path, File::Open_Read, File::Mode_Read)) + { + AssignError(perror, "Failed to open file"); + return NULL; + } + + int len = f.GetLength(); + UByte* buff = (UByte*)OVR_ALLOC(len + 1); + int bytes = f.Read(buff, len); + f.Close(); + + if (bytes == 0 || bytes != len) + { + OVR_FREE(buff); + return NULL; + } + + // Ensure the result is null-terminated since Parse() expects null-terminated input. + buff[len] = '\0'; + + JSON* json = JSON::Parse((char*)buff, perror); + OVR_FREE(buff); + return json; +} + +//----------------------------------------------------------------------------- +// Serializes the JSON object and writes to the give file path +bool JSON::Save(const char* path) +{ + SysFile f; + if (!f.Open(path, File::Open_Write | File::Open_Create | File::Open_Truncate, File::Mode_Write)) + return false; + + char* text = PrintValue(0, true); + if (text) + { + SPInt len = OVR_strlen(text); + OVR_ASSERT(len <= (SPInt)(int)len); + + int bytes = f.Write((UByte*)text, (int)len); + f.Close(); + OVR_FREE(text); + return (bytes == len); + } + else + { + return false; + } +} + +} diff --git a/LibOVR/Src/OVR_JSON.h b/LibOVR/Src/OVR_JSON.h new file mode 100644 index 0000000..a2a603c --- /dev/null +++ b/LibOVR/Src/OVR_JSON.h @@ -0,0 +1,164 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_JSON.h +Content : JSON format reader and writer +Created : April 9, 2013 +Author : Brant Lewis +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_JSON_H +#define OVR_JSON_H + +#include "Kernel/OVR_RefCount.h" +#include "Kernel/OVR_String.h" +#include "Kernel/OVR_List.h" + +namespace OVR { + +// JSONItemType describes the type of JSON item, specifying the type of +// data that can be obtained from it. +enum JSONItemType +{ + JSON_None = 0, + JSON_Null = 1, + JSON_Bool = 2, + JSON_Number = 3, + JSON_String = 4, + JSON_Array = 5, + JSON_Object = 6 +}; + +//----------------------------------------------------------------------------- +// ***** JSON + +// JSON object represents a JSON node that can be either a root of the JSON tree +// or a child item. Every node has a type that describes what is is. +// New JSON trees are typically loaded JSON::Load or created with JSON::Parse. + +class JSON : public RefCountBase<JSON>, public ListNode<JSON> +{ +protected: + List<JSON> Children; + +public: + JSONItemType Type; // Type of this JSON node. + String Name; // Name part of the {Name, Value} pair in a parent object. + String Value; + double dValue; + +public: + ~JSON(); + + // *** Creation of NEW JSON objects + + static JSON* CreateObject() { return new JSON(JSON_Object);} + static JSON* CreateNull() { return new JSON(JSON_Null); } + static JSON* CreateArray() { return new JSON(JSON_Array); } + static JSON* CreateBool(bool b) { return createHelper(JSON_Bool, b ? 1.0 : 0.0); } + static JSON* CreateNumber(double num) { return createHelper(JSON_Number, num); } + static JSON* CreateString(const char *s) { return createHelper(JSON_String, 0.0, s); } + + // Creates a new JSON object from parsing string. + // Returns null pointer and fills in *perror in case of parse error. + static JSON* Parse(const char* buff, const char** perror = 0); + + // This version works for buffers that are not null terminated strings. + static JSON* ParseBuffer(const char *buff, int len, const char** perror = 0); + + // Loads and parses a JSON object from a file. + // Returns 0 and assigns perror with error message on fail. + static JSON* Load(const char* path, const char** perror = 0); + + // Saves a JSON object to a file. + bool Save(const char* path); + + // *** Object Member Access + + // These provide access to child items of the list. + bool HasItems() const { return Children.IsEmpty(); } + // Returns first/last child item, or null if child list is empty + JSON* GetFirstItem() { return (!Children.IsEmpty()) ? Children.GetFirst() : 0; } + JSON* GetLastItem() { return (!Children.IsEmpty()) ? Children.GetLast() : 0; } + + // Counts the number of items in the object; these methods are inefficient. + unsigned GetItemCount() const; + JSON* GetItemByIndex(unsigned i); + JSON* GetItemByName(const char* name); + + // Accessors by name + double GetNumberByName(const char *name, double defValue = 0.0); + int GetIntByName(const char *name, int defValue = 0); + bool GetBoolByName(const char *name, bool defValue = false); + String GetStringByName(const char *name, const String &defValue = ""); + + // Returns next item in a list of children; 0 if no more items exist. + JSON* GetNextItem(JSON* item) { return Children.IsNull(item->pNext) ? 0 : item->pNext; } + JSON* GetPrevItem(JSON* item) { return Children.IsNull(item->pPrev) ? 0 : item->pPrev; } + + + // Child item access functions + void AddItem(const char *string, JSON* item); + void AddNullItem(const char* name) { AddItem(name, CreateNull()); } + void AddBoolItem(const char* name, bool b) { AddItem(name, CreateBool(b)); } + void AddNumberItem(const char* name, double n) { AddItem(name, CreateNumber(n)); } + void AddStringItem(const char* name, const char* s) { AddItem(name, CreateString(s)); } +// void ReplaceItem(unsigned index, JSON* new_item); +// void DeleteItem(unsigned index); + void RemoveLast(); + + // *** Array Element Access + + // Add new elements to the end of array. + void AddArrayElement(JSON *item); + void InsertArrayElement(int index, JSON* item); + void AddArrayNumber(double n) { AddArrayElement(CreateNumber(n)); } + void AddArrayString(const char* s) { AddArrayElement(CreateString(s)); } + + // Accessed array elements; currently inefficient. + int GetArraySize(); + double GetArrayNumber(int index); + const char* GetArrayString(int index); + + JSON* Copy(); // Create a copy of this object + +protected: + JSON(JSONItemType itemType = JSON_Object); + + static JSON* createHelper(JSONItemType itemType, double dval, const char* strVal = 0); + + // JSON Parsing helper functions. + const char* parseValue(const char *buff, const char** perror); + const char* parseNumber(const char *num); + const char* parseArray(const char* value, const char** perror); + const char* parseObject(const char* value, const char** perror); + const char* parseString(const char* str, const char** perror); + + char* PrintValue(int depth, bool fmt); + char* PrintObject(int depth, bool fmt); + char* PrintArray(int depth, bool fmt); +}; + + +} + +#endif diff --git a/LibOVR/Src/OVR_LatencyTestImpl.cpp b/LibOVR/Src/OVR_LatencyTestImpl.cpp new file mode 100644 index 0000000..015d9e4 --- /dev/null +++ b/LibOVR/Src/OVR_LatencyTestImpl.cpp @@ -0,0 +1,773 @@ +/************************************************************************************ + +Filename : OVR_LatencyTestImpl.cpp +Content : Oculus Latency Tester device implementation. +Created : March 7, 2013 +Authors : Lee Cooper + +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 "OVR_LatencyTestImpl.h" +#include "Kernel/OVR_Alg.h" + +namespace OVR { + +using namespace Alg; + +//------------------------------------------------------------------------------------- +// ***** Oculus Latency Tester specific packet data structures + +enum { + LatencyTester_VendorId = Oculus_VendorId, + LatencyTester_ProductId = 0x0101, +}; + +static void UnpackSamples(const UByte* buffer, UByte* r, UByte* g, UByte* b) +{ + *r = buffer[0]; + *g = buffer[1]; + *b = buffer[2]; +} + +// Messages we handle. +enum LatencyTestMessageType +{ + LatencyTestMessage_None = 0, + LatencyTestMessage_Samples = 1, + LatencyTestMessage_ColorDetected = 2, + LatencyTestMessage_TestStarted = 3, + LatencyTestMessage_Button = 4, + LatencyTestMessage_Unknown = 0x100, + LatencyTestMessage_SizeError = 0x101, +}; + +struct LatencyTestSample +{ + UByte Value[3]; +}; + +struct LatencyTestSamples +{ + UByte SampleCount; + UInt16 Timestamp; + + LatencyTestSample Samples[20]; + + LatencyTestMessageType Decode(const UByte* buffer, int size) + { + if (size < 64) + { + return LatencyTestMessage_SizeError; + } + + SampleCount = buffer[1]; + Timestamp = DecodeUInt16(buffer + 2); + + for (UByte i = 0; i < SampleCount; i++) + { + UnpackSamples(buffer + 4 + (3 * i), &Samples[i].Value[0], &Samples[i].Value[1], &Samples[i].Value[2]); + } + + return LatencyTestMessage_Samples; + } +}; + +struct LatencyTestSamplesMessage +{ + LatencyTestMessageType Type; + LatencyTestSamples Samples; +}; + +bool DecodeLatencyTestSamplesMessage(LatencyTestSamplesMessage* message, UByte* buffer, int size) +{ + memset(message, 0, sizeof(LatencyTestSamplesMessage)); + + if (size < 64) + { + message->Type = LatencyTestMessage_SizeError; + return false; + } + + switch (buffer[0]) + { + case LatencyTestMessage_Samples: + message->Type = message->Samples.Decode(buffer, size); + break; + + default: + message->Type = LatencyTestMessage_Unknown; + break; + } + + return (message->Type < LatencyTestMessage_Unknown) && (message->Type != LatencyTestMessage_None); +} + +struct LatencyTestColorDetected +{ + UInt16 CommandID; + UInt16 Timestamp; + UInt16 Elapsed; + UByte TriggerValue[3]; + UByte TargetValue[3]; + + LatencyTestMessageType Decode(const UByte* buffer, int size) + { + if (size < 13) + return LatencyTestMessage_SizeError; + + CommandID = DecodeUInt16(buffer + 1); + Timestamp = DecodeUInt16(buffer + 3); + Elapsed = DecodeUInt16(buffer + 5); + memcpy(TriggerValue, buffer + 7, 3); + memcpy(TargetValue, buffer + 10, 3); + + return LatencyTestMessage_ColorDetected; + } +}; + +struct LatencyTestColorDetectedMessage +{ + LatencyTestMessageType Type; + LatencyTestColorDetected ColorDetected; +}; + +bool DecodeLatencyTestColorDetectedMessage(LatencyTestColorDetectedMessage* message, UByte* buffer, int size) +{ + memset(message, 0, sizeof(LatencyTestColorDetectedMessage)); + + if (size < 13) + { + message->Type = LatencyTestMessage_SizeError; + return false; + } + + switch (buffer[0]) + { + case LatencyTestMessage_ColorDetected: + message->Type = message->ColorDetected.Decode(buffer, size); + break; + + default: + message->Type = LatencyTestMessage_Unknown; + break; + } + + return (message->Type < LatencyTestMessage_Unknown) && (message->Type != LatencyTestMessage_None); +} + +struct LatencyTestStarted +{ + UInt16 CommandID; + UInt16 Timestamp; + UByte TargetValue[3]; + + LatencyTestMessageType Decode(const UByte* buffer, int size) + { + if (size < 8) + return LatencyTestMessage_SizeError; + + CommandID = DecodeUInt16(buffer + 1); + Timestamp = DecodeUInt16(buffer + 3); + memcpy(TargetValue, buffer + 5, 3); + + return LatencyTestMessage_TestStarted; + } +}; + +struct LatencyTestStartedMessage +{ + LatencyTestMessageType Type; + LatencyTestStarted TestStarted; +}; + +bool DecodeLatencyTestStartedMessage(LatencyTestStartedMessage* message, UByte* buffer, int size) +{ + memset(message, 0, sizeof(LatencyTestStartedMessage)); + + if (size < 8) + { + message->Type = LatencyTestMessage_SizeError; + return false; + } + + switch (buffer[0]) + { + case LatencyTestMessage_TestStarted: + message->Type = message->TestStarted.Decode(buffer, size); + break; + + default: + message->Type = LatencyTestMessage_Unknown; + break; + } + + return (message->Type < LatencyTestMessage_Unknown) && (message->Type != LatencyTestMessage_None); +} + +struct LatencyTestButton +{ + UInt16 CommandID; + UInt16 Timestamp; + + LatencyTestMessageType Decode(const UByte* buffer, int size) + { + if (size < 5) + return LatencyTestMessage_SizeError; + + CommandID = DecodeUInt16(buffer + 1); + Timestamp = DecodeUInt16(buffer + 3); + + return LatencyTestMessage_Button; + } +}; + +struct LatencyTestButtonMessage +{ + LatencyTestMessageType Type; + LatencyTestButton Button; +}; + +bool DecodeLatencyTestButtonMessage(LatencyTestButtonMessage* message, UByte* buffer, int size) +{ + memset(message, 0, sizeof(LatencyTestButtonMessage)); + + if (size < 5) + { + message->Type = LatencyTestMessage_SizeError; + return false; + } + + switch (buffer[0]) + { + case LatencyTestMessage_Button: + message->Type = message->Button.Decode(buffer, size); + break; + + default: + message->Type = LatencyTestMessage_Unknown; + break; + } + + return (message->Type < LatencyTestMessage_Unknown) && (message->Type != LatencyTestMessage_None); +} + +struct LatencyTestConfigurationImpl +{ + enum { PacketSize = 5 }; + UByte Buffer[PacketSize]; + + OVR::LatencyTestConfiguration Configuration; + + LatencyTestConfigurationImpl(const OVR::LatencyTestConfiguration& configuration) + : Configuration(configuration) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 5; + Buffer[1] = UByte(Configuration.SendSamples); + Buffer[2] = Configuration.Threshold.R; + Buffer[3] = Configuration.Threshold.G; + Buffer[4] = Configuration.Threshold.B; + } + + void Unpack() + { + Configuration.SendSamples = Buffer[1] != 0 ? true : false; + Configuration.Threshold.R = Buffer[2]; + Configuration.Threshold.G = Buffer[3]; + Configuration.Threshold.B = Buffer[4]; + } +}; + +struct LatencyTestCalibrateImpl +{ + enum { PacketSize = 4 }; + UByte Buffer[PacketSize]; + + Color CalibrationColor; + + LatencyTestCalibrateImpl(const Color& calibrationColor) + : CalibrationColor(calibrationColor) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 7; + Buffer[1] = CalibrationColor.R; + Buffer[2] = CalibrationColor.G; + Buffer[3] = CalibrationColor.B; + } + + void Unpack() + { + CalibrationColor.R = Buffer[1]; + CalibrationColor.G = Buffer[2]; + CalibrationColor.B = Buffer[3]; + } +}; + +struct LatencyTestStartTestImpl +{ + enum { PacketSize = 6 }; + UByte Buffer[PacketSize]; + + Color TargetColor; + + LatencyTestStartTestImpl(const Color& targetColor) + : TargetColor(targetColor) + { + Pack(); + } + + void Pack() + { + UInt16 commandID = 1; + + Buffer[0] = 8; + EncodeUInt16(Buffer+1, commandID); + Buffer[3] = TargetColor.R; + Buffer[4] = TargetColor.G; + Buffer[5] = TargetColor.B; + } + + void Unpack() + { +// UInt16 commandID = DecodeUInt16(Buffer+1); + TargetColor.R = Buffer[3]; + TargetColor.G = Buffer[4]; + TargetColor.B = Buffer[5]; + } +}; + +struct LatencyTestDisplayImpl +{ + enum { PacketSize = 6 }; + UByte Buffer[PacketSize]; + + OVR::LatencyTestDisplay Display; + + LatencyTestDisplayImpl(const OVR::LatencyTestDisplay& display) + : Display(display) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 9; + Buffer[1] = Display.Mode; + EncodeUInt32(Buffer+2, Display.Value); + } + + void Unpack() + { + Display.Mode = Buffer[1]; + Display.Value = DecodeUInt32(Buffer+2); + } +}; + +//------------------------------------------------------------------------------------- +// ***** LatencyTestDeviceFactory + +LatencyTestDeviceFactory &LatencyTestDeviceFactory::GetInstance() +{ + static LatencyTestDeviceFactory instance; + return instance; +} + +void LatencyTestDeviceFactory::EnumerateDevices(EnumerateVisitor& visitor) +{ + + class LatencyTestEnumerator : public HIDEnumerateVisitor + { + // Assign not supported; suppress MSVC warning. + void operator = (const LatencyTestEnumerator&) { } + + DeviceFactory* pFactory; + EnumerateVisitor& ExternalVisitor; + public: + LatencyTestEnumerator(DeviceFactory* factory, EnumerateVisitor& externalVisitor) + : pFactory(factory), ExternalVisitor(externalVisitor) { } + + virtual bool MatchVendorProduct(UInt16 vendorId, UInt16 productId) + { + return pFactory->MatchVendorProduct(vendorId, productId); + } + + virtual void Visit(HIDDevice& device, const HIDDeviceDesc& desc) + { + OVR_UNUSED(device); + + LatencyTestDeviceCreateDesc createDesc(pFactory, desc); + ExternalVisitor.Visit(createDesc); + } + }; + + LatencyTestEnumerator latencyTestEnumerator(this, visitor); + GetManagerImpl()->GetHIDDeviceManager()->Enumerate(&latencyTestEnumerator); +} + +bool LatencyTestDeviceFactory::MatchVendorProduct(UInt16 vendorId, UInt16 productId) const +{ + return ((vendorId == LatencyTester_VendorId) && (productId == LatencyTester_ProductId)); +} + +bool LatencyTestDeviceFactory::DetectHIDDevice(DeviceManager* pdevMgr, + const HIDDeviceDesc& desc) +{ + if (MatchVendorProduct(desc.VendorId, desc.ProductId)) + { + LatencyTestDeviceCreateDesc createDesc(this, desc); + return pdevMgr->AddDevice_NeedsLock(createDesc).GetPtr() != NULL; + } + return false; +} + +//------------------------------------------------------------------------------------- +// ***** LatencyTestDeviceCreateDesc + +DeviceBase* LatencyTestDeviceCreateDesc::NewDeviceInstance() +{ + return new LatencyTestDeviceImpl(this); +} + +bool LatencyTestDeviceCreateDesc::GetDeviceInfo(DeviceInfo* info) const +{ + if ((info->InfoClassType != Device_LatencyTester) && + (info->InfoClassType != Device_None)) + return false; + + info->Type = Device_LatencyTester; + info->ProductName = HIDDesc.Product; + info->Manufacturer = HIDDesc.Manufacturer; + info->Version = HIDDesc.VersionNumber; + + if (info->InfoClassType == Device_LatencyTester) + { + SensorInfo* sinfo = (SensorInfo*)info; + sinfo->VendorId = HIDDesc.VendorId; + sinfo->ProductId = HIDDesc.ProductId; + sinfo->SerialNumber = HIDDesc.SerialNumber; + } + return true; +} + +//------------------------------------------------------------------------------------- +// ***** LatencyTestDevice + +LatencyTestDeviceImpl::LatencyTestDeviceImpl(LatencyTestDeviceCreateDesc* createDesc) + : OVR::HIDDeviceImpl<OVR::LatencyTestDevice>(createDesc, 0) +{ +} + +LatencyTestDeviceImpl::~LatencyTestDeviceImpl() +{ + // Check that Shutdown() was called. + OVR_ASSERT(!pCreateDesc->pDevice); +} + +// Internal creation APIs. +bool LatencyTestDeviceImpl::Initialize(DeviceBase* parent) +{ + if (HIDDeviceImpl<OVR::LatencyTestDevice>::Initialize(parent)) + { + LogText("OVR::LatencyTestDevice initialized.\n"); + return true; + } + + return false; +} + +void LatencyTestDeviceImpl::Shutdown() +{ + HIDDeviceImpl<OVR::LatencyTestDevice>::Shutdown(); + + LogText("OVR::LatencyTestDevice - Closed '%s'\n", getHIDDesc()->Path.ToCStr()); +} + +void LatencyTestDeviceImpl::OnInputReport(UByte* pData, UInt32 length) +{ + + bool processed = false; + if (!processed) + { + LatencyTestSamplesMessage message; + if (DecodeLatencyTestSamplesMessage(&message, pData, length)) + { + processed = true; + onLatencyTestSamplesMessage(&message); + } + } + + if (!processed) + { + LatencyTestColorDetectedMessage message; + if (DecodeLatencyTestColorDetectedMessage(&message, pData, length)) + { + processed = true; + onLatencyTestColorDetectedMessage(&message); + } + } + + if (!processed) + { + LatencyTestStartedMessage message; + if (DecodeLatencyTestStartedMessage(&message, pData, length)) + { + processed = true; + onLatencyTestStartedMessage(&message); + } + } + + if (!processed) + { + LatencyTestButtonMessage message; + if (DecodeLatencyTestButtonMessage(&message, pData, length)) + { + processed = true; + onLatencyTestButtonMessage(&message); + } + } +} + +bool LatencyTestDeviceImpl::SetConfiguration(const OVR::LatencyTestConfiguration& configuration, bool waitFlag) +{ + bool result = false; + ThreadCommandQueue* queue = GetManagerImpl()->GetThreadQueue(); + + if (GetManagerImpl()->GetThreadId() != OVR::GetCurrentThreadId()) + { + if (!waitFlag) + { + return queue->PushCall(this, &LatencyTestDeviceImpl::setConfiguration, configuration); + } + + if (!queue->PushCallAndWaitResult( this, + &LatencyTestDeviceImpl::setConfiguration, + &result, + configuration)) + { + return false; + } + } + else + return setConfiguration(configuration); + + return result; +} + +bool LatencyTestDeviceImpl::setConfiguration(const OVR::LatencyTestConfiguration& configuration) +{ + LatencyTestConfigurationImpl ltc(configuration); + return GetInternalDevice()->SetFeatureReport(ltc.Buffer, LatencyTestConfigurationImpl::PacketSize); +} + +bool LatencyTestDeviceImpl::GetConfiguration(OVR::LatencyTestConfiguration* configuration) +{ + bool result = false; + + ThreadCommandQueue* pQueue = this->GetManagerImpl()->GetThreadQueue(); + if (!pQueue->PushCallAndWaitResult(this, &LatencyTestDeviceImpl::getConfiguration, &result, configuration)) + return false; + + return result; +} + +bool LatencyTestDeviceImpl::getConfiguration(OVR::LatencyTestConfiguration* configuration) +{ + LatencyTestConfigurationImpl ltc(*configuration); + if (GetInternalDevice()->GetFeatureReport(ltc.Buffer, LatencyTestConfigurationImpl::PacketSize)) + { + ltc.Unpack(); + *configuration = ltc.Configuration; + return true; + } + + return false; +} + +bool LatencyTestDeviceImpl::SetCalibrate(const Color& calibrationColor, bool waitFlag) +{ + bool result = false; + ThreadCommandQueue* queue = GetManagerImpl()->GetThreadQueue(); + + if (!waitFlag) + { + return queue->PushCall(this, &LatencyTestDeviceImpl::setCalibrate, calibrationColor); + } + + if (!queue->PushCallAndWaitResult( this, + &LatencyTestDeviceImpl::setCalibrate, + &result, + calibrationColor)) + { + return false; + } + + return result; +} + +bool LatencyTestDeviceImpl::setCalibrate(const Color& calibrationColor) +{ + LatencyTestCalibrateImpl ltc(calibrationColor); + return GetInternalDevice()->SetFeatureReport(ltc.Buffer, LatencyTestCalibrateImpl::PacketSize); +} + +bool LatencyTestDeviceImpl::SetStartTest(const Color& targetColor, bool waitFlag) +{ + bool result = false; + ThreadCommandQueue* queue = GetManagerImpl()->GetThreadQueue(); + + if (!waitFlag) + { + return queue->PushCall(this, &LatencyTestDeviceImpl::setStartTest, targetColor); + } + + if (!queue->PushCallAndWaitResult( this, + &LatencyTestDeviceImpl::setStartTest, + &result, + targetColor)) + { + return false; + } + + return result; +} + +bool LatencyTestDeviceImpl::setStartTest(const Color& targetColor) +{ + LatencyTestStartTestImpl ltst(targetColor); + return GetInternalDevice()->SetFeatureReport(ltst.Buffer, LatencyTestStartTestImpl::PacketSize); +} + +bool LatencyTestDeviceImpl::SetDisplay(const OVR::LatencyTestDisplay& display, bool waitFlag) +{ + bool result = false; + ThreadCommandQueue * queue = GetManagerImpl()->GetThreadQueue(); + + if (!waitFlag) + { + return queue->PushCall(this, &LatencyTestDeviceImpl::setDisplay, display); + } + + if (!queue->PushCallAndWaitResult( this, + &LatencyTestDeviceImpl::setDisplay, + &result, + display)) + { + return false; + } + + return result; +} + +bool LatencyTestDeviceImpl::setDisplay(const OVR::LatencyTestDisplay& display) +{ + LatencyTestDisplayImpl ltd(display); + return GetInternalDevice()->SetFeatureReport(ltd.Buffer, LatencyTestDisplayImpl::PacketSize); +} + +void LatencyTestDeviceImpl::onLatencyTestSamplesMessage(LatencyTestSamplesMessage* message) +{ + + if (message->Type != LatencyTestMessage_Samples) + return; + + LatencyTestSamples& s = message->Samples; + + // Call OnMessage() within a lock to avoid conflicts with handlers. + Lock::Locker scopeLock(HandlerRef.GetLock()); + + if (HandlerRef.HasHandlers()) + { + MessageLatencyTestSamples samples(this); + for (UByte i = 0; i < s.SampleCount; i++) + { + samples.Samples.PushBack(Color(s.Samples[i].Value[0], s.Samples[i].Value[1], s.Samples[i].Value[2])); + } + + HandlerRef.Call(samples); + } +} + +void LatencyTestDeviceImpl::onLatencyTestColorDetectedMessage(LatencyTestColorDetectedMessage* message) +{ + if (message->Type != LatencyTestMessage_ColorDetected) + return; + + LatencyTestColorDetected& s = message->ColorDetected; + + // Call OnMessage() within a lock to avoid conflicts with handlers. + Lock::Locker scopeLock(HandlerRef.GetLock()); + + if (HandlerRef.HasHandlers()) + { + MessageLatencyTestColorDetected detected(this); + detected.Elapsed = s.Elapsed; + detected.DetectedValue = Color(s.TriggerValue[0], s.TriggerValue[1], s.TriggerValue[2]); + detected.TargetValue = Color(s.TargetValue[0], s.TargetValue[1], s.TargetValue[2]); + + HandlerRef.Call(detected); + } +} + +void LatencyTestDeviceImpl::onLatencyTestStartedMessage(LatencyTestStartedMessage* message) +{ + if (message->Type != LatencyTestMessage_TestStarted) + return; + + LatencyTestStarted& ts = message->TestStarted; + + // Call OnMessage() within a lock to avoid conflicts with handlers. + Lock::Locker scopeLock(HandlerRef.GetLock()); + + if (HandlerRef.HasHandlers()) + { + MessageLatencyTestStarted started(this); + started.TargetValue = Color(ts.TargetValue[0], ts.TargetValue[1], ts.TargetValue[2]); + + HandlerRef.Call(started); + } +} + +void LatencyTestDeviceImpl::onLatencyTestButtonMessage(LatencyTestButtonMessage* message) +{ + if (message->Type != LatencyTestMessage_Button) + return; + +// LatencyTestButton& s = message->Button; + + // Call OnMessage() within a lock to avoid conflicts with handlers. + Lock::Locker scopeLock(HandlerRef.GetLock()); + + if (HandlerRef.HasHandlers()) + { + MessageLatencyTestButton button(this); + + HandlerRef.Call(button); + } +} + +} // namespace OVR diff --git a/LibOVR/Src/OVR_LatencyTestImpl.h b/LibOVR/Src/OVR_LatencyTestImpl.h new file mode 100644 index 0000000..34faec2 --- /dev/null +++ b/LibOVR/Src/OVR_LatencyTestImpl.h @@ -0,0 +1,144 @@ +/************************************************************************************ + +Filename : OVR_LatencyTestImpl.h +Content : Latency Tester specific implementation. +Created : March 7, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_LatencyTestImpl_h +#define OVR_LatencyTestImpl_h + +#include "OVR_HIDDeviceImpl.h" + +namespace OVR { + +struct LatencyTestSamplesMessage; +struct LatencyTestButtonMessage; +struct LatencyTestStartedMessage; +struct LatencyTestColorDetectedMessage; + +//------------------------------------------------------------------------------------- +// LatencyTestDeviceFactory enumerates Oculus Latency Tester devices. +class LatencyTestDeviceFactory : public DeviceFactory +{ +public: + static LatencyTestDeviceFactory &GetInstance(); + + // Enumerates devices, creating and destroying relevant objects in manager. + virtual void EnumerateDevices(EnumerateVisitor& visitor); + + virtual bool MatchVendorProduct(UInt16 vendorId, UInt16 productId) const; + virtual bool DetectHIDDevice(DeviceManager* pdevMgr, const HIDDeviceDesc& desc); + +protected: + DeviceManager* getManager() const { return (DeviceManager*) pManager; } +}; + + +// Describes a single a Oculus Latency Tester device and supports creating its instance. +class LatencyTestDeviceCreateDesc : public HIDDeviceCreateDesc +{ +public: + LatencyTestDeviceCreateDesc(DeviceFactory* factory, const HIDDeviceDesc& hidDesc) + : HIDDeviceCreateDesc(factory, Device_LatencyTester, hidDesc) { } + + virtual DeviceCreateDesc* Clone() const + { + return new LatencyTestDeviceCreateDesc(*this); + } + + virtual DeviceBase* NewDeviceInstance(); + + virtual MatchResult MatchDevice(const DeviceCreateDesc& other, + DeviceCreateDesc**) const + { + if ((other.Type == Device_LatencyTester) && (pFactory == other.pFactory)) + { + const LatencyTestDeviceCreateDesc& s2 = (const LatencyTestDeviceCreateDesc&) other; + if (MatchHIDDevice(s2.HIDDesc)) + return Match_Found; + } + return Match_None; + } + + virtual bool MatchHIDDevice(const HIDDeviceDesc& hidDesc) const + { + // should paths comparison be case insensitive? + return ((HIDDesc.Path.CompareNoCase(hidDesc.Path) == 0) && + (HIDDesc.SerialNumber == hidDesc.SerialNumber)); + } + virtual bool GetDeviceInfo(DeviceInfo* info) const; +}; + + +//------------------------------------------------------------------------------------- +// ***** OVR::LatencyTestDeviceImpl + +// Oculus Latency Tester interface. + +class LatencyTestDeviceImpl : public HIDDeviceImpl<OVR::LatencyTestDevice> +{ +public: + LatencyTestDeviceImpl(LatencyTestDeviceCreateDesc* createDesc); + ~LatencyTestDeviceImpl(); + + // DeviceCommon interface. + virtual bool Initialize(DeviceBase* parent); + virtual void Shutdown(); + + // DeviceManagerThread::Notifier interface. + virtual void OnInputReport(UByte* pData, UInt32 length); + + // LatencyTesterDevice interface + virtual bool SetConfiguration(const OVR::LatencyTestConfiguration& configuration, bool waitFlag = false); + virtual bool GetConfiguration(OVR::LatencyTestConfiguration* configuration); + + virtual bool SetCalibrate(const Color& calibrationColor, bool waitFlag = false); + + virtual bool SetStartTest(const Color& targetColor, bool waitFlag = false); + virtual bool SetDisplay(const LatencyTestDisplay& display, bool waitFlag = false); + +protected: + bool openDevice(const char** errorFormatString); + void closeDevice(); + void closeDeviceOnIOError(); + + bool initializeRead(); + bool processReadResult(); + + bool setConfiguration(const OVR::LatencyTestConfiguration& configuration); + bool getConfiguration(OVR::LatencyTestConfiguration* configuration); + bool setCalibrate(const Color& calibrationColor); + bool setStartTest(const Color& targetColor); + bool setDisplay(const OVR::LatencyTestDisplay& display); + + // Called for decoded messages + void onLatencyTestSamplesMessage(LatencyTestSamplesMessage* message); + void onLatencyTestButtonMessage(LatencyTestButtonMessage* message); + void onLatencyTestStartedMessage(LatencyTestStartedMessage* message); + void onLatencyTestColorDetectedMessage(LatencyTestColorDetectedMessage* message); + +}; + +} // namespace OVR + +#endif // OVR_LatencyTestImpl_h diff --git a/LibOVR/Src/OVR_Linux_DeviceManager.cpp b/LibOVR/Src/OVR_Linux_DeviceManager.cpp new file mode 100644 index 0000000..f1c4278 --- /dev/null +++ b/LibOVR/Src/OVR_Linux_DeviceManager.cpp @@ -0,0 +1,331 @@ +/************************************************************************************ + +Filename : OVR_Linux_DeviceManager.h +Content : Linux implementation of DeviceManager. +Created : +Authors : + +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 "OVR_Linux_DeviceManager.h" + +// Sensor & HMD Factories +#include "OVR_LatencyTestImpl.h" +#include "OVR_SensorImpl.h" +#include "OVR_Linux_HIDDevice.h" +#include "OVR_Linux_HMDDevice.h" + +#include "Kernel/OVR_Timer.h" +#include "Kernel/OVR_Std.h" +#include "Kernel/OVR_Log.h" + +namespace OVR { namespace Linux { + + +//------------------------------------------------------------------------------------- +// **** Linux::DeviceManager + +DeviceManager::DeviceManager() +{ +} + +DeviceManager::~DeviceManager() +{ +} + +bool DeviceManager::Initialize(DeviceBase*) +{ + if (!DeviceManagerImpl::Initialize(0)) + return false; + + pThread = *new DeviceManagerThread(); + if (!pThread || !pThread->Start()) + return false; + + // Wait for the thread to be fully up and running. + pThread->StartupEvent.Wait(); + + // Do this now that we know the thread's run loop. + HidDeviceManager = *HIDDeviceManager::CreateInternal(this); + + pCreateDesc->pDevice = this; + LogText("OVR::DeviceManager - initialized.\n"); + return true; +} + +void DeviceManager::Shutdown() +{ + LogText("OVR::DeviceManager - shutting down.\n"); + + // Set Manager shutdown marker variable; this prevents + // any existing DeviceHandle objects from accessing device. + pCreateDesc->pLock->pManager = 0; + + // Push for thread shutdown *WITH NO WAIT*. + // This will have the following effect: + // - Exit command will get enqueued, which will be executed later on the thread itself. + // - Beyond this point, this DeviceManager object may be deleted by our caller. + // - Other commands, such as CreateDevice, may execute before ExitCommand, but they will + // fail gracefully due to pLock->pManager == 0. Future commands can't be enqued + // after pManager is null. + // - Once ExitCommand executes, ThreadCommand::Run loop will exit and release the last + // reference to the thread object. + pThread->PushExitCommand(false); + pThread.Clear(); + + DeviceManagerImpl::Shutdown(); +} + +ThreadCommandQueue* DeviceManager::GetThreadQueue() +{ + return pThread; +} + +ThreadId DeviceManager::GetThreadId() const +{ + return pThread->GetThreadId(); +} + +bool DeviceManager::GetDeviceInfo(DeviceInfo* info) const +{ + if ((info->InfoClassType != Device_Manager) && + (info->InfoClassType != Device_None)) + return false; + + info->Type = Device_Manager; + info->Version = 0; + info->ProductName = "DeviceManager"; + info->Manufacturer = "Oculus VR, Inc."; + return true; +} + +DeviceEnumerator<> DeviceManager::EnumerateDevicesEx(const DeviceEnumerationArgs& args) +{ + // TBD: Can this be avoided in the future, once proper device notification is in place? + pThread->PushCall((DeviceManagerImpl*)this, + &DeviceManager::EnumerateAllFactoryDevices, true); + + return DeviceManagerImpl::EnumerateDevicesEx(args); +} + + +//------------------------------------------------------------------------------------- +// ***** DeviceManager Thread + +DeviceManagerThread::DeviceManagerThread() + : Thread(ThreadStackSize) +{ + int result = pipe(CommandFd); + OVR_ASSERT(!result); + OVR_UNUSED(result); + + AddSelectFd(NULL, CommandFd[0]); +} + +DeviceManagerThread::~DeviceManagerThread() +{ + if (CommandFd[0]) + { + RemoveSelectFd(NULL, CommandFd[0]); + close(CommandFd[0]); + close(CommandFd[1]); + } +} + +bool DeviceManagerThread::AddSelectFd(Notifier* notify, int fd) +{ + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN|POLLHUP|POLLERR; + pfd.revents = 0; + + FdNotifiers.PushBack(notify); + PollFds.PushBack(pfd); + + OVR_ASSERT(FdNotifiers.GetSize() == PollFds.GetSize()); + return true; +} + +bool DeviceManagerThread::RemoveSelectFd(Notifier* notify, int fd) +{ + // [0] is reserved for thread commands with notify of null, but we still + // can use this function to remove it. + for (UPInt i = 0; i < FdNotifiers.GetSize(); i++) + { + if ((FdNotifiers[i] == notify) && (PollFds[i].fd == fd)) + { + FdNotifiers.RemoveAt(i); + PollFds.RemoveAt(i); + return true; + } + } + return false; +} + + + +int DeviceManagerThread::Run() +{ + ThreadCommand::PopBuffer command; + + SetThreadName("OVR::DeviceManagerThread"); + LogText("OVR::DeviceManagerThread - running (ThreadId=%p).\n", GetThreadId()); + + // Signal to the parent thread that initialization has finished. + StartupEvent.SetEvent(); + + while(!IsExiting()) + { + // PopCommand will reset event on empty queue. + if (PopCommand(&command)) + { + command.Execute(); + } + else + { + bool commands = 0; + do + { + int waitMs = -1; + + // If devices have time-dependent logic registered, get the longest wait + // allowed based on current ticks. + if (!TicksNotifiers.IsEmpty()) + { + double timeSeconds = Timer::GetSeconds(); + unsigned waitAllowed; + + for (UPInt j = 0; j < TicksNotifiers.GetSize(); j++) + { + waitAllowed = (unsigned)(TicksNotifiers[j]->OnTicks(timeSeconds) * Timer::MsPerSecond); + if (waitAllowed < (unsigned)waitMs) + waitMs = waitAllowed; + } + } + + // wait until there is data available on one of the devices or the timeout expires + int n = poll(&PollFds[0], PollFds.GetSize(), waitMs); + + if (n > 0) + { + // Iterate backwards through the list so the ordering will not be + // affected if the called object gets removed during the callback + // Also, the HID data streams are located toward the back of the list + // and servicing them first will allow a disconnect to be handled + // and cleaned directly at the device first instead of the general HID monitor + for (int i=PollFds.GetSize()-1; i>=0; i--) + { + if (PollFds[i].revents & POLLERR) + { + OVR_DEBUG_LOG(("poll: error on [%d]: %d", i, PollFds[i].fd)); + } + else if (PollFds[i].revents & POLLIN) + { + if (FdNotifiers[i]) + FdNotifiers[i]->OnEvent(i, PollFds[i].fd); + else if (i == 0) // command + { + char dummy[128]; + read(PollFds[i].fd, dummy, 128); + commands = 1; + } + } + + if (PollFds[i].revents & POLLHUP) + PollFds[i].events = 0; + + if (PollFds[i].revents != 0) + { + n--; + if (n == 0) + break; + } + } + } + } while (PollFds.GetSize() > 0 && !commands); + } + } + + LogText("OVR::DeviceManagerThread - exiting (ThreadId=%p).\n", GetThreadId()); + return 0; +} + +bool DeviceManagerThread::AddTicksNotifier(Notifier* notify) +{ + TicksNotifiers.PushBack(notify); + return true; +} + +bool DeviceManagerThread::RemoveTicksNotifier(Notifier* notify) +{ + for (UPInt i = 0; i < TicksNotifiers.GetSize(); i++) + { + if (TicksNotifiers[i] == notify) + { + TicksNotifiers.RemoveAt(i); + return true; + } + } + return false; +} + +} // namespace Linux + + +//------------------------------------------------------------------------------------- +// ***** Creation + + +// Creates a new DeviceManager and initializes OVR. +DeviceManager* DeviceManager::Create() +{ + if (!System::IsInitialized()) + { + // Use custom message, since Log is not yet installed. + OVR_DEBUG_STATEMENT(Log::GetDefaultLog()-> + LogMessage(Log_Debug, "DeviceManager::Create failed - OVR::System not initialized"); ); + return 0; + } + + Ptr<Linux::DeviceManager> manager = *new Linux::DeviceManager; + + if (manager) + { + if (manager->Initialize(0)) + { + manager->AddFactory(&LatencyTestDeviceFactory::GetInstance()); + manager->AddFactory(&SensorDeviceFactory::GetInstance()); + manager->AddFactory(&Linux::HMDDeviceFactory::GetInstance()); + + manager->AddRef(); + } + else + { + manager.Clear(); + } + + } + + return manager.GetPtr(); +} + + +} // namespace OVR + diff --git a/LibOVR/Src/OVR_Linux_DeviceManager.h b/LibOVR/Src/OVR_Linux_DeviceManager.h new file mode 100644 index 0000000..6532103 --- /dev/null +++ b/LibOVR/Src/OVR_Linux_DeviceManager.h @@ -0,0 +1,122 @@ +/************************************************************************************ + +Filename : OVR_Linux_DeviceManager.h +Content : Linux-specific DeviceManager header. +Created : +Authors : + +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. + +*************************************************************************************/ + +#ifndef OVR_Linux_DeviceManager_h +#define OVR_Linux_DeviceManager_h + +#include "OVR_DeviceImpl.h" + +#include <unistd.h> +#include <sys/poll.h> + + +namespace OVR { namespace Linux { + +class DeviceManagerThread; + +//------------------------------------------------------------------------------------- +// ***** Linux DeviceManager + +class DeviceManager : public DeviceManagerImpl +{ +public: + DeviceManager(); + ~DeviceManager(); + + // Initialize/Shutdowncreate and shutdown manger thread. + virtual bool Initialize(DeviceBase* parent); + virtual void Shutdown(); + + virtual ThreadCommandQueue* GetThreadQueue(); + virtual ThreadId GetThreadId() const; + + virtual DeviceEnumerator<> EnumerateDevicesEx(const DeviceEnumerationArgs& args); + + virtual bool GetDeviceInfo(DeviceInfo* info) const; + + Ptr<DeviceManagerThread> pThread; +}; + +//------------------------------------------------------------------------------------- +// ***** Device Manager Background Thread + +class DeviceManagerThread : public Thread, public ThreadCommandQueue +{ + friend class DeviceManager; + enum { ThreadStackSize = 64 * 1024 }; +public: + DeviceManagerThread(); + ~DeviceManagerThread(); + + virtual int Run(); + + // ThreadCommandQueue notifications for CommandEvent handling. + virtual void OnPushNonEmpty_Locked() { write(CommandFd[1], this, 1); } + virtual void OnPopEmpty_Locked() { } + + class Notifier + { + public: + // Called when I/O is received + virtual void OnEvent(int i, int fd) = 0; + + // Called when timing ticks are updated. + // Returns the largest number of seconds this function can + // wait till next call. + virtual double OnTicks(double tickSeconds) + { + OVR_UNUSED1(tickSeconds); + return 1000.0; + } + }; + + // Add I/O notifier + bool AddSelectFd(Notifier* notify, int fd); + bool RemoveSelectFd(Notifier* notify, int fd); + + // Add notifier that will be called at regular intervals. + bool AddTicksNotifier(Notifier* notify); + bool RemoveTicksNotifier(Notifier* notify); + +private: + + bool threadInitialized() { return CommandFd[0] != 0; } + + // pipe used to signal commands + int CommandFd[2]; + + Array<struct pollfd> PollFds; + Array<Notifier*> FdNotifiers; + + Event StartupEvent; + + // Ticks notifiers - used for time-dependent events such as keep-alive. + Array<Notifier*> TicksNotifiers; +}; + +}} // namespace Linux::OVR + +#endif // OVR_Linux_DeviceManager_h diff --git a/LibOVR/Src/OVR_Linux_HIDDevice.cpp b/LibOVR/Src/OVR_Linux_HIDDevice.cpp new file mode 100644 index 0000000..133e5c3 --- /dev/null +++ b/LibOVR/Src/OVR_Linux_HIDDevice.cpp @@ -0,0 +1,819 @@ +/************************************************************************************ +Filename : OVR_Linux_HIDDevice.cpp +Content : Linux HID device implementation. +Created : February 26, 2013 +Authors : Lee Cooper + +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 "OVR_Linux_HIDDevice.h" + +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <linux/hidraw.h> +#include "OVR_HIDDeviceImpl.h" + +namespace OVR { namespace Linux { + +static const UInt32 MAX_QUEUED_INPUT_REPORTS = 5; + +//------------------------------------------------------------------------------------- +// **** Linux::DeviceManager +//----------------------------------------------------------------------------- +HIDDeviceManager::HIDDeviceManager(DeviceManager* manager) : DevManager(manager) +{ + UdevInstance = NULL; + HIDMonitor = NULL; + HIDMonHandle = -1; +} + +//----------------------------------------------------------------------------- +HIDDeviceManager::~HIDDeviceManager() +{ +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::initializeManager() +{ + if (HIDMonitor) + { + return true; + } + + // Create a udev_monitor handle to watch for device changes (hot-plug detection) + HIDMonitor = udev_monitor_new_from_netlink(UdevInstance, "udev"); + if (HIDMonitor == NULL) + { + return false; + } + + udev_monitor_filter_add_match_subsystem_devtype(HIDMonitor, "hidraw", NULL); // filter for hidraw only + + int err = udev_monitor_enable_receiving(HIDMonitor); + if (err) + { + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + return false; + } + + // Get the file descriptor (fd) for the monitor. + HIDMonHandle = udev_monitor_get_fd(HIDMonitor); + if (HIDMonHandle < 0) + { + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + return false; + } + + // This file handle will be polled along-side with the device hid handles for changes + // Add the handle to the polling list + if (!DevManager->pThread->AddSelectFd(this, HIDMonHandle)) + { + close(HIDMonHandle); + HIDMonHandle = -1; + + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::Initialize() +{ + // Get a udev library handle. This handle must stay active during the + // duration the lifetime of device monitoring handles + UdevInstance = udev_new(); + if (!UdevInstance) + return false; + + return initializeManager(); +} + +//----------------------------------------------------------------------------- +void HIDDeviceManager::Shutdown() +{ + OVR_ASSERT_LOG((UdevInstance), ("Should have called 'Initialize' before 'Shutdown'.")); + + if (HIDMonitor) + { + DevManager->pThread->RemoveSelectFd(this, HIDMonHandle); + close(HIDMonHandle); + HIDMonHandle = -1; + + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + } + + udev_unref(UdevInstance); // release the library + + LogText("OVR::Linux::HIDDeviceManager - shutting down.\n"); +} + +//------------------------------------------------------------------------------- +bool HIDDeviceManager::AddNotificationDevice(HIDDevice* device) +{ + NotificationDevices.PushBack(device); + return true; +} + +//------------------------------------------------------------------------------- +bool HIDDeviceManager::RemoveNotificationDevice(HIDDevice* device) +{ + for (UPInt i = 0; i < NotificationDevices.GetSize(); i++) + { + if (NotificationDevices[i] == device) + { + NotificationDevices.RemoveAt(i); + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::getIntProperty(udev_device* device, + const char* propertyName, + SInt32* pResult) +{ + const char* str = udev_device_get_sysattr_value(device, propertyName); + if (str) + { + *pResult = strtol(str, NULL, 16); + return true; + } + else + { + *pResult = 0; + return true; + } +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::initVendorProductVersion(udev_device* device, HIDDeviceDesc* pDevDesc) +{ + SInt32 result; + if (getIntProperty(device, "idVendor", &result)) + pDevDesc->VendorId = result; + else + return false; + + if (getIntProperty(device, "idProduct", &result)) + pDevDesc->ProductId = result; + else + return false; + + if (getIntProperty(device, "bcdDevice", &result)) + pDevDesc->VersionNumber = result; + else + return false; + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::getStringProperty(udev_device* device, + const char* propertyName, + OVR::String* pResult) +{ + // Get the attribute in UTF8 + const char* str = udev_device_get_sysattr_value(device, propertyName); + if (str) + { // Copy the string into the return value + *pResult = String(str); + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::Enumerate(HIDEnumerateVisitor* enumVisitor) +{ + + if (!initializeManager()) + { + return false; + } + + // Get a list of hid devices + udev_enumerate* devices = udev_enumerate_new(UdevInstance); + udev_enumerate_add_match_subsystem(devices, "hidraw"); + udev_enumerate_scan_devices(devices); + + udev_list_entry* entry = udev_enumerate_get_list_entry(devices); + + // Search each device for the matching vid/pid + while (entry != NULL) + { + // Get the device file name + const char* sysfs_path = udev_list_entry_get_name(entry); + udev_device* hid; // The device's HID udev node. + hid = udev_device_new_from_syspath(UdevInstance, sysfs_path); + const char* dev_path = udev_device_get_devnode(hid); + + // Get the USB device + hid = udev_device_get_parent_with_subsystem_devtype(hid, "usb", "usb_device"); + if (hid) + { + HIDDeviceDesc devDesc; + + // Check the VID/PID for a match + if (dev_path && + initVendorProductVersion(hid, &devDesc) && + enumVisitor->MatchVendorProduct(devDesc.VendorId, devDesc.ProductId)) + { + devDesc.Path = dev_path; + getFullDesc(hid, &devDesc); + + // Look for the device to check if it is already opened. + Ptr<DeviceCreateDesc> existingDevice = DevManager->FindHIDDevice(devDesc, true); + // if device exists and it is opened then most likely the device open() + // will fail; therefore, we just set Enumerated to 'true' and continue. + if (existingDevice && existingDevice->pDevice) + { + existingDevice->Enumerated = true; + } + else + { // open the device temporarily for startup communication + int device_handle = open(dev_path, O_RDWR); + if (device_handle >= 0) + { + // Construct minimal device that the visitor callback can get feature reports from + Linux::HIDDevice device(this, device_handle); + enumVisitor->Visit(device, devDesc); + + close(device_handle); // close the file handle + } + } + } + + udev_device_unref(hid); + entry = udev_list_entry_get_next(entry); + } + } + + // Free the enumerator and udev objects + udev_enumerate_unref(devices); + + return true; +} + +//----------------------------------------------------------------------------- +OVR::HIDDevice* HIDDeviceManager::Open(const String& path) +{ + Ptr<Linux::HIDDevice> device = *new Linux::HIDDevice(this); + + if (device->HIDInitialize(path)) + { + device->AddRef(); + return device; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::getFullDesc(udev_device* device, HIDDeviceDesc* desc) +{ + + if (!initVendorProductVersion(device, desc)) + { + return false; + } + + if (!getStringProperty(device, "serial", &(desc->SerialNumber))) + { + return false; + } + + getStringProperty(device, "manufacturer", &(desc->Manufacturer)); + getStringProperty(device, "product", &(desc->Product)); + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::GetDescriptorFromPath(const char* dev_path, HIDDeviceDesc* desc) +{ + if (!initializeManager()) + { + return false; + } + + // Search for the udev device from the given pathname so we can + // have a handle to query device properties + + udev_enumerate* devices = udev_enumerate_new(UdevInstance); + udev_enumerate_add_match_subsystem(devices, "hidraw"); + udev_enumerate_scan_devices(devices); + + udev_list_entry* entry = udev_enumerate_get_list_entry(devices); + + bool success = false; + // Search for the device with the matching path + while (entry != NULL) + { + // Get the device file name + const char* sysfs_path = udev_list_entry_get_name(entry); + udev_device* hid; // The device's HID udev node. + hid = udev_device_new_from_syspath(UdevInstance, sysfs_path); + const char* path = udev_device_get_devnode(hid); + + if (OVR_strcmp(dev_path, path) == 0) + { // Found the device so lets collect the device descriptor + + // Get the USB device + hid = udev_device_get_parent_with_subsystem_devtype(hid, "usb", "usb_device"); + if (hid) + { + desc->Path = dev_path; + success = getFullDesc(hid, desc); + } + + } + + udev_device_unref(hid); + entry = udev_list_entry_get_next(entry); + } + + // Free the enumerator + udev_enumerate_unref(devices); + + return success; +} + +//----------------------------------------------------------------------------- +void HIDDeviceManager::OnEvent(int i, int fd) +{ + OVR_UNUSED(i); + OVR_UNUSED(fd); + + // There is a device status change + udev_device* hid = udev_monitor_receive_device(HIDMonitor); + if (hid) + { + const char* dev_path = udev_device_get_devnode(hid); + const char* action = udev_device_get_action(hid); + + HIDDeviceDesc device_info; + device_info.Path = dev_path; + + MessageType notify_type; + if (OVR_strcmp(action, "add") == 0) + { + notify_type = Message_DeviceAdded; + + // Retrieve the device info. This can only be done on a connected + // device and is invalid for a disconnected device + + // Get the USB device + hid = udev_device_get_parent_with_subsystem_devtype(hid, "usb", "usb_device"); + if (!hid) + { + return; + } + + getFullDesc(hid, &device_info); + } + else if (OVR_strcmp(action, "remove") == 0) + { + notify_type = Message_DeviceRemoved; + } + else + { + return; + } + + bool error = false; + bool deviceFound = false; + for (UPInt i = 0; i < NotificationDevices.GetSize(); i++) + { + if (NotificationDevices[i] && + NotificationDevices[i]->OnDeviceNotification(notify_type, &device_info, &error)) + { + // The notification was for an existing device + deviceFound = true; + break; + } + } + + if (notify_type == Message_DeviceAdded && !deviceFound) + { + DevManager->DetectHIDDevice(device_info); + } + + udev_device_unref(hid); + } +} + +//============================================================================= +// Linux::HIDDevice +//============================================================================= +HIDDevice::HIDDevice(HIDDeviceManager* manager) + : InMinimalMode(false), HIDManager(manager) +{ + DeviceHandle = -1; +} + +//----------------------------------------------------------------------------- +// This is a minimal constructor used during enumeration for us to pass +// a HIDDevice to the visit function (so that it can query feature reports). +HIDDevice::HIDDevice(HIDDeviceManager* manager, int device_handle) +: InMinimalMode(true), HIDManager(manager), DeviceHandle(device_handle) +{ +} + +//----------------------------------------------------------------------------- +HIDDevice::~HIDDevice() +{ + if (!InMinimalMode) + { + HIDShutdown(); + } +} + +//----------------------------------------------------------------------------- +bool HIDDevice::HIDInitialize(const String& path) +{ + const char* hid_path = path.ToCStr(); + if (!openDevice(hid_path)) + { + LogText("OVR::Linux::HIDDevice - Failed to open HIDDevice: %s", hid_path); + return false; + } + + HIDManager->DevManager->pThread->AddTicksNotifier(this); + HIDManager->AddNotificationDevice(this); + + LogText("OVR::Linux::HIDDevice - Opened '%s'\n" + " Manufacturer:'%s' Product:'%s' Serial#:'%s'\n", + DevDesc.Path.ToCStr(), + DevDesc.Manufacturer.ToCStr(), DevDesc.Product.ToCStr(), + DevDesc.SerialNumber.ToCStr()); + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDevice::initInfo() +{ + // Device must have been successfully opened. + OVR_ASSERT(DeviceHandle >= 0); + + int desc_size = 0; + hidraw_report_descriptor rpt_desc; + memset(&rpt_desc, 0, sizeof(rpt_desc)); + + // get report descriptor size + int r = ioctl(DeviceHandle, HIDIOCGRDESCSIZE, &desc_size); + if (r < 0) + { + OVR_ASSERT_LOG(false, ("Failed to get report descriptor size.")); + return false; + } + + // Get the report descriptor + rpt_desc.size = desc_size; + r = ioctl(DeviceHandle, HIDIOCGRDESC, &rpt_desc); + if (r < 0) + { + OVR_ASSERT_LOG(false, ("Failed to get report descriptor.")); + return false; + } + + /* + // Get report lengths. + SInt32 bufferLength; + bool getResult = HIDManager->getIntProperty(Device, CFSTR(kIOHIDMaxInputReportSizeKey), &bufferLength); + OVR_ASSERT(getResult); + InputReportBufferLength = (UInt16) bufferLength; + + getResult = HIDManager->getIntProperty(Device, CFSTR(kIOHIDMaxOutputReportSizeKey), &bufferLength); + OVR_ASSERT(getResult); + OutputReportBufferLength = (UInt16) bufferLength; + + getResult = HIDManager->getIntProperty(Device, CFSTR(kIOHIDMaxFeatureReportSizeKey), &bufferLength); + OVR_ASSERT(getResult); + FeatureReportBufferLength = (UInt16) bufferLength; + + + if (ReadBufferSize < InputReportBufferLength) + { + OVR_ASSERT_LOG(false, ("Input report buffer length is bigger than read buffer.")); + return false; + } + + // Get device desc. + if (!HIDManager->getFullDesc(Device, &DevDesc)) + { + OVR_ASSERT_LOG(false, ("Failed to get device desc while initializing device.")); + return false; + } + + return true; + */ + + // Get report lengths. +// TODO: hard-coded for now. Need to interpret these values from the report descriptor + InputReportBufferLength = 62; + OutputReportBufferLength = 0; + FeatureReportBufferLength = 69; + + if (ReadBufferSize < InputReportBufferLength) + { + OVR_ASSERT_LOG(false, ("Input report buffer length is bigger than read buffer.")); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDevice::openDevice(const char* device_path) +{ + // First fill out the device descriptor + if (!HIDManager->GetDescriptorFromPath(device_path, &DevDesc)) + { + return false; + } + + // Now open the device + DeviceHandle = open(device_path, O_RDWR); + if (DeviceHandle < 0) + { + OVR_DEBUG_LOG(("Failed 'CreateHIDFile' while opening device, error = 0x%X.", errno)); + DeviceHandle = -1; + return false; + } + + // fill out some values from the feature report descriptor + if (!initInfo()) + { + OVR_ASSERT_LOG(false, ("Failed to get HIDDevice info.")); + + close(DeviceHandle); + DeviceHandle = -1; + return false; + } + + // Add the device to the polling list + if (!HIDManager->DevManager->pThread->AddSelectFd(this, DeviceHandle)) + { + OVR_ASSERT_LOG(false, ("Failed to initialize polling for HIDDevice.")); + + close(DeviceHandle); + DeviceHandle = -1; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +void HIDDevice::HIDShutdown() +{ + + HIDManager->DevManager->pThread->RemoveTicksNotifier(this); + HIDManager->RemoveNotificationDevice(this); + + if (DeviceHandle >= 0) // Device may already have been closed if unplugged. + { + closeDevice(false); + } + + LogText("OVR::Linux::HIDDevice - HIDShutdown '%s'\n", DevDesc.Path.ToCStr()); +} + +//----------------------------------------------------------------------------- +void HIDDevice::closeDevice(bool wasUnplugged) +{ + OVR_UNUSED(wasUnplugged); + OVR_ASSERT(DeviceHandle >= 0); + + + HIDManager->DevManager->pThread->RemoveSelectFd(this, DeviceHandle); + + close(DeviceHandle); // close the file handle + DeviceHandle = -1; + + LogText("OVR::Linux::HIDDevice - HID Device Closed '%s'\n", DevDesc.Path.ToCStr()); +} + +//----------------------------------------------------------------------------- +void HIDDevice::closeDeviceOnIOError() +{ + LogText("OVR::Linux::HIDDevice - Lost connection to '%s'\n", DevDesc.Path.ToCStr()); + closeDevice(false); +} + +//----------------------------------------------------------------------------- +bool HIDDevice::SetFeatureReport(UByte* data, UInt32 length) +{ + + if (DeviceHandle < 0) + return false; + + UByte reportID = data[0]; + + if (reportID == 0) + { + // Not using reports so remove from data packet. + data++; + length--; + } + + int r = ioctl(DeviceHandle, HIDIOCSFEATURE(length), data); + return (r >= 0); +} + +//----------------------------------------------------------------------------- +bool HIDDevice::GetFeatureReport(UByte* data, UInt32 length) +{ + if (DeviceHandle < 0) + return false; + + int r = ioctl(DeviceHandle, HIDIOCGFEATURE(length), data); + return (r >= 0); +} + +//----------------------------------------------------------------------------- +double HIDDevice::OnTicks(double tickSeconds) +{ + if (Handler) + { + return Handler->OnTicks(tickSeconds); + } + + return DeviceManagerThread::Notifier::OnTicks(tickSeconds); +} + +//----------------------------------------------------------------------------- +void HIDDevice::OnEvent(int i, int fd) +{ + OVR_UNUSED(i); + // We have data to read from the device + int bytes = read(fd, ReadBuffer, ReadBufferSize); + if (bytes >= 0) + { +// TODO: I need to handle partial messages and package reconstruction + if (Handler) + { + Handler->OnInputReport(ReadBuffer, bytes); + } + } + else + { // Close the device on read error. + closeDeviceOnIOError(); + } +} + +//----------------------------------------------------------------------------- +bool HIDDevice::OnDeviceNotification(MessageType messageType, + HIDDeviceDesc* device_info, + bool* error) +{ + const char* device_path = device_info->Path.ToCStr(); + + if (messageType == Message_DeviceAdded && DeviceHandle < 0) + { + // Is this the correct device? + if (!(device_info->VendorId == DevDesc.VendorId + && device_info->ProductId == DevDesc.ProductId + && device_info->SerialNumber == DevDesc.SerialNumber)) + { + return false; + } + + // A closed device has been re-added. Try to reopen. + if (!openDevice(device_path)) + { + LogError("OVR::Linux::HIDDevice - Failed to reopen a device '%s' that was re-added.\n", + device_path); + *error = true; + return true; + } + + LogText("OVR::Linux::HIDDevice - Reopened device '%s'\n", device_path); + + if (Handler) + { + Handler->OnDeviceMessage(HIDHandler::HIDDeviceMessage_DeviceAdded); + } + } + else if (messageType == Message_DeviceRemoved) + { + // Is this the correct device? + // For disconnected device, the device description will be invalid so + // checking the path is the only way to match them + if (DevDesc.Path.CompareNoCase(device_path) != 0) + { + return false; + } + + if (DeviceHandle >= 0) + { + closeDevice(true); + } + + if (Handler) + { + Handler->OnDeviceMessage(HIDHandler::HIDDeviceMessage_DeviceRemoved); + } + } + else + { + OVR_ASSERT(0); + } + + *error = false; + return true; +} + +//----------------------------------------------------------------------------- +HIDDeviceManager* HIDDeviceManager::CreateInternal(Linux::DeviceManager* devManager) +{ + + if (!System::IsInitialized()) + { + // Use custom message, since Log is not yet installed. + OVR_DEBUG_STATEMENT(Log::GetDefaultLog()-> + LogMessage(Log_Debug, "HIDDeviceManager::Create failed - OVR::System not initialized"); ); + return 0; + } + + Ptr<Linux::HIDDeviceManager> manager = *new Linux::HIDDeviceManager(devManager); + + if (manager) + { + if (manager->Initialize()) + { + manager->AddRef(); + } + else + { + manager.Clear(); + } + } + + return manager.GetPtr(); +} + +} // namespace Linux + +//------------------------------------------------------------------------------------- +// ***** Creation + +// Creates a new HIDDeviceManager and initializes OVR. +HIDDeviceManager* HIDDeviceManager::Create(Ptr<OVR::DeviceManager>& deviceManager) +{ + + if (!System::IsInitialized()) + { + // Use custom message, since Log is not yet installed. + OVR_DEBUG_STATEMENT(Log::GetDefaultLog()-> + LogMessage(Log_Debug, "HIDDeviceManager::Create failed - OVR::System not initialized"); ); + return 0; + } + + Ptr<Linux::DeviceManager> deviceManagerLinux = *new Linux::DeviceManager; + + if (!deviceManagerLinux) + { + return NULL; + } + + if (!deviceManagerLinux->Initialize(NULL)) + { + return NULL; + } + + deviceManager = deviceManagerLinux; + + return deviceManagerLinux->GetHIDDeviceManager(); +} + +} // namespace OVR diff --git a/LibOVR/Src/OVR_Linux_HIDDevice.h b/LibOVR/Src/OVR_Linux_HIDDevice.h new file mode 100644 index 0000000..52f2d69 --- /dev/null +++ b/LibOVR/Src/OVR_Linux_HIDDevice.h @@ -0,0 +1,135 @@ +/************************************************************************************ +Filename : OVR_Linux_HIDDevice.h +Content : Linux HID device implementation. +Created : June 13, 2013 +Authors : Brant Lewis + +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. + +*************************************************************************************/ + +#ifndef OVR_LINUX_HIDDevice_h +#define OVR_LINUX_HIDDevice_h + +#include "OVR_HIDDevice.h" +#include "OVR_Linux_DeviceManager.h" +#include <libudev.h> + +namespace OVR { namespace Linux { + +class HIDDeviceManager; + +//------------------------------------------------------------------------------------- +// ***** Linux HIDDevice + +class HIDDevice : public OVR::HIDDevice, public DeviceManagerThread::Notifier +{ +private: + friend class HIDDeviceManager; + +public: + HIDDevice(HIDDeviceManager* manager); + + // This is a minimal constructor used during enumeration for us to pass + // a HIDDevice to the visit function (so that it can query feature reports). + HIDDevice(HIDDeviceManager* manager, int device_handle); + + virtual ~HIDDevice(); + + bool HIDInitialize(const String& path); + void HIDShutdown(); + + virtual bool SetFeatureReport(UByte* data, UInt32 length); + virtual bool GetFeatureReport(UByte* data, UInt32 length); + + // DeviceManagerThread::Notifier + void OnEvent(int i, int fd); + double OnTicks(double tickSeconds); + + bool OnDeviceNotification(MessageType messageType, + HIDDeviceDesc* device_info, + bool* error); + +private: + bool initInfo(); + bool openDevice(const char* dev_path); + void closeDevice(bool wasUnplugged); + void closeDeviceOnIOError(); + bool setupDevicePluggedInNotification(); + + bool InMinimalMode; + HIDDeviceManager* HIDManager; + int DeviceHandle; // file handle to the device + HIDDeviceDesc DevDesc; + + enum { ReadBufferSize = 96 }; + UByte ReadBuffer[ReadBufferSize]; + + UInt16 InputReportBufferLength; + UInt16 OutputReportBufferLength; + UInt16 FeatureReportBufferLength; +}; + + +//------------------------------------------------------------------------------------- +// ***** Linux HIDDeviceManager + +class HIDDeviceManager : public OVR::HIDDeviceManager, public DeviceManagerThread::Notifier +{ + friend class HIDDevice; + +public: + HIDDeviceManager(Linux::DeviceManager* Manager); + virtual ~HIDDeviceManager(); + + virtual bool Initialize(); + virtual void Shutdown(); + + virtual bool Enumerate(HIDEnumerateVisitor* enumVisitor); + virtual OVR::HIDDevice* Open(const String& path); + + static HIDDeviceManager* CreateInternal(DeviceManager* manager); + + void OnEvent(int i, int fd); + +private: + bool initializeManager(); + bool initVendorProductVersion(udev_device* device, HIDDeviceDesc* pDevDesc); + bool getPath(udev_device* device, String* pPath); + bool getIntProperty(udev_device* device, const char* key, int32_t* pResult); + bool getStringProperty(udev_device* device, + const char* propertyName, + OVR::String* pResult); + bool getFullDesc(udev_device* device, HIDDeviceDesc* desc); + bool GetDescriptorFromPath(const char* dev_path, HIDDeviceDesc* desc); + + bool AddNotificationDevice(HIDDevice* device); + bool RemoveNotificationDevice(HIDDevice* device); + + DeviceManager* DevManager; + + udev* UdevInstance; // a handle to the udev library instance + udev_monitor* HIDMonitor; + int HIDMonHandle; // the udev_monitor file handle + + Array<HIDDevice*> NotificationDevices; +}; + +}} // namespace OVR::Linux + +#endif // OVR_Linux_HIDDevice_h diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.cpp b/LibOVR/Src/OVR_Linux_HMDDevice.cpp new file mode 100644 index 0000000..98143d3 --- /dev/null +++ b/LibOVR/Src/OVR_Linux_HMDDevice.cpp @@ -0,0 +1,291 @@ +/************************************************************************************ + +Filename : OVR_Linux_HMDDevice.h +Content : Linux HMDDevice implementation +Created : June 17, 2013 +Authors : Brant Lewis + +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 "OVR_Linux_HMDDevice.h" + +#include "OVR_Linux_DeviceManager.h" + +#include "OVR_Profile.h" + +#include "../../3rdParty/EDID/edid.h" + +namespace OVR { namespace Linux { + +//------------------------------------------------------------------------------------- + +HMDDeviceCreateDesc::HMDDeviceCreateDesc(DeviceFactory* factory, const String& displayDeviceName, long dispId) + : DeviceCreateDesc(factory, Device_HMD), + DisplayDeviceName(displayDeviceName), + Contents(0), + DisplayId(dispId) +{ + DeviceId = DisplayDeviceName; + + Desktop.X = 0; + Desktop.Y = 0; + ResolutionInPixels = Sizei(0); + ScreenSizeInMeters = Sizef(0.0f); + VCenterFromTopInMeters = 0.0f; + LensSeparationInMeters = 0.0f; +} + +HMDDeviceCreateDesc::HMDDeviceCreateDesc(const HMDDeviceCreateDesc& other) + : DeviceCreateDesc(other.pFactory, Device_HMD), + DeviceId(other.DeviceId), DisplayDeviceName(other.DisplayDeviceName), + Contents(other.Contents), + DisplayId(other.DisplayId) +{ + Desktop.X = other.Desktop.X; + Desktop.Y = other.Desktop.Y; + ResolutionInPixels = other.ResolutionInPixels; + ScreenSizeInMeters = other.ScreenSizeInMeters; + VCenterFromTopInMeters = other.VCenterFromTopInMeters; + LensSeparationInMeters = other.LensSeparationInMeters; +} + +HMDDeviceCreateDesc::MatchResult HMDDeviceCreateDesc::MatchDevice(const DeviceCreateDesc& other, + DeviceCreateDesc** pcandidate) const +{ + if ((other.Type != Device_HMD) || (other.pFactory != pFactory)) + return Match_None; + + // There are several reasons we can come in here: + // a) Matching this HMD Monitor created desc to OTHER HMD Monitor desc + // - Require exact device DeviceId/DeviceName match + // b) Matching SensorDisplayInfo created desc to OTHER HMD Monitor desc + // - This DeviceId is empty; becomes candidate + // c) Matching this HMD Monitor created desc to SensorDisplayInfo desc + // - This other.DeviceId is empty; becomes candidate + + const HMDDeviceCreateDesc& s2 = (const HMDDeviceCreateDesc&) other; + + if ((DeviceId == s2.DeviceId) && + (DisplayId == s2.DisplayId)) + { + // Non-null DeviceId may match while size is different if screen size was overwritten + // by SensorDisplayInfo in prior iteration. + if (!DeviceId.IsEmpty() || + (ScreenSizeInMeters == s2.ScreenSizeInMeters) ) + { + *pcandidate = 0; + return Match_Found; + } + } + + + // DisplayInfo takes precedence, although we try to match it first. + if ((ResolutionInPixels == s2.ResolutionInPixels) && + (ScreenSizeInMeters == s2.ScreenSizeInMeters)) + { + if (DeviceId.IsEmpty() && !s2.DeviceId.IsEmpty()) + { + *pcandidate = const_cast<DeviceCreateDesc*>((const DeviceCreateDesc*)this); + return Match_Candidate; + } + + *pcandidate = 0; + return Match_Found; + } + + // SensorDisplayInfo may override resolution settings, so store as candidate. + if (s2.DeviceId.IsEmpty()) + { + *pcandidate = const_cast<DeviceCreateDesc*>((const DeviceCreateDesc*)this); + return Match_Candidate; + } + // OTHER HMD Monitor desc may initialize DeviceName/Id + else if (DeviceId.IsEmpty()) + { + *pcandidate = const_cast<DeviceCreateDesc*>((const DeviceCreateDesc*)this); + return Match_Candidate; + } + + return Match_None; +} + + +bool HMDDeviceCreateDesc::UpdateMatchedCandidate(const DeviceCreateDesc& other, + bool* newDeviceFlag) +{ + // This candidate was the the "best fit" to apply sensor DisplayInfo to. + OVR_ASSERT(other.Type == Device_HMD); + + const HMDDeviceCreateDesc& s2 = (const HMDDeviceCreateDesc&) other; + + // Force screen size on resolution from SensorDisplayInfo. + // We do this because USB detection is more reliable as compared to HDMI EDID, + // which may be corrupted by splitter reporting wrong monitor + if (s2.DeviceId.IsEmpty()) + { + ScreenSizeInMeters = s2.ScreenSizeInMeters; + Contents |= Contents_Screen; + + if (s2.Contents & HMDDeviceCreateDesc::Contents_Distortion) + { + memcpy(DistortionK, s2.DistortionK, sizeof(float)*4); + // TODO: DistortionEqn + Contents |= Contents_Distortion; + } + DeviceId = s2.DeviceId; + DisplayId = s2.DisplayId; + DisplayDeviceName = s2.DisplayDeviceName; + Desktop.X = s2.Desktop.X; + Desktop.Y = s2.Desktop.Y; + if (newDeviceFlag) *newDeviceFlag = true; + } + else if (DeviceId.IsEmpty()) + { + // This branch is executed when 'fake' HMD descriptor is being replaced by + // the real one. + DeviceId = s2.DeviceId; + DisplayId = s2.DisplayId; + DisplayDeviceName = s2.DisplayDeviceName; + Desktop.X = s2.Desktop.X; + Desktop.Y = s2.Desktop.Y; + + // ScreenSize and Resolution are NOT assigned here, since they may have + // come from a sensor DisplayInfo (which has precedence over HDMI). + + if (newDeviceFlag) *newDeviceFlag = true; + } + else + { + if (newDeviceFlag) *newDeviceFlag = false; + } + + return true; +} + +bool HMDDeviceCreateDesc::MatchDevice(const String& path) +{ + return DeviceId.CompareNoCase(path) == 0; +} + +//------------------------------------------------------------------------------------- +// ***** HMDDeviceFactory + +HMDDeviceFactory &HMDDeviceFactory::GetInstance() +{ + static HMDDeviceFactory instance; + return instance; +} + +void HMDDeviceFactory::EnumerateDevices(EnumerateVisitor& visitor) +{ + // For now we'll assume the Rift DK1 is attached in extended monitor mode. Ultimately we need to + // use XFree86 to enumerate X11 screens in case the Rift is attached as a separate screen. + + bool foundHMD = false; + Display* display = XOpenDisplay(NULL); + XRRScreenResources *screen = XRRGetScreenResources(display, DefaultRootWindow(display)); + for (int iscres = screen->noutput - 1; iscres >= 0; --iscres) { + RROutput output = screen->outputs[iscres]; + MonitorInfo * mi = read_edid_data(display, output); + if (mi == NULL) { + continue; + } + + XRROutputInfo * info = XRRGetOutputInfo (display, screen, output); + if (info && (0 == memcmp(mi->manufacturer_code, "OVR", 3))) { + + // Generate a device ID string similar to the way Windows does it + char device_id[32]; + OVR_sprintf(device_id, 32, "%s%04d", mi->manufacturer_code, mi->product_code); + + // The default monitor coordinates + int mx = 0; + int my = 0; + int mwidth = 1280; + int mheight = 800; + + if (info->connection == RR_Connected && info->crtc) { + XRRCrtcInfo * crtc_info = XRRGetCrtcInfo (display, screen, info->crtc); + if (crtc_info) + { + mx = crtc_info->x; + my = crtc_info->y; + //mwidth = crtc_info->width; + //mheight = crtc_info->height; + XRRFreeCrtcInfo(crtc_info); + } + } + + String deviceID = device_id; + HMDDeviceCreateDesc hmdCreateDesc(this, deviceID, iscres); + + // Hard-coded defaults in case the device doesn't have the data itself. + if (strstr(device_id, "OVR0003")) + { // DK2 prototypes and variants (default to HmdType_DK2) + hmdCreateDesc.SetScreenParameters(mx, my, 1920, 1080, 0.12576f, 0.07074f, 0.12576f*0.5f, 0.0635f ); + } + else if (strstr(device_id, "OVR0002")) + { // HD Prototypes (default to HmdType_DKHDProto) + hmdCreateDesc.SetScreenParameters(mx, my, 1920, 1080, 0.12096f, 0.06804f, 0.06804f*0.5f, 0.0635f ); + } + else if (strstr(device_id, "OVR0001")) + { // DK1 + hmdCreateDesc.SetScreenParameters(mx, my, mwidth, mheight, 0.14976f, 0.0936f, 0.0936f*0.5f, 0.0635f); + } + else if (strstr(device_id, "OVR00")) + { // Future Oculus HMD devices (default to DK1 dimensions) + hmdCreateDesc.SetScreenParameters(mx, my, mwidth, mheight, 0.14976f, 0.0936f, 0.0936f*0.5f, 0.0635f); + } + else + { // Duct-tape prototype + hmdCreateDesc.SetScreenParameters(mx, my, mwidth, mheight, 0.12096f, 0.0756f, 0.0756f*0.5f, 0.0635f); + } + + OVR_DEBUG_LOG_TEXT(("DeviceManager - HMD Found %s - %s\n", device_id, mi->dsc_product_name)); + + // Notify caller about detected device. This will call EnumerateAddDevice + // if the this is the first time device was detected. + visitor.Visit(hmdCreateDesc); + foundHMD = true; + break; + } // if + + XRRFreeOutputInfo(info); + delete mi; + } // for + XRRFreeScreenResources(screen); + + + // Real HMD device is not found; however, we still may have a 'fake' HMD + // device created via SensorDeviceImpl::EnumerateHMDFromSensorDisplayInfo. + // Need to find it and set 'Enumerated' to true to avoid Removal notification. + if (!foundHMD) + { + Ptr<DeviceCreateDesc> hmdDevDesc = getManager()->FindDevice("", Device_HMD); + if (hmdDevDesc) + hmdDevDesc->Enumerated = true; + } +} + +#include "OVR_Common_HMDDevice.cpp" + +}} // namespace OVR::Linux + + diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.h b/LibOVR/Src/OVR_Linux_HMDDevice.h new file mode 100644 index 0000000..a8c044f --- /dev/null +++ b/LibOVR/Src/OVR_Linux_HMDDevice.h @@ -0,0 +1,154 @@ +/************************************************************************************ + +Filename : OVR_Linux_HMDDevice.h +Content : Linux HMDDevice implementation +Created : June 17, 2013 +Authors : Brant Lewis + +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. + +*************************************************************************************/ + +#ifndef OVR_Linux_HMDDevice_h +#define OVR_Linux_HMDDevice_h + +#include "OVR_Linux_DeviceManager.h" +#include "OVR_Profile.h" + +namespace OVR { namespace Linux { + +class HMDDevice; + +//------------------------------------------------------------------------------------- + +// HMDDeviceFactory enumerates attached Oculus HMD devices. +// +// This is currently done by matching monitor device strings. + +class HMDDeviceFactory : public DeviceFactory +{ +public: + static HMDDeviceFactory &GetInstance(); + + // Enumerates devices, creating and destroying relevant objects in manager. + virtual void EnumerateDevices(EnumerateVisitor& visitor); + +protected: + DeviceManager* getManager() const { return (DeviceManager*) pManager; } +}; + + +class HMDDeviceCreateDesc : public DeviceCreateDesc +{ + friend class HMDDevice; + +protected: + enum + { + Contents_Screen = 1, + Contents_Distortion = 2, + }; + String DeviceId; + String DisplayDeviceName; + struct + { + int X, Y; + } Desktop; + unsigned int Contents; + + Sizei ResolutionInPixels; + Sizef ScreenSizeInMeters; + float VCenterFromTopInMeters; + float LensSeparationInMeters; + + // TODO: update these to splines. + DistortionEqnType DistortionEqn; + float DistortionK[4]; + + long DisplayId; + +public: + HMDDeviceCreateDesc(DeviceFactory* factory, + const String& displayDeviceName, long dispId); + HMDDeviceCreateDesc(const HMDDeviceCreateDesc& other); + + virtual DeviceCreateDesc* Clone() const + { + return new HMDDeviceCreateDesc(*this); + } + + virtual DeviceBase* NewDeviceInstance(); + + virtual MatchResult MatchDevice(const DeviceCreateDesc& other, + DeviceCreateDesc**) const; + + // Matches device by path. + virtual bool MatchDevice(const String& path); + + virtual bool UpdateMatchedCandidate(const DeviceCreateDesc&, bool* newDeviceFlag = NULL); + + virtual bool GetDeviceInfo(DeviceInfo* info) const; + + void SetScreenParameters(int x, int y, + int hres, int vres, + float hsize, float vsize, + float vCenterFromTopInMeters, float lensSeparationInMeters); + void SetDistortion(const float* dks); + + HmdTypeEnum GetHmdType() const; +}; + + +//------------------------------------------------------------------------------------- + +// HMDDevice represents an Oculus HMD device unit. An instance of this class +// is typically created from the DeviceManager. +// After HMD device is created, we its sensor data can be obtained by +// first creating a Sensor object and then wrappig it in SensorFusion. + +class HMDDevice : public DeviceImpl<OVR::HMDDevice> +{ +public: + HMDDevice(HMDDeviceCreateDesc* createDesc); + ~HMDDevice(); + + virtual bool Initialize(DeviceBase* parent); + virtual void Shutdown(); + + // Requests the currently used default profile. This profile affects the + // settings reported by HMDInfo. + virtual Profile* GetProfile(); + virtual const char* GetProfileName(); + virtual bool SetProfileName(const char* name); + + // Query associated sensor. + virtual OVR::SensorDevice* GetSensor(); + +protected: + HMDDeviceCreateDesc* getDesc() const { return (HMDDeviceCreateDesc*)pCreateDesc.GetPtr(); } + + // User name for the profile used with this device. + String ProfileName; + mutable Ptr<Profile> pCachedProfile; +}; + + +}} // namespace OVR::Linux + +#endif // OVR_Linux_HMDDevice_h + diff --git a/LibOVR/Src/OVR_Linux_SensorDevice.cpp b/LibOVR/Src/OVR_Linux_SensorDevice.cpp new file mode 100644 index 0000000..5b671a6 --- /dev/null +++ b/LibOVR/Src/OVR_Linux_SensorDevice.cpp @@ -0,0 +1,57 @@ +/************************************************************************************ + +Filename : OVR_Linux_SensorDevice.cpp +Content : Linux SensorDevice implementation +Created : June 13, 2013 +Authors : Brant Lewis + +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 "OVR_Linux_HMDDevice.h" +#include "OVR_SensorImpl.h" +#include "OVR_DeviceImpl.h" + +namespace OVR { namespace Linux { + +} // namespace Linux + +//------------------------------------------------------------------------------------- +void SensorDeviceImpl::EnumerateHMDFromSensorDisplayInfo( const SensorDisplayInfoImpl& displayInfo, + DeviceFactory::EnumerateVisitor& visitor) +{ + Linux::HMDDeviceCreateDesc hmdCreateDesc(&Linux::HMDDeviceFactory::GetInstance(), String(), 0); + + hmdCreateDesc.SetScreenParameters( 0, 0, + displayInfo.HResolution, displayInfo.VResolution, + displayInfo.HScreenSize, displayInfo.VScreenSize, + displayInfo.VCenter, displayInfo.LensSeparation); + + if ((displayInfo.DistortionType & SensorDisplayInfoImpl::Mask_BaseFmt) == SensorDisplayInfoImpl::Base_Distortion) + { + // TODO: update to spline system. + hmdCreateDesc.SetDistortion(displayInfo.DistortionK); + } + + visitor.Visit(hmdCreateDesc); +} + +} // namespace OVR + + diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp new file mode 100644 index 0000000..4844c29 --- /dev/null +++ b/LibOVR/Src/OVR_Profile.cpp @@ -0,0 +1,1517 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_Profile.cpp +Content : Structs and functions for loading and storing device profile settings +Created : February 14, 2013 +Notes : + + Profiles are used to store per-user settings that can be transferred and used + across multiple applications. For example, player IPD can be configured once + and reused for a unified experience across games. Configuration and saving of profiles + can be accomplished in game via the Profile API or by the official Oculus Configuration + Utility. + +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 "OVR_Profile.h" +#include "OVR_Device.h" +#include "OVR_JSON.h" +#include "Kernel/OVR_Types.h" +#include "Kernel/OVR_SysFile.h" +#include "Kernel/OVR_Allocator.h" +#include "Kernel/OVR_Array.h" + +#ifdef OVR_OS_WIN32 +#include <Shlobj.h> +#else +#include <dirent.h> +#include <sys/stat.h> + +#ifdef OVR_OS_LINUX +#include <unistd.h> +#include <pwd.h> +#endif + +#endif + + +#define PROFILE_VERSION 2.0 +#define MAX_PROFILE_MAJOR_VERSION 2 +#define MAX_DEVICE_PROFILE_MAJOR_VERSION 1 + +namespace OVR { + +//----------------------------------------------------------------------------- +// Returns the pathname of the JSON file containing the stored profiles +String GetBaseOVRPath(bool create_dir) +{ + String path; + +#if defined(OVR_OS_WIN32) + + TCHAR data_path[MAX_PATH]; + SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path); + path = String(data_path); + + path += "/Oculus"; + + if (create_dir) + { // Create the Oculus directory if it doesn't exist + WCHAR wpath[128]; + OVR::UTF8Util::DecodeString(wpath, path.ToCStr()); + + DWORD attrib = GetFileAttributes(wpath); + bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); + if (!exists) + { + CreateDirectory(wpath, NULL); + } + } + +#elif defined(OVR_OS_MAC) + + const char* home = getenv("HOME"); + path = home; + path += "/Library/Preferences/Oculus"; + + if (create_dir) + { // Create the Oculus directory if it doesn't exist + DIR* dir = opendir(path); + if (dir == NULL) + { + mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO); + } + else + { + closedir(dir); + } + } + +#else + + passwd* pwd = getpwuid(getuid()); + const char* home = pwd->pw_dir; + path = home; + path += "/.config/Oculus"; + + if (create_dir) + { // Create the Oculus directory if it doesn't exist + DIR* dir = opendir(path); + if (dir == NULL) + { + mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO); + } + else + { + closedir(dir); + } + } + +#endif + + return path; +} + +String ProfileManager::GetProfilePath(bool create_dir) +{ + String path = GetBaseOVRPath(create_dir); + path += "/ProfileDB.json"; + return path; +} + +bool ProfileManager::GetDeviceTags(const DeviceBase* device, String& product, String& serial) +{ + product = ""; + serial = ""; + + if (device && device->GetType() == Device_HMD) + { + HMDDevice* hmd = (HMDDevice*)device; + + Ptr<SensorDevice> sensor = *(hmd->GetSensor()); + if (sensor) + { + SensorInfo sinfo; + sensor->GetDeviceInfo(&sinfo); + serial = sinfo.SerialNumber; // get the serial number + + // Derive the product tag from the HMD product name + HMDInfo hmdinfo; + hmd->GetDeviceInfo(&hmdinfo); + + const char* product_name = NULL; + + // If the HMD is unrecognized then use the name stamped into the + // sensor firmware + if (hmdinfo.HmdType == HmdType_None || hmdinfo.HmdType == HmdType_Unknown) + product_name = sinfo.ProductName.ToCStr(); + else + product_name = hmdinfo.ProductName.ToCStr(); + + // First strip off "Oculus" + const char* oculus = strstr(product_name, "Oculus "); + if (oculus) + product_name = oculus + OVR_strlen("Oculus "); + // And remove spaces from the name + for (const char* s=product_name; *s != 0; s++) + { + if (*s != ' ') + product.AppendChar(*s); + } + } + } + + return (!product.IsEmpty() && !serial.IsEmpty()); +} + +static JSON* FindTaggedData(JSON* data, const char** tag_names, const char** qtags, int num_qtags) +{ + if (data == NULL || !(data->Name == "TaggedData") || data->Type != JSON_Array) + return NULL; + + JSON* tagged_item = data->GetFirstItem(); + while (tagged_item) + { + JSON* tags = tagged_item->GetItemByName("tags"); + if (tags->Type == JSON_Array && num_qtags == tags->GetArraySize()) + { // Check for a full tag match on each item + int num_matches = 0; + + for (int k=0; k<num_qtags; k++) + { + JSON* tag = tags->GetFirstItem(); + while (tag) + { + JSON* tagval = tag->GetFirstItem(); + if (tagval && tagval->Name == tag_names[k]) + { + if (tagval->Value == qtags[k]) + num_matches++; + break; + } + tag = tags->GetNextItem(tag); + } + } + + // if all tags were matched then copy the values into this Profile + if (num_matches == num_qtags) + { + JSON* vals = tagged_item->GetItemByName("vals"); + return vals; + } + } + + tagged_item = data->GetNextItem(tagged_item); + } + + return NULL; +} + +static void FilterTaggedData(JSON* data, const char* tag_name, const char* qtag, Array<JSON*>& items) +{ + if (data == NULL || !(data->Name == "TaggedData") || data->Type != JSON_Array) + return; + + JSON* tagged_item = data->GetFirstItem(); + while (tagged_item) + { + JSON* tags = tagged_item->GetItemByName("tags"); + if (tags->Type == JSON_Array) + { // Check for a tag match on the requested tag + + JSON* tag = tags->GetFirstItem(); + while (tag) + { + JSON* tagval = tag->GetFirstItem(); + if (tagval && tagval->Name == tag_name) + { + if (tagval->Value == qtag) + { // Add this item to the output list + items.PushBack(tagged_item); + } + break; + } + tag = tags->GetNextItem(tag); + } + } + + tagged_item = data->GetNextItem(tagged_item); + } +} + +//----------------------------------------------------------------------------- +// ***** ProfileManager + +ProfileManager::ProfileManager() +{ + Changed = false; +} + +ProfileManager::~ProfileManager() +{ + ClearCache(); +} + +ProfileManager* ProfileManager::Create() +{ + return new ProfileManager(); +} + +// Clear the local profile cache +void ProfileManager::ClearCache() +{ + Lock::Locker lockScope(&ProfileLock); + //ProfileCache.Clear(); + if (ProfileCache) + { + //ProfileCache->Release(); + ProfileCache = NULL; + } + Changed = false; +} + +// Returns a profile with all system default values +Profile* ProfileManager::GetDefaultProfile(const DeviceBase* device) +{ + // In the absence of any data, set some reasonable profile defaults. + // However, this is not future proof and developers should still + // provide reasonable default values for queried fields. + Profile* profile = CreateProfile(); + profile->SetValue(OVR_KEY_USER, "default"); + profile->SetValue(OVR_KEY_NAME, "Default"); + profile->SetValue(OVR_KEY_GENDER, OVR_DEFAULT_GENDER); + profile->SetFloatValue(OVR_KEY_PLAYER_HEIGHT, OVR_DEFAULT_PLAYER_HEIGHT); + profile->SetFloatValue(OVR_KEY_EYE_HEIGHT, 1.675f); + profile->SetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD); + float dist[2] = {OVR_DEFAULT_NECK_TO_EYE_HORIZONTAL, OVR_DEFAULT_NECK_TO_EYE_VERTICAL}; + profile->SetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, dist, 2); + //profile->SetFloatValue(OVR_KEY_NECK_TO_EYE_VERTICAL, 0.12f); + + // TODO: Provide device specific defaults + OVR_UNUSED(device); + + // DK1 default + //profile->SetValue("EyeCup", "A"); + + return profile; +} + +// Poplulates the local profile cache. This occurs on the first access of the profile +// data. All profile operations are performed against the local cache until the +// ProfileManager is released or goes out of scope at which time the cache is serialized +// to disk. +void ProfileManager::LoadCache(bool create) +{ + Lock::Locker lockScope(&ProfileLock); + + ClearCache(); + + String path = GetProfilePath(false); + + Ptr<JSON> root = *JSON::Load(path); + if (root == NULL) + { + path = GetBaseOVRPath(false) + "/Profiles.json"; // look for legacy profile + root = *JSON::Load(path); + + if (root == NULL) + { + if (create) + { // Generate a skeleton profile database + root = *JSON::CreateObject(); + root->AddNumberItem("Oculus Profile Version", 2.0); + root->AddItem("Users", JSON::CreateArray()); + root->AddItem("TaggedData", JSON::CreateArray()); + ProfileCache = root; + } + + return; + } + + // Verify the legacy version + JSON* version_item = root->GetFirstItem(); + if (version_item->Name == "Oculus Profile Version") + { + int major = atoi(version_item->Value.ToCStr()); + if (major != 1) + return; // don't use the file on unsupported major version number + } + else + { + return; // invalid file + } + + // Convert the legacy format to the new database format + LoadV1Profiles(root); + } + else + { + // Verify the file format and version + JSON* version_item = root->GetFirstItem(); + if (version_item->Name == "Oculus Profile Version") + { + int major = atoi(version_item->Value.ToCStr()); + if (major != 2) + return; // don't use the file on unsupported major version number + } + else + { + return; // invalid file + } + + ProfileCache = root; // store the database contents for traversal + } +} + +void ProfileManager::LoadV1Profiles(JSON* v1) +{ + JSON* item0 = v1->GetFirstItem(); + JSON* item1 = v1->GetNextItem(item0); + JSON* item2 = v1->GetNextItem(item1); + + // Create the new profile database + Ptr<JSON> root = *JSON::CreateObject(); + root->AddNumberItem("Oculus Profile Version", 2.0); + root->AddItem("Users", JSON::CreateArray()); + root->AddItem("TaggedData", JSON::CreateArray()); + ProfileCache = root; + + const char* default_dk1_user = item1->Value; + + // Read the number of profiles + int profileCount = (int)item2->dValue; + JSON* profileItem = item2; + + for (int p=0; p<profileCount; p++) + { + profileItem = root->GetNextItem(profileItem); + if (profileItem == NULL) + break; + + if (profileItem->Name == "Profile") + { + // Read the required Name field + const char* profileName; + JSON* item = profileItem->GetFirstItem(); + + if (item && (item->Name == "Name")) + { + profileName = item->Value; + } + else + { + return; // invalid field + } + + // Read the user profile fields + if (CreateUser(profileName, profileName)) + { + const char* tag_names[2] = {"User", "Product"}; + const char* tags[2]; + tags[0] = profileName; + + Ptr<Profile> user_profile = *CreateProfile(); + user_profile->SetValue(OVR_KEY_NAME, profileName); + + float neckeye[2] = { 0, 0 }; + + item = profileItem->GetNextItem(item); + while (item) + { + if (item->Type != JSON_Object) + { + if (item->Name == OVR_KEY_PLAYER_HEIGHT) + { // Add an explicit eye height + + } + if (item->Name == "NeckEyeHori") + neckeye[0] = (float)item->dValue; + else if (item->Name == "NeckEyeVert") + neckeye[1] = (float)item->dValue; + else + user_profile->SetValue(item); + } + else + { + // Add the user/device tag values + const char* device_name = item->Name.ToCStr(); + Ptr<Profile> device_profile = *CreateProfile(); + + JSON* device_item = item->GetFirstItem(); + while (device_item) + { + device_profile->SetValue(device_item); + device_item = item->GetNextItem(device_item); + } + + tags[1] = device_name; + SetTaggedProfile(tag_names, tags, 2, device_profile); + } + + item = profileItem->GetNextItem(item); + } + + // Add an explicit eye-height field + float player_height = user_profile->GetFloatValue(OVR_KEY_PLAYER_HEIGHT, + OVR_DEFAULT_PLAYER_HEIGHT); + if (player_height > 0) + { + char gender[16]; + user_profile->GetValue(OVR_KEY_GENDER, gender, 16); + + const float EYE_TO_HEADTOP_RATIO = 0.44538f; + const float MALE_AVG_HEAD_HEIGHT = 0.232f; + const float FEMALE_AVG_HEAD_HEIGHT = 0.218f; + + // compute distance from top of skull to the eye + float head_height; + if (OVR_strcmp(gender, "Female") == 0) + head_height = FEMALE_AVG_HEAD_HEIGHT; + else + head_height = MALE_AVG_HEAD_HEIGHT; + + float skull = EYE_TO_HEADTOP_RATIO * head_height; + float eye_height = player_height - skull; + + user_profile->SetFloatValue(OVR_KEY_EYE_HEIGHT, eye_height); + } + + // Convert NeckEye values to an array + if (neckeye[0] > 0 && neckeye[1] > 0) + user_profile->SetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, neckeye, 2); + + // Add the user tag values + SetTaggedProfile(tag_names, tags, 1, user_profile); + } + } + } + + // since V1 profiles were only for DK1, the assign the user to all DK1's + const char* tag_names[1] = { "Product" }; + const char* tags[1] = { "RiftDK1" }; + Ptr<Profile> product_profile = *CreateProfile(); + product_profile->SetValue("DefaultUser", default_dk1_user); + SetTaggedProfile(tag_names, tags, 1, product_profile); +} + +// Returns the number of stored profiles for this device type +int ProfileManager::GetUserCount() +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(false); + if (ProfileCache == NULL) + return 0; + } + + JSON* users = ProfileCache->GetItemByName("Users"); + if (users == NULL) + return 0; + + return users->GetItemCount(); +} + +bool ProfileManager::CreateUser(const char* user, const char* name) +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(true); + if (ProfileCache == NULL) + return false; + } + + JSON* users = ProfileCache->GetItemByName("Users"); + if (users == NULL) + { // Generate the User section + users = JSON::CreateArray(); + ProfileCache->AddItem("Users", users); +//TODO: Insert this before the TaggedData + } + + // Search for the pre-existence of this user + JSON* user_item = users->GetFirstItem(); + int index = 0; + while (user_item) + { + JSON* userid = user_item->GetItemByName("User"); + int compare = OVR_strcmp(user, userid->Value); + if (compare == 0) + { // The user already exists so simply update the fields + JSON* name_item = user_item->GetItemByName("Name"); + if (name_item && OVR_strcmp(name, name_item->Value) != 0) + { + name_item->Value = name; + Changed = true; + } + return true; + } + else if (compare < 0) + { // A new user should be placed before this item + break; + } + + user_item = users->GetNextItem(user_item); + index++; + } + + // Create and fill the user struct + JSON* new_user = JSON::CreateObject(); + new_user->AddStringItem(OVR_KEY_USER, user); + new_user->AddStringItem(OVR_KEY_NAME, name); + // user_item->AddStringItem("Password", password); + + if (user_item == NULL) + users->AddArrayElement(new_user); + else + users->InsertArrayElement(index, new_user); + + Changed = true; + return true; +} + +// Returns the user id of a specific user in the list. The returned +// memory is locally allocated and should not be stored or deleted. Returns NULL +// if the index is invalid +const char* ProfileManager::GetUser(unsigned int index) +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(false); + if (ProfileCache == NULL) + return NULL; + } + + JSON* users = ProfileCache->GetItemByName("Users"); + + if (users && index < users->GetItemCount()) + { + JSON* user_item = users->GetItemByIndex(index); + if (user_item) + { + JSON* user = user_item->GetFirstItem(); + if (user) + { + JSON* userid = user_item->GetItemByName(OVR_KEY_USER); + if (userid) + return userid->Value.ToCStr(); + } + } + } + + + return NULL; +} + +bool ProfileManager::RemoveUser(const char* user) +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(false); + if (ProfileCache == NULL) + return true; + } + + JSON* users = ProfileCache->GetItemByName("Users"); + if (users == NULL) + return true; + + // Remove this user from the User table + JSON* user_item = users->GetFirstItem(); + while (user_item) + { + JSON* userid = user_item->GetItemByName("User"); + if (OVR_strcmp(user, userid->Value) == 0) + { // Delete the user entry + user_item->RemoveNode(); + user_item->Release(); + Changed = true; + break; + } + + user_item = users->GetNextItem(user_item); + } + + // Now remove all data entries with this user tag + JSON* tagged_data = ProfileCache->GetItemByName("TaggedData"); + Array<JSON*> user_items; + FilterTaggedData(tagged_data, "User", user, user_items); + for (unsigned int i=0; i<user_items.GetSize(); i++) + { + user_items[i]->RemoveNode(); + user_items[i]->Release(); + Changed = true; + } + + return Changed; +} + +Profile* ProfileManager::CreateProfile() +{ + Profile* profile = new Profile(); + return profile; +} + +// Returns the name of the profile that is marked as the current default user. +const char* ProfileManager::GetDefaultUser(const DeviceBase* device) +{ + const char* tag_names[2] = {"Product", "Serial"}; + const char* tags[2]; + + String product; + String serial; + if (!GetDeviceTags(device, product, serial)) + return NULL; + + const char* product_str = product.IsEmpty() ? NULL : product.ToCStr(); + const char* serial_str = serial.IsEmpty() ? NULL : serial.ToCStr(); + + if (product_str && serial_str) + { + tags[0] = product_str; + tags[1] = serial_str; + // Look for a default user on this specific device + Ptr<Profile> p = *GetTaggedProfile(tag_names, tags, 2); + if (p == NULL) + { // Look for a default user on this product + p = *GetTaggedProfile(tag_names, tags, 1); + } + + if (p) + { + const char* user = p->GetValue("DefaultUser"); + if (user != NULL && user[0] != 0) + { + TempBuff = user; + return TempBuff.ToCStr(); + } + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +bool ProfileManager::SetDefaultUser(const DeviceBase* device, const char* user) +{ + const char* tag_names[2] = {"Product", "Serial"}; + const char* tags[2]; + + String product; + String serial; + if (!GetDeviceTags(device, product, serial)) + return NULL; + + const char* product_str = product.IsEmpty() ? NULL : product.ToCStr(); + const char* serial_str = serial.IsEmpty() ? NULL : serial.ToCStr(); + + if (product_str && serial_str) + { + tags[0] = product_str; + tags[1] = serial_str; + + Ptr<Profile> p = *CreateProfile(); + p->SetValue("DefaultUser", user); + return SetTaggedProfile(tag_names, tags, 2, p); + } + + return false; +} + +//----------------------------------------------------------------------------- +Profile* ProfileManager::GetTaggedProfile(const char** tag_names, const char** tags, int num_tags) +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(false); + if (ProfileCache == NULL) + return NULL; + } + + JSON* tagged_data = ProfileCache->GetItemByName("TaggedData"); + OVR_ASSERT(tagged_data); + if (tagged_data == NULL) + return NULL; + + Profile* profile = new Profile(); + + JSON* vals = FindTaggedData(tagged_data, tag_names, tags, num_tags); + if (vals) + { + JSON* item = vals->GetFirstItem(); + while (item) + { + //printf("Add %s, %s\n", item->Name.ToCStr(), item->Value.ToCStr()); + //profile->Settings.Set(item->Name, item->Value); + profile->SetValue(item); + item = vals->GetNextItem(item); + } + + return profile; + } + else + { + profile->Release(); + return NULL; + } +} + +//----------------------------------------------------------------------------- +bool ProfileManager::SetTaggedProfile(const char** tag_names, const char** tags, int num_tags, Profile* profile) +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(true); + if (ProfileCache == NULL) + return false; // TODO: Generate a new profile DB + } + + JSON* tagged_data = ProfileCache->GetItemByName("TaggedData"); + OVR_ASSERT(tagged_data); + if (tagged_data == NULL) + return false; + + // Get the cached tagged data section + JSON* vals = FindTaggedData(tagged_data, tag_names, tags, num_tags); + if (vals == NULL) + { + JSON* tagged_item = JSON::CreateObject(); + JSON* taglist = JSON::CreateArray(); + for (int i=0; i<num_tags; i++) + { + JSON* k = JSON::CreateObject(); + k->AddStringItem(tag_names[i], tags[i]); + taglist->AddArrayElement(k); + } + + vals = JSON::CreateObject(); + + tagged_item->AddItem("tags", taglist); + tagged_item->AddItem("vals", vals); + tagged_data->AddArrayElement(tagged_item); + } + + // Now add or update each profile setting in cache + for (unsigned int i=0; i<profile->Values.GetSize(); i++) + { + JSON* value = profile->Values[i]; + + bool found = false; + JSON* item = vals->GetFirstItem(); + while (item) + { + if (value->Name == item->Name) + { + // Don't allow a pre-existing type to be overridden + OVR_ASSERT(value->Type == item->Type); + + if (value->Type == item->Type) + { // Check for the same value + if (value->Type == JSON_Array) + { // Update each array item + if (item->GetArraySize() == value->GetArraySize()) + { // Update each value (assumed to be basic types and not array of objects) + JSON* value_element = value->GetFirstItem(); + JSON* item_element = item->GetFirstItem(); + while (item_element && value_element) + { + if (value_element->Type == JSON_String) + { + if (item_element->Value != value_element->Value) + { // Overwrite the changed value and mark for file update + item_element->Value = value_element->Value; + Changed = true; + } + } + else { + if (item_element->dValue != value_element->dValue) + { // Overwrite the changed value and mark for file update + item_element->dValue = value_element->dValue; + Changed = true; + } + } + + value_element = value->GetNextItem(value_element); + item_element = item->GetNextItem(item_element); + } + } + else + { // if the array size changed, simply create a new one +// TODO: Create the new array + } + } + else if (value->Type == JSON_String) + { + if (item->Value != value->Value) + { // Overwrite the changed value and mark for file update + item->Value = value->Value; + Changed = true; + } + } + else { + if (item->dValue != value->dValue) + { // Overwrite the changed value and mark for file update + item->dValue = value->dValue; + Changed = true; + } + } + } + else + { + return false; + } + + found = true; + break; + } + + item = vals->GetNextItem(item); + } + + if (!found) + { // Add the new value + if (value->Type == JSON_String) + vals->AddStringItem(value->Name, value->Value); + else if (value->Type == JSON_Bool) + vals->AddBoolItem(value->Name, (value->dValue != 0)); + else if (value->Type == JSON_Array) + vals->AddItem(value->Name, value->Copy()); + else + vals->AddNumberItem(value->Name, value->dValue); + + Changed = true; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +Profile* ProfileManager::GetProfile(const DeviceBase* device, const char* user) +{ + Lock::Locker lockScope(&ProfileLock); + + if (ProfileCache == NULL) + { // Load the cache + LoadCache(false); + if (ProfileCache == NULL) + return NULL; + } + + Profile* profile = new Profile(); + + if (device) + { + if (!profile->LoadDeviceProfile(device) && (user == NULL)) + { + profile->Release(); + return NULL; + } + } + + if (user) + { + String product; + String serial; + GetDeviceTags(device, product, serial); + + const char* product_str = product.IsEmpty() ? NULL : product.ToCStr(); + const char* serial_str = serial.IsEmpty() ? NULL : serial.ToCStr(); + + if (!profile->LoadProfile(ProfileCache.GetPtr(), user, product_str, serial_str)) + { + profile->Release(); + return NULL; + } + } + + return profile; +} + +//----------------------------------------------------------------------------- +// ***** Profile + +Profile::~Profile() +{ + ValMap.Clear(); + for (unsigned int i=0; i<Values.GetSize(); i++) + Values[i]->Release(); + + Values.Clear(); +} + +bool Profile::Close() +{ + // TODO: + return true; +} + +//----------------------------------------------------------------------------- +void Profile::CopyItems(JSON* root, String prefix) +{ + JSON* item = root->GetFirstItem(); + while (item) + { + String item_name; + if (prefix.IsEmpty()) + item_name = item->Name; + else + item_name = prefix + "." + item->Name; + + if (item->Type == JSON_Object) + { // recursively copy the children + + CopyItems(item, item_name); + } + else + { + //Settings.Set(item_name, item->Value); + SetValue(item); + } + + item = root->GetNextItem(item); + } +} + +//----------------------------------------------------------------------------- +bool Profile::LoadDeviceFile(unsigned int device_id, const char* serial) +{ + if (serial[0] == 0) + return false; + + String path = GetBaseOVRPath(false); + path += "/Devices.json"; + + // Load the device profiles + Ptr<JSON> root = *JSON::Load(path); + if (root == NULL) + return false; + + // Quick sanity check of the file type and format before we parse it + JSON* version = root->GetFirstItem(); + if (version && version->Name == "Oculus Device Profile Version") + { + int major = atoi(version->Value.ToCStr()); + if (major > MAX_DEVICE_PROFILE_MAJOR_VERSION) + return false; // don't parse the file on unsupported major version number + } + else + { + return false; + } + + + JSON* device = root->GetNextItem(version); + while (device) + { + if (device->Name == "Device") + { + JSON* product_item = device->GetItemByName("ProductID"); + JSON* serial_item = device->GetItemByName("Serial"); + if (product_item && serial_item + && (product_item->dValue == device_id) && (serial_item->Value == serial)) + { + // found the entry for this device so recursively copy all the settings to the profile + CopyItems(device, ""); + return true; + } + } + + device = root->GetNextItem(device); + } + + return false; +} + +//----------------------------------------------------------------------------- +static int BCDByte(unsigned int byte) +{ + int digit1 = (byte >> 4) & 0x000f; + int digit2 = byte & 0x000f; + int decimal = digit1 * 10 + digit2; + return decimal; +} + +//----------------------------------------------------------------------------- +bool Profile::LoadDeviceProfile(const DeviceBase* device) +{ + bool success = false; + if (device == NULL) + return false; + + SensorDevice* sensor = NULL; + + if (device->GetType() == Device_HMD) + { + // Convert the HMD device to Sensor + sensor = ((HMDDevice*)device)->GetSensor(); + device = sensor; + if (device == NULL) + return false; + } + + if (device->GetType() == Device_Sensor) + { + SensorDevice* sensor = (SensorDevice*)device; + + SensorInfo sinfo; + sensor->GetDeviceInfo(&sinfo); + + int dev_major = BCDByte((sinfo.Version >> 8) & 0x00ff); + OVR_UNUSED(dev_major); + int dev_minor = BCDByte(sinfo.Version & 0xff); + + if (dev_minor > 18) + { // If the firmware supports hardware stored profiles then grab the device profile + // from the sensor + // TBD: Implement this + } + else + { + // Grab the model and serial number from the device and use it to access the device + // profile file stored on the local machine + success = LoadDeviceFile(sinfo.ProductId, sinfo.SerialNumber); + } + } + + if (sensor) + sensor->Release(); // release the sensor handle + + return success; +} + +//----------------------------------------------------------------------------- +bool Profile::LoadUser(JSON* root, + const char* user, + const char* model_name, + const char* device_serial) +{ + if (user == NULL) + return false; + + // For legacy files, convert to old style names + //if (model_name && OVR_strcmp(model_name, "Oculus Rift DK1") == 0) + // model_name = "RiftDK1"; + + bool user_found = false; + JSON* data = root->GetItemByName("TaggedData"); + if (data) + { + const char* tag_names[3]; + const char* tags[3]; + tag_names[0] = "User"; + tags[0] = user; + int num_tags = 1; + + if (model_name) + { + tag_names[num_tags] = "Product"; + tags[num_tags] = model_name; + num_tags++; + } + + if (device_serial) + { + tag_names[num_tags] = "Serial"; + tags[num_tags] = device_serial; + num_tags++; + } + + // Retrieve all tag permutations + for (int combos=1; combos<=num_tags; combos++) + { + for (int i=0; i<(num_tags - combos + 1); i++) + { + JSON* vals = FindTaggedData(data, tag_names+i, tags+i, combos); + if (vals) + { + if (i==0) // This tag-combination contains a user match + user_found = true; + + // Add the values to the Profile. More specialized multi-tag values + // will take precedence over and overwrite generalized ones + // For example: ("Me","RiftDK1").IPD would overwrite ("Me").IPD + JSON* item = vals->GetFirstItem(); + while (item) + { + //printf("Add %s, %s\n", item->Name.ToCStr(), item->Value.ToCStr()); + //Settings.Set(item->Name, item->Value); + SetValue(item); + item = vals->GetNextItem(item); + } + } + } + } + } + + if (user_found) + SetValue(OVR_KEY_USER, user); + + return user_found; +} + + +//----------------------------------------------------------------------------- +bool Profile::LoadProfile(JSON* root, + const char* user, + const char* device_model, + const char* device_serial) +{ + if (!LoadUser(root, user, device_model, device_serial)) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +char* Profile::GetValue(const char* key, char* val, int val_length) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value)) + { + OVR_strcpy(val, val_length, value->Value.ToCStr()); + return val; + } + else + { + val[0] = 0; + return NULL; + } +} + +//----------------------------------------------------------------------------- +const char* Profile::GetValue(const char* key) +{ + // Non-reentrant query. The returned buffer can only be used until the next call + // to GetValue() + JSON* value = NULL; + if (ValMap.Get(key, &value)) + { + TempVal = value->Value; + return TempVal.ToCStr(); + } + else + { + return NULL; + } +} + +//----------------------------------------------------------------------------- +int Profile::GetNumValues(const char* key) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value)) + { + if (value->Type == JSON_Array) + return value->GetArraySize(); + else + return 1; + } + else + return 0; +} + +//----------------------------------------------------------------------------- +bool Profile::GetBoolValue(const char* key, bool default_val) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value) && value->Type == JSON_Bool) + return (value->dValue != 0); + else + return default_val; +} + +//----------------------------------------------------------------------------- +int Profile::GetIntValue(const char* key, int default_val) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value) && value->Type == JSON_Number) + return (int)(value->dValue); + else + return default_val; +} + +//----------------------------------------------------------------------------- +float Profile::GetFloatValue(const char* key, float default_val) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value) && value->Type == JSON_Number) + return (float)(value->dValue); + else + return default_val; +} + +//----------------------------------------------------------------------------- +int Profile::GetFloatValues(const char* key, float* values, int num_vals) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value) && value->Type == JSON_Array) + { + int val_count = Alg::Min(value->GetArraySize(), num_vals); + JSON* item = value->GetFirstItem(); + int count=0; + while (item && count < val_count) + { + if (item->Type == JSON_Number) + values[count] = (float)item->dValue; + else + break; + + count++; + item = value->GetNextItem(item); + } + + return count; + } + else + { + return 0; + } +} + +//----------------------------------------------------------------------------- +double Profile::GetDoubleValue(const char* key, double default_val) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value) && value->Type == JSON_Number) + return value->dValue; + else + return default_val; +} + +//----------------------------------------------------------------------------- +int Profile::GetDoubleValues(const char* key, double* values, int num_vals) const +{ + JSON* value = NULL; + if (ValMap.Get(key, &value) && value->Type == JSON_Array) + { + int val_count = Alg::Min(value->GetArraySize(), num_vals); + JSON* item = value->GetFirstItem(); + int count=0; + while (item && count < val_count) + { + if (item->Type == JSON_Number) + values[count] = item->dValue; + else + break; + + count++; + item = value->GetNextItem(item); + } + + return count; + } + else + { + return 0; + } +} + +//----------------------------------------------------------------------------- +void Profile::SetValue(JSON* val) +{ + if (val->Type == JSON_Number) + SetDoubleValue(val->Name, val->dValue); + else if (val->Type == JSON_Bool) + SetBoolValue(val->Name, (val->dValue != 0)); + else if (val->Type == JSON_String) + SetValue(val->Name, val->Value); + else if (val->Type == JSON_Array) + { + if (val == NULL) + return; + + // Create a copy of the array + JSON* value = val->Copy(); + Values.PushBack(value); + ValMap.Set(value->Name, value); + } +} + +//----------------------------------------------------------------------------- +void Profile::SetValue(const char* key, const char* val) +{ + if (key == NULL || val == NULL) + return; + + JSON* value = NULL; + if (ValMap.Get(key, &value)) + { + value->Value = val; + } + else + { + value = JSON::CreateString(val); + value->Name = key; + + Values.PushBack(value); + ValMap.Set(key, value); + } +} + +//----------------------------------------------------------------------------- +void Profile::SetBoolValue(const char* key, bool val) +{ + if (key == NULL) + return; + + JSON* value = NULL; + if (ValMap.Get(key, &value)) + { + value->dValue = val; + } + else + { + value = JSON::CreateBool(val); + value->Name = key; + + Values.PushBack(value); + ValMap.Set(key, value); + } +} + +//----------------------------------------------------------------------------- +void Profile::SetIntValue(const char* key, int val) +{ + SetDoubleValue(key, val); +} + +//----------------------------------------------------------------------------- +void Profile::SetFloatValue(const char* key, float val) +{ + SetDoubleValue(key, val); +} + +//----------------------------------------------------------------------------- +void Profile::SetFloatValues(const char* key, const float* vals, int num_vals) +{ + JSON* value = NULL; + int val_count = 0; + if (ValMap.Get(key, &value)) + { + if (value->Type == JSON_Array) + { + // truncate the existing array if fewer entries provided + int num_existing_vals = value->GetArraySize(); + for (int i=num_vals; i<num_existing_vals; i++) + value->RemoveLast(); + + JSON* item = value->GetFirstItem(); + while (item && val_count < num_vals) + { + if (item->Type == JSON_Number) + item->dValue = vals[val_count]; + + item = value->GetNextItem(item); + val_count++; + } + } + else + { + return; // Maybe we should change the data type? + } + } + else + { + value = JSON::CreateArray(); + value->Name = key; + + Values.PushBack(value); + ValMap.Set(key, value); + } + + for (; val_count < num_vals; val_count++) + value->AddArrayNumber(vals[val_count]); +} + +//----------------------------------------------------------------------------- +void Profile::SetDoubleValue(const char* key, double val) +{ + JSON* value = NULL; + if (ValMap.Get(key, &value)) + { + value->dValue = val; + } + else + { + value = JSON::CreateNumber(val); + value->Name = key; + + Values.PushBack(value); + ValMap.Set(key, value); + } +} + +//----------------------------------------------------------------------------- +void Profile::SetDoubleValues(const char* key, const double* vals, int num_vals) +{ + JSON* value = NULL; + int val_count = 0; + if (ValMap.Get(key, &value)) + { + if (value->Type == JSON_Array) + { + // truncate the existing array if fewer entries provided + int num_existing_vals = value->GetArraySize(); + for (int i=num_vals; i<num_existing_vals; i++) + value->RemoveLast(); + + JSON* item = value->GetFirstItem(); + while (item && val_count < num_vals) + { + if (item->Type == JSON_Number) + item->dValue = vals[val_count]; + + item = value->GetNextItem(item); + val_count++; + } + } + else + { + return; // Maybe we should change the data type? + } + } + else + { + value = JSON::CreateArray(); + value->Name = key; + + Values.PushBack(value); + ValMap.Set(key, value); + } + + for (; val_count < num_vals; val_count++) + value->AddArrayNumber(vals[val_count]); +} + +} // OVR diff --git a/LibOVR/Src/OVR_Profile.h b/LibOVR/Src/OVR_Profile.h new file mode 100644 index 0000000..e34820a --- /dev/null +++ b/LibOVR/Src/OVR_Profile.h @@ -0,0 +1,203 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Profile.h +Content : Structs and functions for loading and storing device profile settings +Created : February 14, 2013 +Notes : + Profiles are used to store per-user settings that can be transferred and used + across multiple applications. For example, player IPD can be configured once + and reused for a unified experience across games. Configuration and saving of profiles + can be accomplished in game via the Profile API or by the official Oculus Configuration + Utility. + +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. + +************************************************************************************/ + +#ifndef OVR_Profile_h +#define OVR_Profile_h + +#include "OVR_DeviceConstants.h" +#include "Kernel/OVR_String.h" +#include "Kernel/OVR_RefCount.h" +#include "Kernel/OVR_Array.h" +#include "Kernel/OVR_StringHash.h" + +namespace OVR { + +class Profile; +class DeviceBase; +class JSON; + +// ----------------------------------------------------------------------------- +// ***** ProfileManager + +// Profiles are interfaced through a ProfileManager object. Applications should +// create a ProfileManager each time they intend to read or write user profile data. +// The scope of the ProfileManager object defines when disk I/O is performed. Disk +// reads are performed on the first profile access and disk writes are performed when +// the ProfileManager goes out of scope. All profile interactions between these times +// are performed in local memory and are fast. A typical profile interaction might +// look like this: +// +// { +// Ptr<ProfileManager> pm = *ProfileManager::Create(); +// Ptr<Profile> profile = pm->LoadProfile(Profile_RiftDK1, +// pm->GetDefaultProfileName(Profile_RiftDK1)); +// if (profile) +// { // Retrieve the current profile settings +// } +// } // Profile will be destroyed and any disk I/O completed when going out of scope +class ProfileManager : public RefCountBase<ProfileManager> +{ +protected: + // Synchronize ProfileManager access since it may be accessed from multiple threads, + // as it's shared through DeviceManager. + Lock ProfileLock; + Ptr<JSON> ProfileCache; + bool Changed; + String TempBuff; + +public: + static ProfileManager* Create(); + + int GetUserCount(); + const char* GetUser(unsigned int index); + bool CreateUser(const char* user, const char* name); + bool RemoveUser(const char* user); + const char* GetDefaultUser(const DeviceBase* device); + bool SetDefaultUser(const DeviceBase* device, const char* user); + + virtual Profile* CreateProfile(); + Profile* GetProfile(const DeviceBase* device, const char* user); + Profile* GetDefaultProfile(const DeviceBase* device); + Profile* GetTaggedProfile(const char** key_names, const char** keys, int num_keys); + bool SetTaggedProfile(const char** key_names, const char** keys, int num_keys, Profile* profile); + + bool GetDeviceTags(const DeviceBase* device, String& product, String& serial); + +protected: + ProfileManager(); + ~ProfileManager(); + String GetProfilePath(bool create_dir); + void LoadCache(bool create); + void ClearCache(); + void LoadV1Profiles(JSON* v1); + + +}; + + +//------------------------------------------------------------------- +// ***** Profile + +// The base profile for all users. This object is not created directly. +// Instead derived device objects provide add specific device members to +// the base profile +class Profile : public RefCountBase<Profile> +{ +protected: + OVR::Hash<String, JSON*, String::HashFunctor> ValMap; + OVR::Array<JSON*> Values; + OVR::String TempVal; + +public: + ~Profile(); + + int GetNumValues(const char* key) const; + const char* GetValue(const char* key); + char* GetValue(const char* key, char* val, int val_length) const; + bool GetBoolValue(const char* key, bool default_val) const; + int GetIntValue(const char* key, int default_val) const; + float GetFloatValue(const char* key, float default_val) const; + int GetFloatValues(const char* key, float* values, int num_vals) const; + double GetDoubleValue(const char* key, double default_val) const; + int GetDoubleValues(const char* key, double* values, int num_vals) const; + + void SetValue(const char* key, const char* val); + void SetBoolValue(const char* key, bool val); + void SetIntValue(const char* key, int val); + void SetFloatValue(const char* key, float val); + void SetFloatValues(const char* key, const float* vals, int num_vals); + void SetDoubleValue(const char* key, double val); + void SetDoubleValues(const char* key, const double* vals, int num_vals); + + bool Close(); + +protected: + Profile() {}; + + + void SetValue(JSON* val); + + + static bool LoadProfile(const DeviceBase* device, + const char* user, + Profile** profile); + void CopyItems(JSON* root, String prefix); + + bool LoadDeviceFile(unsigned int device_id, const char* serial); + bool LoadDeviceProfile(const DeviceBase* device); + + bool LoadProfile(JSON* root, + const char* user, + const char* device_model, + const char* device_serial); + + bool LoadUser(JSON* root, + const char* user, + const char* device_name, + const char* device_serial); + + + friend class ProfileManager; +}; + +// # defined() check for CAPI compatibility near term that re-defines these +// for now. To be unified. +#if !defined(OVR_KEY_USER) + +#define OVR_KEY_USER "User" +#define OVR_KEY_NAME "Name" +#define OVR_KEY_GENDER "Gender" +#define OVR_KEY_PLAYER_HEIGHT "PlayerHeight" +#define OVR_KEY_EYE_HEIGHT "EyeHeight" +#define OVR_KEY_IPD "IPD" +#define OVR_KEY_NECK_TO_EYE_DISTANCE "NeckEyeDistance" +#define OVR_KEY_EYE_RELIEF_DIAL "EyeReliefDial" +#define OVR_KEY_EYE_TO_NOSE_DISTANCE "EyeToNoseDist" +#define OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE "MaxEyeToPlateDist" +#define OVR_KEY_EYE_CUP "EyeCup" +#define OVR_KEY_CUSTOM_EYE_RENDER "CustomEyeRender" + +#define OVR_DEFAULT_GENDER "Male" +#define OVR_DEFAULT_PLAYER_HEIGHT 1.778f +#define OVR_DEFAULT_EYE_HEIGHT 1.675f +#define OVR_DEFAULT_IPD 0.064f +#define OVR_DEFAULT_NECK_TO_EYE_HORIZONTAL 0.09f +#define OVR_DEFAULT_NECK_TO_EYE_VERTICAL 0.15f +#define OVR_DEFAULT_EYE_RELIEF_DIAL 3 + +#endif // OVR_KEY_USER + +String GetBaseOVRPath(bool create_dir); + +} + +#endif // OVR_Profile_h
\ No newline at end of file diff --git a/LibOVR/Src/OVR_Recording.cpp b/LibOVR/Src/OVR_Recording.cpp new file mode 100644 index 0000000..a2006c8 --- /dev/null +++ b/LibOVR/Src/OVR_Recording.cpp @@ -0,0 +1,38 @@ +/************************************************************************************ + +Filename : Recording.h +Content : Support for recording sensor + camera data +Created : May 12, 2014 +Notes : + +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 "Kernel/OVR_Math.h" +#include "Kernel/OVR_Array.h" +#include "OVR_DeviceMessages.h" +#include "OVR_Recording.h" + +namespace OVR { namespace Recording { + +// global instance that doesn't do anything +Recorder r; + +}} // OVR::Recording + diff --git a/LibOVR/Src/OVR_Recording.h b/LibOVR/Src/OVR_Recording.h new file mode 100644 index 0000000..fc83270 --- /dev/null +++ b/LibOVR/Src/OVR_Recording.h @@ -0,0 +1,83 @@ +/************************************************************************************ + +Filename : Recording.h +Content : Support for recording sensor + camera data +Created : March 14, 2014 +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_Recording_h +#define OVR_Recording_h + +namespace OVR { namespace Recording { + +enum RecordingMode +{ + RecordingOff = 0x0, + RecordForPlayback = 0x1, + RecordForLogging = 0x2 +}; + +}} // OVR::Recording + +#ifdef ENABLE_RECORDING + +#include "Recording/Recording_Recorder.h" + +#else +// If Recording is not enabled, then stub it out + +namespace OVR { + +struct PositionCalibrationReport; +namespace Vision { + class CameraIntrinsics; + class DistortionCoefficients; + class Blob; +}; + +namespace Recording { + +class Recorder +{ +public: + OVR_FORCE_INLINE void RecordCameraParams(const Vision::CameraIntrinsics&, + const Vision::DistortionCoefficients&) { } + OVR_FORCE_INLINE void RecordLedPositions(const Array<PositionCalibrationReport>&) { } + OVR_FORCE_INLINE void RecordUserParams(const Vector3f&, float) { } + OVR_FORCE_INLINE void RecordDeviceIfcVersion(UByte) { } + OVR_FORCE_INLINE void RecordMessage(const Message&) { } + OVR_FORCE_INLINE void RecordCameraFrameUsed(UInt32) { } + OVR_FORCE_INLINE void RecordVisionSuccess(UInt32) { } + template<typename T> OVR_FORCE_INLINE void LogData(const char*, const T&) { } + OVR_FORCE_INLINE void SetRecordingMode(RecordingMode) { } + OVR_FORCE_INLINE RecordingMode GetRecordingMode() { return RecordingOff; } +}; + +extern Recorder r; + +OVR_FORCE_INLINE Recorder& GetRecorder() { return r; } + +}} // namespace OVR::Recording + +#endif // ENABLE_RECORDING + +#endif // OVR_Recording_h
\ No newline at end of file diff --git a/LibOVR/Src/OVR_Sensor2Impl.cpp b/LibOVR/Src/OVR_Sensor2Impl.cpp new file mode 100644 index 0000000..95d486c --- /dev/null +++ b/LibOVR/Src/OVR_Sensor2Impl.cpp @@ -0,0 +1,1124 @@ +/************************************************************************************ + +Filename : OVR_Sensor2Impl.cpp +Content : DK2 sensor device specific implementation. +Created : January 21, 2013 +Authors : Lee Cooper + +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 "OVR_Sensor2Impl.h" +#include "OVR_SensorImpl_Common.h" +#include "OVR_Sensor2ImplUtil.h" +#include "Kernel/OVR_Alg.h" + +//extern FILE *SF_LOG_fp; + +namespace OVR { + +//------------------------------------------------------------------------------------- +// ***** Oculus Sensor2-specific packet data structures + +enum { + Sensor2_VendorId = Oculus_VendorId, + Sensor2_ProductId = 0x0021, + + Sensor2_BootLoader = 0x1001, + + Sensor2_DefaultReportRate = 1000, // Hz +}; + + +// Messages we care for +enum Tracker2MessageType +{ + Tracker2Message_None = 0, + Tracker2Message_Sensors = 11, + Tracker2Message_Unknown = 0x100, + Tracker2Message_SizeError = 0x101, +}; + + +struct Tracker2Sensors +{ + UInt16 LastCommandID; + UByte NumSamples; + UInt16 RunningSampleCount; // Named 'SampleCount' in the firmware docs. + SInt16 Temperature; + UInt32 SampleTimestamp; + TrackerSample Samples[2]; + SInt16 MagX, MagY, MagZ; + UInt16 FrameCount; + UInt32 FrameTimestamp; + UByte FrameID; + UByte CameraPattern; + UInt16 CameraFrameCount; // Named 'CameraCount' in the firmware docs. + UInt32 CameraTimestamp; + + Tracker2MessageType Decode(const UByte* buffer, int size) + { + if (size < 64) + return Tracker2Message_SizeError; + + LastCommandID = DecodeUInt16(buffer + 1); + NumSamples = buffer[3]; + RunningSampleCount = DecodeUInt16(buffer + 4); + Temperature = DecodeSInt16(buffer + 6); + SampleTimestamp = DecodeUInt32(buffer + 8); + + // Only unpack as many samples as there actually are. + UByte iterationCount = (NumSamples > 1) ? 2 : NumSamples; + + for (UByte i = 0; i < iterationCount; i++) + { + UnpackSensor(buffer + 12 + 16 * i, &Samples[i].AccelX, &Samples[i].AccelY, &Samples[i].AccelZ); + UnpackSensor(buffer + 20 + 16 * i, &Samples[i].GyroX, &Samples[i].GyroY, &Samples[i].GyroZ); + } + + MagX = DecodeSInt16(buffer + 44); + MagY = DecodeSInt16(buffer + 46); + MagZ = DecodeSInt16(buffer + 48); + + FrameCount = DecodeUInt16(buffer + 50); + + FrameTimestamp = DecodeUInt32(buffer + 52); + FrameID = buffer[56]; + CameraPattern = buffer[57]; + CameraFrameCount = DecodeUInt16(buffer + 58); + CameraTimestamp = DecodeUInt32(buffer + 60); + + return Tracker2Message_Sensors; + } +}; + +struct Tracker2Message +{ + Tracker2MessageType Type; + Tracker2Sensors Sensors; +}; + +// Sensor reports data in the following coordinate system: +// Accelerometer: 10^-4 m/s^2; X forward, Y right, Z Down. +// Gyro: 10^-4 rad/s; X positive roll right, Y positive pitch up; Z positive yaw right. + + +// We need to convert it to the following RHS coordinate system: +// X right, Y Up, Z Back (out of screen) +// +Vector3f AccelFromBodyFrameUpdate(const Tracker2Sensors& update, UByte sampleNumber) +{ + const TrackerSample& sample = update.Samples[sampleNumber]; + float ax = (float)sample.AccelX; + float ay = (float)sample.AccelY; + float az = (float)sample.AccelZ; + + return Vector3f(ax, ay, az) * 0.0001f; +} + + +Vector3f MagFromBodyFrameUpdate(const Tracker2Sensors& update) +{ + return Vector3f( (float)update.MagX, (float)update.MagY, (float)update.MagZ) * 0.0001f; +} + +Vector3f EulerFromBodyFrameUpdate(const Tracker2Sensors& update, UByte sampleNumber) +{ + const TrackerSample& sample = update.Samples[sampleNumber]; + float gx = (float)sample.GyroX; + float gy = (float)sample.GyroY; + float gz = (float)sample.GyroZ; + + return Vector3f(gx, gy, gz) * 0.0001f; +} + +bool Sensor2DeviceImpl::decodeTracker2Message(Tracker2Message* message, UByte* buffer, int size) +{ + memset(message, 0, sizeof(Tracker2Message)); + + if (size < 4) + { + message->Type = Tracker2Message_SizeError; + return false; + } + + switch (buffer[0]) + { + case Tracker2Message_Sensors: + message->Type = message->Sensors.Decode(buffer, size); + break; + + default: + message->Type = Tracker2Message_Unknown; + break; + } + + return (message->Type < Tracker2Message_Unknown) && (message->Type != Tracker2Message_None); +} + +//------------------------------------------------------------------------------------- +// ***** Sensor2Device + +Sensor2DeviceImpl::Sensor2DeviceImpl(SensorDeviceCreateDesc* createDesc) + : SensorDeviceImpl(createDesc), + LastNumSamples(0), + LastRunningSampleCount(0), + FullCameraFrameCount(0), + LastCameraTime("C"), + LastFrameTime("F"), + LastSensorTime("S"), + LastFrameTimestamp(0) +{ + // 15 samples ok in min-window for DK2 since it uses microsecond clock. + TimeFilter = SensorTimeFilter(SensorTimeFilter::Settings(15)); + + pCalibration = new SensorCalibration(this); +} + +Sensor2DeviceImpl::~Sensor2DeviceImpl() +{ + delete pCalibration; +} + +void Sensor2DeviceImpl::openDevice() +{ + + // Read the currently configured range from sensor. + SensorRangeImpl sr(SensorRange(), 0); + + if (GetInternalDevice()->GetFeatureReport(sr.Buffer, SensorRangeImpl::PacketSize)) + { + sr.Unpack(); + sr.GetSensorRange(&CurrentRange); + } + + // Read the currently configured calibration from sensor. + SensorFactoryCalibrationImpl sc; + if (GetInternalDevice()->GetFeatureReport(sc.Buffer, SensorFactoryCalibrationImpl::PacketSize)) + { + sc.Unpack(); + AccelCalibrationOffset = sc.AccelOffset; + GyroCalibrationOffset = sc.GyroOffset; + AccelCalibrationMatrix = sc.AccelMatrix; + GyroCalibrationMatrix = sc.GyroMatrix; + CalibrationTemperature = sc.Temperature; + } + + // If the sensor has "DisplayInfo" data, use HMD coordinate frame by default. + SensorDisplayInfoImpl displayInfo; + if (GetInternalDevice()->GetFeatureReport(displayInfo.Buffer, SensorDisplayInfoImpl::PacketSize)) + { + displayInfo.Unpack(); + Coordinates = (displayInfo.DistortionType & SensorDisplayInfoImpl::Mask_BaseFmt) ? + Coord_HMD : Coord_Sensor; + } + Coordinates = Coord_HMD; // TODO temporary to force it behave + + // Read/Apply sensor config. + setCoordinateFrame(Coordinates); + setReportRate(Sensor2_DefaultReportRate); + setOnboardCalibrationEnabled(false); + + // Must send DK2 keep-alive. Set Keep-alive at 10 seconds. + KeepAliveMuxReport keepAlive; + keepAlive.CommandId = 0; + keepAlive.INReport = 11; + keepAlive.Interval = 10 * 1000; + + // Device creation is done from background thread so we don't need to add this to the command queue. + KeepAliveMuxImpl keepAliveImpl(keepAlive); + GetInternalDevice()->SetFeatureReport(keepAliveImpl.Buffer, KeepAliveMuxImpl::PacketSize); + + // Read the temperature data from the device + pCalibration->Initialize(); +} + +bool Sensor2DeviceImpl::SetTrackingReport(const TrackingReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setTrackingReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setTrackingReport(const TrackingReport& data) +{ + TrackingImpl ci(data); + return GetInternalDevice()->SetFeatureReport(ci.Buffer, TrackingImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetTrackingReport(TrackingReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getTrackingReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getTrackingReport(TrackingReport* data) +{ + TrackingImpl ci; + if (GetInternalDevice()->GetFeatureReport(ci.Buffer, TrackingImpl::PacketSize)) + { + ci.Unpack(); + *data = ci.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetDisplayReport(const DisplayReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setDisplayReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setDisplayReport(const DisplayReport& data) +{ + DisplayImpl di(data); + return GetInternalDevice()->SetFeatureReport(di.Buffer, DisplayImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetDisplayReport(DisplayReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getDisplayReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getDisplayReport(DisplayReport* data) +{ + DisplayImpl di; + if (GetInternalDevice()->GetFeatureReport(di.Buffer, DisplayImpl::PacketSize)) + { + di.Unpack(); + *data = di.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetMagCalibrationReport(const MagCalibrationReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setMagCalibrationReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setMagCalibrationReport(const MagCalibrationReport& data) +{ + MagCalibrationImpl mci(data); + return GetInternalDevice()->SetFeatureReport(mci.Buffer, MagCalibrationImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetMagCalibrationReport(MagCalibrationReport* data) +{ + // direct call if we are already on the device manager thread + if (GetCurrentThreadId() == GetManagerImpl()->GetThreadId()) + { + return getMagCalibrationReport(data); + } + + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getMagCalibrationReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getMagCalibrationReport(MagCalibrationReport* data) +{ + MagCalibrationImpl mci; + if (GetInternalDevice()->GetFeatureReport(mci.Buffer, MagCalibrationImpl::PacketSize)) + { + mci.Unpack(); + *data = mci.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetPositionCalibrationReport(const PositionCalibrationReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setPositionCalibrationReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setPositionCalibrationReport(const PositionCalibrationReport& data) +{ + UByte version = GetDeviceInterfaceVersion(); + if (version < 5) + { + PositionCalibrationImpl_Pre5 pci(data); + return GetInternalDevice()->SetFeatureReport(pci.Buffer, PositionCalibrationImpl_Pre5::PacketSize); + } + + PositionCalibrationImpl pci(data); + return GetInternalDevice()->SetFeatureReport(pci.Buffer, PositionCalibrationImpl::PacketSize); +} + +bool Sensor2DeviceImpl::getPositionCalibrationReport(PositionCalibrationReport* data) +{ + UByte version = GetDeviceInterfaceVersion(); + if (version < 5) + { + PositionCalibrationImpl_Pre5 pci; + if (GetInternalDevice()->GetFeatureReport(pci.Buffer, PositionCalibrationImpl_Pre5::PacketSize)) + { + pci.Unpack(); + *data = pci.Settings; + return true; + } + + return false; + } + + PositionCalibrationImpl pci; + if (GetInternalDevice()->GetFeatureReport(pci.Buffer, PositionCalibrationImpl::PacketSize)) + { + pci.Unpack(); + *data = pci.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::GetAllPositionCalibrationReports(Array<PositionCalibrationReport>* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getAllPositionCalibrationReports, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getAllPositionCalibrationReports(Array<PositionCalibrationReport>* data) +{ + PositionCalibrationReport pc; + bool result = getPositionCalibrationReport(&pc); + if (!result) + return false; + + int positions = pc.NumPositions; + data->Clear(); + data->Resize(positions); + + for (int i = 0; i < positions; i++) + { + result = getPositionCalibrationReport(&pc); + if (!result) + return false; + OVR_ASSERT(pc.NumPositions == positions); + + (*data)[pc.PositionIndex] = pc; + // IMU should be the last one + OVR_ASSERT(pc.PositionType == (pc.PositionIndex == positions - 1) ? + PositionCalibrationReport::PositionType_IMU : PositionCalibrationReport::PositionType_LED); + } + return true; +} + +bool Sensor2DeviceImpl::SetCustomPatternReport(const CustomPatternReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setCustomPatternReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setCustomPatternReport(const CustomPatternReport& data) +{ + CustomPatternImpl cpi(data); + return GetInternalDevice()->SetFeatureReport(cpi.Buffer, CustomPatternImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetCustomPatternReport(CustomPatternReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getCustomPatternReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getCustomPatternReport(CustomPatternReport* data) +{ + CustomPatternImpl cpi; + if (GetInternalDevice()->GetFeatureReport(cpi.Buffer, CustomPatternImpl::PacketSize)) + { + cpi.Unpack(); + *data = cpi.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetManufacturingReport(const ManufacturingReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setManufacturingReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setManufacturingReport(const ManufacturingReport& data) +{ + ManufacturingImpl mi(data); + return GetInternalDevice()->SetFeatureReport(mi.Buffer, ManufacturingImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetManufacturingReport(ManufacturingReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getManufacturingReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getManufacturingReport(ManufacturingReport* data) +{ + ManufacturingImpl mi; + if (GetInternalDevice()->GetFeatureReport(mi.Buffer, ManufacturingImpl::PacketSize)) + { + mi.Unpack(); + *data = mi.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetLensDistortionReport(const LensDistortionReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setLensDistortionReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setLensDistortionReport(const LensDistortionReport& data) +{ + LensDistortionImpl ui(data); + return GetInternalDevice()->SetFeatureReport(ui.Buffer, LensDistortionImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetLensDistortionReport(LensDistortionReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getLensDistortionReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getLensDistortionReport(LensDistortionReport* data) +{ + LensDistortionImpl ui; + if (GetInternalDevice()->GetFeatureReport(ui.Buffer, LensDistortionImpl::PacketSize)) + { + ui.Unpack(); + *data = ui.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetUUIDReport(const UUIDReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setUUIDReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setUUIDReport(const UUIDReport& data) +{ + UUIDImpl ui(data); + return GetInternalDevice()->SetFeatureReport(ui.Buffer, UUIDImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetUUIDReport(UUIDReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getUUIDReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getUUIDReport(UUIDReport* data) +{ + UUIDImpl ui; + if (GetInternalDevice()->GetFeatureReport(ui.Buffer, UUIDImpl::PacketSize)) + { + ui.Unpack(); + *data = ui.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetKeepAliveMuxReport(const KeepAliveMuxReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setKeepAliveMuxReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setKeepAliveMuxReport(const KeepAliveMuxReport& data) +{ + KeepAliveMuxImpl kami(data); + return GetInternalDevice()->SetFeatureReport(kami.Buffer, KeepAliveMuxImpl::PacketSize); +} + +bool Sensor2DeviceImpl::GetKeepAliveMuxReport(KeepAliveMuxReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getKeepAliveMuxReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getKeepAliveMuxReport(KeepAliveMuxReport* data) +{ + KeepAliveMuxImpl kami; + if (GetInternalDevice()->GetFeatureReport(kami.Buffer, KeepAliveMuxImpl::PacketSize)) + { + kami.Unpack(); + *data = kami.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::SetTemperatureReport(const TemperatureReport& data) +{ + // direct call if we are already on the device manager thread + if (GetCurrentThreadId() == GetManagerImpl()->GetThreadId()) + { + return setTemperatureReport(data); + } + + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setTemperatureReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::setTemperatureReport(const TemperatureReport& data) +{ + TemperatureImpl ti(data); + return GetInternalDevice()->SetFeatureReport(ti.Buffer, TemperatureImpl::PacketSize); +} + +bool Sensor2DeviceImpl::getTemperatureReport(TemperatureReport* data) +{ + TemperatureImpl ti; + if (GetInternalDevice()->GetFeatureReport(ti.Buffer, TemperatureImpl::PacketSize)) + { + ti.Unpack(); + *data = ti.Settings; + return true; + } + + return false; +} + +bool Sensor2DeviceImpl::GetAllTemperatureReports(Array<Array<TemperatureReport> >* data) +{ + // direct call if we are already on the device manager thread + if (GetCurrentThreadId() == GetManagerImpl()->GetThreadId()) + { + return getAllTemperatureReports(data); + } + + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getAllTemperatureReports, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getAllTemperatureReports(Array<Array<TemperatureReport> >* data) +{ + TemperatureReport t; + bool result = getTemperatureReport(&t); + if (!result) + return false; + + int bins = t.NumBins, samples = t.NumSamples; + data->Clear(); + data->Resize(bins); + for (int i = 0; i < bins; i++) + (*data)[i].Resize(samples); + + for (int i = 0; i < bins; i++) + for (int j = 0; j < samples; j++) + { + result = getTemperatureReport(&t); + if (!result) + return false; + OVR_ASSERT(t.NumBins == bins && t.NumSamples == samples); + + (*data)[t.Bin][t.Sample] = t; + } + return true; +} + +bool Sensor2DeviceImpl::GetGyroOffsetReport(GyroOffsetReport* data) +{ + // direct call if we are already on the device manager thread + if (GetCurrentThreadId() == GetManagerImpl()->GetThreadId()) + { + return getGyroOffsetReport(data); + } + + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getGyroOffsetReport, &result, data)) + { + return false; + } + + return result; +} + +bool Sensor2DeviceImpl::getGyroOffsetReport(GyroOffsetReport* data) +{ + GyroOffsetImpl goi; + if (GetInternalDevice()->GetFeatureReport(goi.Buffer, GyroOffsetImpl::PacketSize)) + { + goi.Unpack(); + *data = goi.Settings; + return true; + } + + return false; +} + +void Sensor2DeviceImpl::onTrackerMessage(Tracker2Message* message) +{ + if (message->Type != Tracker2Message_Sensors) + return; + + const float sampleIntervalTimeUnit = (1.0f / 1000.f); + double scaledSampleIntervalTimeUnit = sampleIntervalTimeUnit; + Tracker2Sensors& s = message->Sensors; + + double absoluteTimeSeconds = 0.0; + + if (SequenceValid) + { + UInt32 runningSampleCountDelta; + + if (s.RunningSampleCount < LastRunningSampleCount) + { + // The running sample count on the device rolled around the 16 bit counter + // (expect to happen about once per minute), so RunningSampleCount + // needs a high word increment. + runningSampleCountDelta = ((((int)s.RunningSampleCount) + 0x10000) - (int)LastRunningSampleCount); + } + else + { + runningSampleCountDelta = (s.RunningSampleCount - LastRunningSampleCount); + } + + absoluteTimeSeconds = LastSensorTime.TimeSeconds; + scaledSampleIntervalTimeUnit = TimeFilter.ScaleTimeUnit(sampleIntervalTimeUnit); + + // If we missed a small number of samples, replicate the last sample. + if ((runningSampleCountDelta > LastNumSamples) && (runningSampleCountDelta <= 254)) + { + if (HandlerRef.HasHandlers()) + { + MessageBodyFrame sensors(this); + + sensors.AbsoluteTimeSeconds = absoluteTimeSeconds - s.NumSamples * scaledSampleIntervalTimeUnit; + sensors.TimeDelta = (float) ((runningSampleCountDelta - LastNumSamples) * scaledSampleIntervalTimeUnit); + sensors.Acceleration = LastAcceleration; + sensors.RotationRate = LastRotationRate; + sensors.MagneticField = LastMagneticField; + sensors.Temperature = LastTemperature; + + pCalibration->Apply(sensors); + HandlerRef.Call(sensors); + } + } + } + else + { + LastAcceleration = Vector3f(0); + LastRotationRate = Vector3f(0); + LastMagneticField= Vector3f(0); + LastTemperature = 0; + SequenceValid = true; + } + + LastNumSamples = s.NumSamples; + LastRunningSampleCount = s.RunningSampleCount; + + if (HandlerRef.HasHandlers()) + { + MessageBodyFrame sensors(this); + UByte iterations = s.NumSamples; + + if (s.NumSamples > 2) + { + iterations = 2; + sensors.TimeDelta = (float) ((s.NumSamples - 1) * scaledSampleIntervalTimeUnit); + } + else + { + sensors.TimeDelta = (float) scaledSampleIntervalTimeUnit; + } + + for (UByte i = 0; i < iterations; i++) + { + sensors.AbsoluteTimeSeconds = absoluteTimeSeconds - ( iterations - 1 - i ) * scaledSampleIntervalTimeUnit; + sensors.Acceleration = AccelFromBodyFrameUpdate(s, i); + sensors.RotationRate = EulerFromBodyFrameUpdate(s, i); + sensors.MagneticField= MagFromBodyFrameUpdate(s); + sensors.Temperature = s.Temperature * 0.01f; + + pCalibration->Apply(sensors); + HandlerRef.Call(sensors); + + // TimeDelta for the last two sample is always fixed. + sensors.TimeDelta = (float) scaledSampleIntervalTimeUnit; + } + + // Send pixel read only when frame timestamp changes. + if (LastFrameTimestamp != s.FrameTimestamp) + { + MessagePixelRead pixelRead(this); + // Prepare message for pixel read + pixelRead.PixelReadValue = s.FrameID; + pixelRead.RawFrameTime = s.FrameTimestamp; + pixelRead.RawSensorTime = s.SampleTimestamp; + pixelRead.SensorTimeSeconds = LastSensorTime.TimeSeconds; + pixelRead.FrameTimeSeconds = LastFrameTime.TimeSeconds; + + HandlerRef.Call(pixelRead); + LastFrameTimestamp = s.FrameTimestamp; + } + + UInt16 lowFrameCount = (UInt16) FullCameraFrameCount; + // Send message only when frame counter changes + if (lowFrameCount != s.CameraFrameCount) + { + // check for the rollover in the counter + if (s.CameraFrameCount < lowFrameCount) + FullCameraFrameCount += 0x10000; + // update the low bits + FullCameraFrameCount = (FullCameraFrameCount & ~0xFFFF) | s.CameraFrameCount; + + MessageExposureFrame vision(this); + vision.CameraPattern = s.CameraPattern; + vision.CameraFrameCount = FullCameraFrameCount; + vision.CameraTimeSeconds = LastCameraTime.TimeSeconds; + + HandlerRef.Call(vision); + } + + LastAcceleration = sensors.Acceleration; + LastRotationRate = sensors.RotationRate; + LastMagneticField= sensors.MagneticField; + LastTemperature = sensors.Temperature; + + //LastPixelRead = pixelRead.PixelReadValue; + //LastPixelReadTimeStamp = LastFrameTime; + } + else + { + if (s.NumSamples != 0) + { + UByte i = (s.NumSamples > 1) ? 1 : 0; + LastAcceleration = AccelFromBodyFrameUpdate(s, i); + LastRotationRate = EulerFromBodyFrameUpdate(s, i); + LastMagneticField = MagFromBodyFrameUpdate(s); + LastTemperature = s.Temperature * 0.01f; + } + } +} + +// Helper function to handle wrap-around of timestamps from Tracker2Message and convert them +// to system time. +// - Any timestamps that didn't increment keep their old system time. +// - This is a bit tricky since we don't know which one of timestamps has most recent time. +// - The first timestamp must be the IMU one; we assume that others can't be too much ahead of it + +void UpdateDK2Timestamps(SensorTimeFilter& tf, + SensorTimestampMapping** timestamps, UInt32 *rawValues, int count) +{ + int updateIndices[4]; + int updateCount = 0; + int i; + double now = Timer::GetSeconds(); + + OVR_ASSERT(count <= sizeof(updateIndices)/sizeof(int)); + + // Update timestamp wrapping for any values that changed. + for (i = 0; i < count; i++) + { + UInt32 lowMks = (UInt32)timestamps[i]->TimestampMks; // Low 32-bits are raw old timestamp. + + if (rawValues[i] != lowMks) + { + if (i == 0) + { + // Only check for rollover in the IMU timestamp + if (rawValues[i] < lowMks) + { + LogText("Timestamp %d rollover, was: %u, now: %u\n", i, lowMks, rawValues[i]); + timestamps[i]->TimestampMks += 0x100000000; + } + // Update the low bits + timestamps[i]->TimestampMks = (timestamps[i]->TimestampMks & 0xFFFFFFFF00000000) | rawValues[i]; + } + else + { + // Take the high bits from the main timestamp first (not a typo in the first argument!) + timestamps[i]->TimestampMks = + (timestamps[0]->TimestampMks & 0xFFFFFFFF00000000) | rawValues[i]; + // Now force it into the reasonable range around the expanded main timestamp + if (timestamps[i]->TimestampMks > timestamps[0]->TimestampMks + 0x1000000) + timestamps[i]->TimestampMks -= 0x100000000; + else if (timestamps[i]->TimestampMks + 0x100000000 < timestamps[0]->TimestampMks + 0x1000000) + timestamps[i]->TimestampMks += 0x100000000; + } + + updateIndices[updateCount] = i; + updateCount++; + } + } + + + // TBD: Simplify. Update indices should no longer be needed with new TimeFilter accepting + // previous values. + // We might want to have multi-element checking time roll-over. + + static const double mksToSec = 1.0 / 1000000.0; + + for (int i = 0; i < updateCount; i++) + { + SensorTimestampMapping& ts = *timestamps[updateIndices[i]]; + + ts.TimeSeconds = tf.SampleToSystemTime(((double)ts.TimestampMks) * mksToSec, + now, ts.TimeSeconds, ts.DebugTag); + } +} + + +void Sensor2DeviceImpl::OnInputReport(UByte* pData, UInt32 length) +{ + bool processed = false; + if (!processed) + { + Tracker2Message message; + if (decodeTracker2Message(&message, pData, length)) + { + processed = true; + + // Process microsecond timestamps from DK2 tracker. + // Mapped and raw values must correspond to one another in each array. + // IMU timestamp must be the first one! + SensorTimestampMapping* tsMaps[3] = + { + &LastSensorTime, + &LastCameraTime, + &LastFrameTime + }; + UInt32 tsRawMks[3] = + { + message.Sensors.SampleTimestamp, + message.Sensors.CameraTimestamp, + message.Sensors.FrameTimestamp + }; + // Handle wrap-around and convert samples to system time for any samples that changed. + UpdateDK2Timestamps(TimeFilter, tsMaps, tsRawMks, sizeof(tsRawMks)/sizeof(tsRawMks[0])); + + onTrackerMessage(&message); + + /* + if (SF_LOG_fp) + { + static UInt32 lastFrameTs = 0; + static UInt32 lastCameraTs = 0; + + if ((lastFrameTs != message.Sensors.FrameTimestamp) || + (lastCameraTs = message.Sensors.CameraTimestamp)) + fprintf(SF_LOG_fp, "msg cameraTs: 0x%X frameTs: 0x%X sensorTs: 0x%X\n", + message.Sensors.CameraTimestamp, message.Sensors.FrameTimestamp, + message.Sensors.SampleTimestamp); + + lastFrameTs = message.Sensors.FrameTimestamp; + lastCameraTs = message.Sensors.CameraTimestamp; + } + */ + +#if 0 + // Checks for DK2 firmware bug. + static unsigned SLastSampleTime = 0; + if ((SLastSampleTime > message.Sensors.SampleTimestamp) && message.Sensors.SampleTimestamp > 1000000 ) + { + fprintf(SF_LOG_fp, "*** Sample Timestamp Wrap! ***\n"); + OVR_ASSERT (SLastSampleTime <= message.Sensors.SampleTimestamp); + } + SLastSampleTime = message.Sensors.SampleTimestamp; + + static unsigned SLastCameraTime = 0; + if ((SLastCameraTime > message.Sensors.CameraTimestamp) && message.Sensors.CameraTimestamp > 1000000 ) + { + fprintf(SF_LOG_fp, "*** Camera Timestamp Wrap! ***\n"); + OVR_ASSERT (SLastCameraTime <= message.Sensors.CameraTimestamp); + } + SLastCameraTime = message.Sensors.CameraTimestamp; + + static unsigned SLastFrameTime = 0; + if ((SLastFrameTime > message.Sensors.FrameTimestamp) && message.Sensors.FrameTimestamp > 1000000 ) + { + fprintf(SF_LOG_fp, "*** Frame Timestamp Wrap! ***\n"); + OVR_ASSERT (SLastFrameTime <= message.Sensors.FrameTimestamp); + } + SLastFrameTime = message.Sensors.FrameTimestamp; +#endif + } + } +} + +double Sensor2DeviceImpl::OnTicks(double tickSeconds) +{ + + if (tickSeconds >= NextKeepAliveTickSeconds) + { + // Must send DK2 keep-alive. Set Keep-alive at 10 seconds. + KeepAliveMuxReport keepAlive; + keepAlive.CommandId = 0; + keepAlive.INReport = 11; + keepAlive.Interval = 10 * 1000; + + // Device creation is done from background thread so we don't need to add this to the command queue. + KeepAliveMuxImpl keepAliveImpl(keepAlive); + GetInternalDevice()->SetFeatureReport(keepAliveImpl.Buffer, KeepAliveMuxImpl::PacketSize); + + // Emit keep-alive every few seconds. + double keepAliveDelta = 3.0; // Use 3-second interval. + NextKeepAliveTickSeconds = tickSeconds + keepAliveDelta; + } + return NextKeepAliveTickSeconds - tickSeconds; +} + +/* +// TBD: don't report calibration for now, until we figure out the logic between camera and mag yaw correction +bool Sensor2DeviceImpl::IsMagCalibrated() +{ + return pCalibration->IsMagCalibrated(); +} +*/ + +} // namespace OVR diff --git a/LibOVR/Src/OVR_Sensor2Impl.h b/LibOVR/Src/OVR_Sensor2Impl.h new file mode 100644 index 0000000..4555eed --- /dev/null +++ b/LibOVR/Src/OVR_Sensor2Impl.h @@ -0,0 +1,153 @@ +/************************************************************************************ + +Filename : OVR_Sensor2Impl.h +Content : DK2 sensor device specific implementation. +Created : January 21, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_Sensor2Impl_h +#define OVR_Sensor2Impl_h + +#include "OVR_SensorImpl.h" +#include "OVR_SensorCalibration.h" + +namespace OVR { + +struct Tracker2Message; + +//------------------------------------------------------------------------------------- +// Used to convert DK2 Mks timestamps to system TimeSeconds +struct SensorTimestampMapping +{ + UInt64 TimestampMks; + double TimeSeconds; + const char* DebugTag; + + SensorTimestampMapping(const char* debugTag) + : TimestampMks(0), TimeSeconds(0.0), DebugTag(debugTag) { } +}; + +//------------------------------------------------------------------------------------- +// ***** OVR::Sensor2DeviceImpl + +// Oculus Sensor2 interface. +class Sensor2DeviceImpl : public SensorDeviceImpl +{ +public: + Sensor2DeviceImpl(SensorDeviceCreateDesc* createDesc); + ~Sensor2DeviceImpl(); + + // HIDDevice::Notifier interface. + virtual void OnInputReport(UByte* pData, UInt32 length); + virtual double OnTicks(double tickSeconds); + + // Get/set feature reports added for DK2. See 'DK2 Firmware Specification' document details. + virtual bool SetTrackingReport(const TrackingReport& data); + virtual bool GetTrackingReport(TrackingReport* data); + + virtual bool SetDisplayReport(const DisplayReport& data); + virtual bool GetDisplayReport(DisplayReport* data); + + virtual bool SetMagCalibrationReport(const MagCalibrationReport& data); + virtual bool GetMagCalibrationReport(MagCalibrationReport* data); + + virtual bool SetPositionCalibrationReport(const PositionCalibrationReport& data); + virtual bool GetAllPositionCalibrationReports(Array<PositionCalibrationReport>* data); + + virtual bool SetCustomPatternReport(const CustomPatternReport& data); + virtual bool GetCustomPatternReport(CustomPatternReport* data); + + virtual bool SetKeepAliveMuxReport(const KeepAliveMuxReport& data); + virtual bool GetKeepAliveMuxReport(KeepAliveMuxReport* data); + + virtual bool SetManufacturingReport(const ManufacturingReport& data); + virtual bool GetManufacturingReport(ManufacturingReport* data); + + virtual bool SetUUIDReport(const UUIDReport& data); + virtual bool GetUUIDReport(UUIDReport* data); + + virtual bool SetTemperatureReport(const TemperatureReport& data); + virtual bool GetAllTemperatureReports(Array<Array<TemperatureReport> >*); + + virtual bool GetGyroOffsetReport(GyroOffsetReport* data); + + virtual bool SetLensDistortionReport(const LensDistortionReport& data); + virtual bool GetLensDistortionReport(LensDistortionReport* data); + +protected: + virtual void openDevice(); + + bool decodeTracker2Message(Tracker2Message* message, UByte* buffer, int size); + + bool setTrackingReport(const TrackingReport& data); + bool getTrackingReport(TrackingReport* data); + + bool setDisplayReport(const DisplayReport& data); + bool getDisplayReport(DisplayReport* data); + + bool setMagCalibrationReport(const MagCalibrationReport& data); + bool getMagCalibrationReport(MagCalibrationReport* data); + + bool setPositionCalibrationReport(const PositionCalibrationReport& data); + bool getPositionCalibrationReport(PositionCalibrationReport* data); + bool getAllPositionCalibrationReports(Array<PositionCalibrationReport>* data); + + bool setCustomPatternReport(const CustomPatternReport& data); + bool getCustomPatternReport(CustomPatternReport* data); + + bool setKeepAliveMuxReport(const KeepAliveMuxReport& data); + bool getKeepAliveMuxReport(KeepAliveMuxReport* data); + + bool setManufacturingReport(const ManufacturingReport& data); + bool getManufacturingReport(ManufacturingReport* data); + + bool setUUIDReport(const UUIDReport& data); + bool getUUIDReport(UUIDReport* data); + + bool setTemperatureReport(const TemperatureReport& data); + bool getTemperatureReport(TemperatureReport* data); + bool getAllTemperatureReports(Array<Array<TemperatureReport> >*); + + bool getGyroOffsetReport(GyroOffsetReport* data); + + bool setLensDistortionReport(const LensDistortionReport& data); + bool getLensDistortionReport(LensDistortionReport* data); + + // Called for decoded messages + void onTrackerMessage(Tracker2Message* message); + + UByte LastNumSamples; + UInt16 LastRunningSampleCount; + UInt32 FullCameraFrameCount; + + SensorTimestampMapping LastCameraTime; + SensorTimestampMapping LastFrameTime; + SensorTimestampMapping LastSensorTime; + // Record last frame timestamp to know when to send pixelRead messages. + UInt32 LastFrameTimestamp; + + SensorCalibration *pCalibration; +}; + +} // namespace OVR + +#endif // OVR_Sensor2Impl_h diff --git a/LibOVR/Src/OVR_Sensor2ImplUtil.h b/LibOVR/Src/OVR_Sensor2ImplUtil.h new file mode 100644 index 0000000..91b2195 --- /dev/null +++ b/LibOVR/Src/OVR_Sensor2ImplUtil.h @@ -0,0 +1,676 @@ +/************************************************************************************ + +Filename : OVR_Sensor2ImplUtil.h +Content : DK2 sensor device feature report utils. +Created : January 27, 2014 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_Sensor2ImplUtil_h +#define OVR_Sensor2ImplUtil_h + +#include "OVR_Device.h" +#include "OVR_SensorImpl_Common.h" +#include "Kernel/OVR_Alg.h" + +namespace OVR { + +using namespace Alg; + +// Tracking feature report. +struct TrackingImpl +{ + enum { PacketSize = 13 }; + UByte Buffer[PacketSize]; + + TrackingReport Settings; + + TrackingImpl() + { + for (int i=0; i<PacketSize; i++) + { + Buffer[i] = 0; + } + + Buffer[0] = 12; + } + + TrackingImpl(const TrackingReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 12; + EncodeUInt16 ( Buffer+1, Settings.CommandId ); + Buffer[3] = Settings.Pattern; + Buffer[4] = UByte(Settings.Enable << 0 | + Settings.Autoincrement << 1 | + Settings.UseCarrier << 2 | + Settings.SyncInput << 3 | + Settings.VsyncLock << 4 | + Settings.CustomPattern << 5); + Buffer[5] = 0; + EncodeUInt16 ( Buffer+6, Settings.ExposureLength ); + EncodeUInt16 ( Buffer+8, Settings.FrameInterval ); + EncodeUInt16 ( Buffer+10, Settings.VsyncOffset ); + Buffer[12] = Settings.DutyCycle; + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.Pattern = Buffer[3]; + Settings.Enable = (Buffer[4] & 0x01) != 0; + Settings.Autoincrement = (Buffer[4] & 0x02) != 0; + Settings.UseCarrier = (Buffer[4] & 0x04) != 0; + Settings.SyncInput = (Buffer[4] & 0x08) != 0; + Settings.VsyncLock = (Buffer[4] & 0x10) != 0; + Settings.CustomPattern = (Buffer[4] & 0x20) != 0; + Settings.ExposureLength = DecodeUInt16(Buffer+6); + Settings.FrameInterval = DecodeUInt16(Buffer+8); + Settings.VsyncOffset = DecodeUInt16(Buffer+10); + Settings.DutyCycle = Buffer[12]; + } +}; + +// Display feature report. +struct DisplayImpl +{ + enum { PacketSize = 16 }; + UByte Buffer[PacketSize]; + + DisplayReport Settings; + + DisplayImpl() + { + for (int i=0; i<PacketSize; i++) + { + Buffer[i] = 0; + } + + Buffer[0] = 13; + } + + DisplayImpl(const DisplayReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 13; + EncodeUInt16 ( Buffer+1, Settings.CommandId ); + Buffer[3] = Settings.Brightness; + Buffer[4] = UByte( (Settings.ShutterType & 0x0F) | + (Settings.CurrentLimit & 0x03) << 4 | + (Settings.UseRolling ? 0x40 : 0) | + (Settings.ReverseRolling ? 0x80 : 0)); + Buffer[5] = UByte( (Settings.HighBrightness ? 0x01 : 0) | + (Settings.SelfRefresh ? 0x02 : 0) | + (Settings.ReadPixel ? 0x04 : 0) | + (Settings.DirectPentile ? 0x08 : 0)); + EncodeUInt16 ( Buffer+8, Settings.Persistence ); + EncodeUInt16 ( Buffer+10, Settings.LightingOffset ); + EncodeUInt16 ( Buffer+12, Settings.PixelSettle ); + EncodeUInt16 ( Buffer+14, Settings.TotalRows ); + } + + void Unpack() + { + + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.Brightness = Buffer[3]; + Settings.ShutterType = DisplayReport::ShutterTypeEnum(Buffer[4] & 0x0F); + Settings.CurrentLimit = DisplayReport::CurrentLimitEnum((Buffer[4] >> 4) & 0x02); + Settings.UseRolling = (Buffer[4] & 0x40) != 0; + Settings.ReverseRolling = (Buffer[4] & 0x80) != 0; + Settings.HighBrightness = (Buffer[5] & 0x01) != 0; + Settings.SelfRefresh = (Buffer[5] & 0x02) != 0; + Settings.ReadPixel = (Buffer[5] & 0x04) != 0; + Settings.DirectPentile = (Buffer[5] & 0x08) != 0; + Settings.Persistence = DecodeUInt16(Buffer+8); + Settings.LightingOffset = DecodeUInt16(Buffer+10); + Settings.PixelSettle = DecodeUInt16(Buffer+12); + Settings.TotalRows = DecodeUInt16(Buffer+14); + } +}; + +// MagCalibration feature report. +struct MagCalibrationImpl +{ + enum { PacketSize = 52 }; + UByte Buffer[PacketSize]; + + MagCalibrationReport Settings; + + MagCalibrationImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 14; + } + + MagCalibrationImpl(const MagCalibrationReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 14; + EncodeUInt16(Buffer+1, Settings.CommandId); + Buffer[3] = Settings.Version; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) + { + SInt32 value = SInt32(Settings.Calibration.M[i][j] * 1e4f); + EncodeSInt32(Buffer + 4 + 4 * (4 * i + j), value); + } + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.Version = Buffer[3]; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) + { + SInt32 value = DecodeSInt32(Buffer + 4 + 4 * (4 * i + j)); + Settings.Calibration.M[i][j] = (float)value * 1e-4f; + } + } +}; + +//------------------------------------------------------------------------------------- +// PositionCalibration feature report. +// - Sensor interface versions before 5 do not support Normal and Rotation. + +struct PositionCalibrationImpl +{ + enum { PacketSize = 30 }; + UByte Buffer[PacketSize]; + + PositionCalibrationReport Settings; + + PositionCalibrationImpl() + { + for (int i=0; i<PacketSize; i++) + { + Buffer[i] = 0; + } + + Buffer[0] = 15; + } + + PositionCalibrationImpl(const PositionCalibrationReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 15; + EncodeUInt16(Buffer+1, Settings.CommandId); + Buffer[3] = Settings.Version; + + Vector3d position = Settings.Position * 1e6; + EncodeSInt32(Buffer+4, (SInt32) position.x); + EncodeSInt32(Buffer+8, (SInt32) position.y); + EncodeSInt32(Buffer+12, (SInt32) position.z); + + Vector3d normal = Settings.Normal * 1e6; + EncodeSInt16(Buffer+16, (SInt16) normal.x); + EncodeSInt16(Buffer+18, (SInt16) normal.y); + EncodeSInt16(Buffer+20, (SInt16) normal.z); + + double rotation = Settings.Angle * 1e4; + EncodeSInt16(Buffer+22, (SInt16) rotation); + + EncodeUInt16(Buffer+24, Settings.PositionIndex); + EncodeUInt16(Buffer+26, Settings.NumPositions); + EncodeUInt16(Buffer+28, UInt16(Settings.PositionType)); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.Version = Buffer[3]; + + Settings.Position.x = DecodeSInt32(Buffer + 4) * 1e-6; + Settings.Position.y = DecodeSInt32(Buffer + 8) * 1e-6; + Settings.Position.z = DecodeSInt32(Buffer + 12) * 1e-6; + + Settings.Normal.x = DecodeSInt16(Buffer + 16) * 1e-6; + Settings.Normal.y = DecodeSInt16(Buffer + 18) * 1e-6; + Settings.Normal.z = DecodeSInt16(Buffer + 20) * 1e-6; + + Settings.Angle = DecodeSInt16(Buffer + 22) * 1e-4; + + Settings.PositionIndex = DecodeUInt16(Buffer + 24); + Settings.NumPositions = DecodeUInt16(Buffer + 26); + + Settings.PositionType = PositionCalibrationReport::PositionTypeEnum(DecodeUInt16(Buffer + 28)); + } +}; + +struct PositionCalibrationImpl_Pre5 +{ + enum { PacketSize = 22 }; + UByte Buffer[PacketSize]; + + PositionCalibrationReport Settings; + + PositionCalibrationImpl_Pre5() + { + for (int i=0; i<PacketSize; i++) + { + Buffer[i] = 0; + } + + Buffer[0] = 15; + } + + PositionCalibrationImpl_Pre5(const PositionCalibrationReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 15; + EncodeUInt16(Buffer+1, Settings.CommandId); + Buffer[3] = Settings.Version; + + Vector3d position = Settings.Position * 1e6; + EncodeSInt32(Buffer+4 , (SInt32) position.x); + EncodeSInt32(Buffer+8 , (SInt32) position.y); + EncodeSInt32(Buffer+12, (SInt32) position.z); + + EncodeUInt16(Buffer+16, Settings.PositionIndex); + EncodeUInt16(Buffer+18, Settings.NumPositions); + EncodeUInt16(Buffer+20, UInt16(Settings.PositionType)); + } + + void Unpack() + { + + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.Version = Buffer[3]; + + Settings.Position.x = DecodeSInt32(Buffer + 4) * 1e-6; + Settings.Position.y = DecodeSInt32(Buffer + 8) * 1e-6; + Settings.Position.z = DecodeSInt32(Buffer + 12) * 1e-6; + + Settings.PositionIndex = DecodeUInt16(Buffer + 16); + Settings.NumPositions = DecodeUInt16(Buffer + 18); + Settings.PositionType = PositionCalibrationReport::PositionTypeEnum(DecodeUInt16(Buffer + 20)); + } +}; + +// CustomPattern feature report. +struct CustomPatternImpl +{ + enum { PacketSize = 12 }; + UByte Buffer[PacketSize]; + + CustomPatternReport Settings; + + CustomPatternImpl() + { + for (int i=0; i<PacketSize; i++) + { + Buffer[i] = 0; + } + + Buffer[0] = 16; + } + + CustomPatternImpl(const CustomPatternReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 16; + EncodeUInt16(Buffer+1, Settings.CommandId); + Buffer[3] = Settings.SequenceLength; + EncodeUInt32(Buffer+4 , Settings.Sequence); + EncodeUInt16(Buffer+8 , Settings.LEDIndex); + EncodeUInt16(Buffer+10, Settings.NumLEDs); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.SequenceLength = Buffer[3]; + Settings.Sequence = DecodeUInt32(Buffer+4); + Settings.LEDIndex = DecodeUInt16(Buffer+8); + Settings.NumLEDs = DecodeUInt16(Buffer+10); + } +}; + +// Manufacturing feature report. +struct ManufacturingImpl +{ + enum { PacketSize = 16 }; + UByte Buffer[PacketSize]; + + ManufacturingReport Settings; + + ManufacturingImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 18; + } + + ManufacturingImpl(const ManufacturingReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 18; + EncodeUInt16(Buffer+1, Settings.CommandId); + Buffer[3] = Settings.NumStages; + Buffer[4] = Settings.Stage; + Buffer[5] = Settings.StageVersion; + EncodeUInt16(Buffer+6, Settings.StageLocation); + EncodeUInt32(Buffer+8, Settings.StageTime); + EncodeUInt32(Buffer+12, Settings.Result); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.NumStages = Buffer[3]; + Settings.Stage = Buffer[4]; + Settings.StageVersion = Buffer[5]; + Settings.StageLocation = DecodeUInt16(Buffer+6); + Settings.StageTime = DecodeUInt32(Buffer+8); + Settings.Result = DecodeUInt32(Buffer+12); + } +}; + +// UUID feature report. +struct UUIDImpl +{ + enum { PacketSize = 23 }; + UByte Buffer[PacketSize]; + + UUIDReport Settings; + + UUIDImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 19; + } + + UUIDImpl(const UUIDReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 19; + EncodeUInt16(Buffer+1, Settings.CommandId); + for (int i = 0; i < 20; ++i) + Buffer[3 + i] = Settings.UUIDValue[i]; + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + for (int i = 0; i < 20; ++i) + Settings.UUIDValue[i] = Buffer[3 + i]; + } +}; + +// LensDistortion feature report. +struct LensDistortionImpl +{ + enum { PacketSize = 64 }; + UByte Buffer[PacketSize]; + + LensDistortionReport Settings; + + LensDistortionImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 22; + } + + LensDistortionImpl(const LensDistortionReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 19; + EncodeUInt16(Buffer+1, Settings.CommandId); + + Buffer[3] = Settings.NumDistortions; + Buffer[4] = Settings.DistortionIndex; + Buffer[5] = Settings.Bitmask; + EncodeUInt16(Buffer+6, Settings.LensType); + EncodeUInt16(Buffer+8, Settings.Version); + EncodeUInt16(Buffer+10, Settings.EyeRelief); + + for (int i = 0; i < 11; ++i) + EncodeUInt16(Buffer+12+2*i, Settings.KCoefficients[i]); + + EncodeUInt16(Buffer+34, Settings.MaxR); + EncodeUInt16(Buffer+36, Settings.MetersPerTanAngleAtCenter); + + for (int i = 0; i < 4; ++i) + EncodeUInt16(Buffer+38+2*i, Settings.ChromaticAberration[i]); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + + Settings.NumDistortions = Buffer[3]; + Settings.DistortionIndex = Buffer[4]; + Settings.Bitmask = Buffer[5]; + Settings.LensType = DecodeUInt16(Buffer+6); + Settings.Version = DecodeUInt16(Buffer+8); + Settings.EyeRelief = DecodeUInt16(Buffer+10); + + for (int i = 0; i < 11; ++i) + Settings.KCoefficients[i] = DecodeUInt16(Buffer+12+2*i); + + Settings.MaxR = DecodeUInt16(Buffer+34); + Settings.MetersPerTanAngleAtCenter = DecodeUInt16(Buffer+36); + + for (int i = 0; i < 4; ++i) + Settings.ChromaticAberration[i] = DecodeUInt16(Buffer+38+2*i); + } +}; + +// KeepAliveMux feature report. +struct KeepAliveMuxImpl +{ + enum { PacketSize = 6 }; + UByte Buffer[PacketSize]; + + KeepAliveMuxReport Settings; + + KeepAliveMuxImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 17; + } + + KeepAliveMuxImpl(const KeepAliveMuxReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + Buffer[0] = 17; + EncodeUInt16(Buffer+1, Settings.CommandId); + Buffer[3] = Settings.INReport; + EncodeUInt16(Buffer+4, Settings.Interval); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer+1); + Settings.INReport = Buffer[3]; + Settings.Interval = DecodeUInt16(Buffer+4); + } +}; + +// Temperature feature report. +struct TemperatureImpl +{ + enum { PacketSize = 24 }; + UByte Buffer[PacketSize]; + + TemperatureReport Settings; + + TemperatureImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 20; + } + + TemperatureImpl(const TemperatureReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 20; + EncodeUInt16(Buffer + 1, Settings.CommandId); + Buffer[3] = Settings.Version; + + Buffer[4] = Settings.NumBins; + Buffer[5] = Settings.Bin; + Buffer[6] = Settings.NumSamples; + Buffer[7] = Settings.Sample; + + EncodeSInt16(Buffer + 8 , SInt16(Settings.TargetTemperature * 1e2)); + EncodeSInt16(Buffer + 10, SInt16(Settings.ActualTemperature * 1e2)); + + EncodeUInt32(Buffer + 12, Settings.Time); + + Vector3d offset = Settings.Offset * 1e4; + PackSensor(Buffer + 16, (SInt16) offset.x, (SInt16) offset.y, (SInt16) offset.z); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer + 1); + Settings.Version = Buffer[3]; + + Settings.NumBins = Buffer[4]; + Settings.Bin = Buffer[5]; + Settings.NumSamples = Buffer[6]; + Settings.Sample = Buffer[7]; + + Settings.TargetTemperature = DecodeSInt16(Buffer + 8) * 1e-2; + Settings.ActualTemperature = DecodeSInt16(Buffer + 10) * 1e-2; + + Settings.Time = DecodeUInt32(Buffer + 12); + + SInt32 x, y, z; + UnpackSensor(Buffer + 16, &x, &y, &z); + Settings.Offset = Vector3d(x, y, z) * 1e-4; + } +}; + +// GyroOffset feature report. +struct GyroOffsetImpl +{ + enum { PacketSize = 18 }; + UByte Buffer[PacketSize]; + + GyroOffsetReport Settings; + + GyroOffsetImpl() + { + memset(Buffer, 0, sizeof(Buffer)); + Buffer[0] = 21; + } + + GyroOffsetImpl(const GyroOffsetReport& settings) + : Settings(settings) + { + Pack(); + } + + void Pack() + { + + Buffer[0] = 21; + Buffer[1] = UByte(Settings.CommandId & 0xFF); + Buffer[2] = UByte(Settings.CommandId >> 8); + Buffer[3] = UByte(Settings.Version); + + Vector3d offset = Settings.Offset * 1e4; + PackSensor(Buffer + 4, (SInt32) offset.x, (SInt32) offset.y, (SInt32) offset.z); + + EncodeSInt16(Buffer + 16, SInt16(Settings.Temperature * 1e2)); + } + + void Unpack() + { + Settings.CommandId = DecodeUInt16(Buffer + 1); + Settings.Version = GyroOffsetReport::VersionEnum(Buffer[3]); + + SInt32 x, y, z; + UnpackSensor(Buffer + 4, &x, &y, &z); + Settings.Offset = Vector3d(x, y, z) * 1e-4f; + + Settings.Temperature = DecodeSInt16(Buffer + 16) * 1e-2; + } +}; + +} // namespace OVR + +#endif // OVR_Sensor2ImplUtil_h diff --git a/LibOVR/Src/OVR_SensorCalibration.cpp b/LibOVR/Src/OVR_SensorCalibration.cpp new file mode 100644 index 0000000..94fbb27 --- /dev/null +++ b/LibOVR/Src/OVR_SensorCalibration.cpp @@ -0,0 +1,354 @@ +/************************************************************************************ + +Filename : OVR_SensorCalibration.cpp +Content : Calibration data implementation for the IMU messages +Created : January 28, 2014 +Authors : Max Katsev + +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 "OVR_SensorCalibration.h" +#include "Kernel/OVR_Log.h" +#include "Kernel/OVR_Threads.h" +#include <time.h> + +namespace OVR { + +using namespace Alg; + +const UByte VERSION = 2; +const UByte MAX_COMPAT_VERSION = 15; + +SensorCalibration::SensorCalibration(SensorDevice* pSensor) + : MagCalibrated(false), GyroFilter(6000), GyroAutoTemperature(0) +{ + this->pSensor = pSensor; +}; + +void SensorCalibration::Initialize() +{ + // read factory calibration + pSensor->GetFactoryCalibration(&AccelOffset, &GyroAutoOffset, &AccelMatrix, &GyroMatrix, &GyroAutoTemperature); + + // if the headset has an autocalibrated offset, prefer it over the factory defaults + GyroOffsetReport gyroReport; + bool result = pSensor->GetGyroOffsetReport(&gyroReport); + if (result && gyroReport.Version != GyroOffsetReport::Version_NoOffset) + { + GyroAutoOffset = (Vector3f) gyroReport.Offset; + GyroAutoTemperature = (float) gyroReport.Temperature; + } + + // read the temperature tables and prepare the interpolation structures + result = pSensor->GetAllTemperatureReports(&TemperatureReports); + OVR_ASSERT(result); + for (int i = 0; i < 3; i++) + Interpolators[i].Initialize(TemperatureReports, i); + + // read the mag calibration + MagCalibrationReport report; + result = pSensor->GetMagCalibrationReport(&report); + MagCalibrated = result && report.Version > 0; + MagMatrix = report.Calibration; + if (!MagCalibrated) + { + // OVR_ASSERT(false); + LogError("Magnetometer calibration not found!\n"); + } +} + +void SensorCalibration::DebugPrintLocalTemperatureTable() +{ + LogText("TemperatureReports:\n"); + for (int i = 0; i < (int)TemperatureReports.GetSize(); i++) + { + for (int j = 0; j < (int)TemperatureReports[i].GetSize(); j++) + { + TemperatureReport& tr = TemperatureReports[i][j]; + + LogText("[%d][%d]: Version=%3d, Bin=%d/%d, " + "Sample=%d/%d, TargetTemp=%3.1lf, " + "ActualTemp=%4.1lf, " + "Offset=(%7.2lf, %7.2lf, %7.2lf), " + "Time=%d\n", i, j, tr.Version, + tr.Bin, tr.NumBins, + tr.Sample, tr.NumSamples, + tr.TargetTemperature, + tr.ActualTemperature, + tr.Offset.x, tr.Offset.y, tr.Offset.z, + tr.Time); + } + } +} + +void SensorCalibration::DebugClearHeadsetTemperatureReports() +{ + OVR_ASSERT(pSensor != NULL); + + bool result; + + Array<Array<TemperatureReport> > temperatureReports; + pSensor->GetAllTemperatureReports(&temperatureReports); + + OVR_ASSERT(temperatureReports.GetSize() > 0); + OVR_ASSERT(temperatureReports[0].GetSize() > 0); + + TemperatureReport& tr = TemperatureReports[0][0]; + + tr.ActualTemperature = 0.0; + tr.Time = 0; + tr.Version = 0; + tr.Offset.x = tr.Offset.y = tr.Offset.z = 0.0; + + for (UByte i = 0; i < tr.NumBins; i++) + { + tr.Bin = i; + + for (UByte j = 0; j < tr.NumSamples; j++) + { + tr.Sample = j; + + result = pSensor->SetTemperatureReport(tr); + OVR_ASSERT(result); + + // Need to wait for the tracker board to finish writing to eeprom. + Thread::MSleep(50); + } + } +} + +void SensorCalibration::Apply(MessageBodyFrame& msg) +{ + AutocalibrateGyro(msg); + + // compute the interpolated offset + Vector3f gyroOffset; + for (int i = 0; i < 3; i++) + gyroOffset[i] = (float) Interpolators[i].GetOffset(msg.Temperature, GyroAutoTemperature, GyroAutoOffset[i]); + + // apply calibration + msg.RotationRate = GyroMatrix.Transform(msg.RotationRate - gyroOffset); + msg.Acceleration = AccelMatrix.Transform(msg.Acceleration - AccelOffset); + if (MagCalibrated) + msg.MagneticField = MagMatrix.Transform(msg.MagneticField); +} + +void SensorCalibration::AutocalibrateGyro(MessageBodyFrame const& msg) +{ + const float alpha = 0.4f; + // 1.25f is a scaling factor related to conversion from per-axis comparison to length comparison + const float absLimit = 1.25f * 0.349066f; + const float noiseLimit = 1.25f * 0.03f; + + Vector3f gyro = msg.RotationRate; + // do a moving average to reject short term noise + Vector3f avg = (GyroFilter.IsEmpty()) ? gyro : gyro * alpha + GyroFilter.PeekBack() * (1 - alpha); + + // Make sure the absolute value is below what is likely motion + // Make sure it is close enough to the current average that it is probably noise and not motion + if (avg.Length() >= absLimit || (avg - GyroFilter.Mean()).Length() >= noiseLimit) + GyroFilter.Clear(); + GyroFilter.PushBack(avg); + + // if had a reasonable number of samples already use it for the current offset + if (GyroFilter.GetSize() > GyroFilter.GetCapacity() / 2) + { + GyroAutoOffset = GyroFilter.Mean(); + GyroAutoTemperature = msg.Temperature; + // After ~6 seconds of no motion, use the average as the new zero rate offset + if (GyroFilter.IsFull()) + StoreAutoOffset(); + } +} + +void SensorCalibration::StoreAutoOffset() +{ + const double maxDeltaT = 2.5; + const double minExtraDeltaT = 0.5; + const UInt32 minDelay = 24 * 3600; // 1 day in seconds + + // find the best bin + UPInt binIdx = 0; + for (UPInt i = 1; i < TemperatureReports.GetSize(); i++) + if (Abs(GyroAutoTemperature - TemperatureReports[i][0].TargetTemperature) < + Abs(GyroAutoTemperature - TemperatureReports[binIdx][0].TargetTemperature)) + binIdx = i; + + // find the oldest and newest samples + // NB: uninitialized samples have Time == 0, so they will get picked as the oldest + UPInt newestIdx = 0, oldestIdx = 0; + for (UPInt i = 1; i < TemperatureReports[binIdx].GetSize(); i++) + { + // if the version is newer - do nothing + if (TemperatureReports[binIdx][i].Version > VERSION) + return; + if (TemperatureReports[binIdx][i].Time > TemperatureReports[binIdx][newestIdx].Time) + newestIdx = i; + if (TemperatureReports[binIdx][i].Time < TemperatureReports[binIdx][oldestIdx].Time) + oldestIdx = i; + } + TemperatureReport& oldestReport = TemperatureReports[binIdx][oldestIdx]; + TemperatureReport& newestReport = TemperatureReports[binIdx][newestIdx]; + OVR_ASSERT((oldestReport.Sample == 0 && newestReport.Sample == 0 && newestReport.Version == 0) || + oldestReport.Sample == (newestReport.Sample + 1) % newestReport.NumSamples); + + bool writeSuccess = false; + UInt32 now = (UInt32) time(0); + if (now - newestReport.Time > minDelay) + { + // only write a new sample if the temperature is close enough + if (Abs(GyroAutoTemperature - oldestReport.TargetTemperature) < maxDeltaT) + { + oldestReport.Time = now; + oldestReport.ActualTemperature = GyroAutoTemperature; + oldestReport.Offset = (Vector3d) GyroAutoOffset; + oldestReport.Version = VERSION; + writeSuccess = pSensor->SetTemperatureReport(oldestReport); + OVR_ASSERT(writeSuccess); + } + } + else + { + // if the newest sample is too recent - _update_ it if significantly closer to the target temp + if (Abs(GyroAutoTemperature - newestReport.TargetTemperature) + minExtraDeltaT + < Abs(newestReport.ActualTemperature - newestReport.TargetTemperature)) + { + // (do not update the time!) + newestReport.ActualTemperature = GyroAutoTemperature; + newestReport.Offset = (Vector3d) GyroAutoOffset; + newestReport.Version = VERSION; + writeSuccess = pSensor->SetTemperatureReport(newestReport); + OVR_ASSERT(writeSuccess); + } + } + + // update the interpolators with the new data + // this is not particularly expensive call and would only happen rarely + // but if performance is a problem, it's possible to only recompute the data that has changed + if (writeSuccess) + for (int i = 0; i < 3; i++) + Interpolators[i].Initialize(TemperatureReports, i); +} + +const TemperatureReport& median(const Array<TemperatureReport>& temperatureReportsBin, int coord) +{ + Array<double> values; + values.Reserve(temperatureReportsBin.GetSize()); + for (unsigned i = 0; i < temperatureReportsBin.GetSize(); i++) + if (temperatureReportsBin[i].ActualTemperature != 0) + values.PushBack(temperatureReportsBin[i].Offset[coord]); + if (values.GetSize() > 0) + { + double med = Median(values); + // this is kind of a hack + for (unsigned i = 0; i < temperatureReportsBin.GetSize(); i++) + if (temperatureReportsBin[i].Offset[coord] == med) + return temperatureReportsBin[i]; + // if we haven't found the median in the original array, something is wrong + OVR_DEBUG_BREAK; + } + return temperatureReportsBin[0]; +} + +void OffsetInterpolator::Initialize(Array<Array<TemperatureReport> > const& temperatureReports, int coord) +{ + int bins = (int) temperatureReports.GetSize(); + Temperatures.Clear(); + Temperatures.Reserve(bins); + Values.Clear(); + Values.Reserve(bins); + + for (int bin = 0; bin < bins; bin++) + { + OVR_ASSERT(temperatureReports[bin].GetSize() == temperatureReports[0].GetSize()); + const TemperatureReport& report = median(temperatureReports[bin], coord); + if (report.Version > 0 && report.Version <= MAX_COMPAT_VERSION) + { + Temperatures.PushBack(report.ActualTemperature); + Values.PushBack(report.Offset[coord]); + } + } +} + +double OffsetInterpolator::GetOffset(double targetTemperature, double autoTemperature, double autoValue) +{ + const double autoRangeExtra = 1.0; + const double minInterpolationDist = 0.5; + + // difference between current and autocalibrated temperature adjusted for preference over historical data + const double adjustedDeltaT = Abs(autoTemperature - targetTemperature) - autoRangeExtra; + + int count = (int) Temperatures.GetSize(); + // handle special cases when we don't have enough data for proper interpolation + if (count == 0) + return autoValue; + if (count == 1) + { + if (adjustedDeltaT < Abs(Temperatures[0] - targetTemperature)) + return autoValue; + else + return Values[0]; + } + + // first, find the interval that contains targetTemperature + // if all points are on the same side of targetTemperature, find the adjacent interval + int l; + if (targetTemperature < Temperatures[1]) + l = 0; + else if (targetTemperature >= Temperatures[count - 2]) + l = count - 2; + else + for (l = 1; l < count - 2; l++) + if (Temperatures[l] <= targetTemperature && targetTemperature < Temperatures[l+1]) + break; + int u = l + 1; + + // extend the interval if it's too small and the interpolation is unreliable + if (Temperatures[u] - Temperatures[l] < minInterpolationDist) + { + if (l > 0 + && (u == count - 1 || Temperatures[u] - Temperatures[l - 1] < Temperatures[u + 1] - Temperatures[l])) + l--; + else if (u < count - 1) + u++; + } + + // verify correctness + OVR_ASSERT(l >= 0 && u < count); + OVR_ASSERT(l == 0 || Temperatures[l] <= targetTemperature); + OVR_ASSERT(u == count - 1 || targetTemperature < Temperatures[u]); + OVR_ASSERT((l == 0 && u == count - 1) || Temperatures[u] - Temperatures[l] > minInterpolationDist); + OVR_ASSERT(Temperatures[l] <= Temperatures[u]); + + // perform the interpolation + double slope; + if (Temperatures[u] - Temperatures[l] >= minInterpolationDist) + slope = (Values[u] - Values[l]) / (Temperatures[u] - Temperatures[l]); + else + // avoid a badly conditioned problem + slope = 0; + if (adjustedDeltaT < Abs(Temperatures[u] - targetTemperature)) + // use the autocalibrated value, if it's close + return autoValue + slope * (targetTemperature - autoTemperature); + else + return Values[u] + slope * (targetTemperature - Temperatures[u]); +} + +} // namespace OVR diff --git a/LibOVR/Src/OVR_SensorCalibration.h b/LibOVR/Src/OVR_SensorCalibration.h new file mode 100644 index 0000000..62883d2 --- /dev/null +++ b/LibOVR/Src/OVR_SensorCalibration.h @@ -0,0 +1,82 @@ +/************************************************************************************ + +Filename : OVR_SensorCalibration.h +Content : Calibration data implementation for the IMU messages +Created : January 28, 2014 +Authors : Max Katsev + +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. + +*************************************************************************************/ + +#ifndef OVR_SensorCalibration_h +#define OVR_SensorCalibration_h + +#include "OVR_Device.h" +#include "OVR_SensorFilter.h" + +namespace OVR { + +class OffsetInterpolator +{ +public: + void Initialize(Array<Array<TemperatureReport> > const& temperatureReports, int coord); + double GetOffset(double targetTemperature, double autoTemperature, double autoValue); + + Array<double> Temperatures; + Array<double> Values; +}; + +class SensorCalibration : public NewOverrideBase +{ +public: + SensorCalibration(SensorDevice* pSensor); + + // Load data from the HW and perform the necessary preprocessing + void Initialize(); + // Apply the calibration + void Apply(MessageBodyFrame& msg); + // Is mag calibration available? + bool IsMagCalibrated() { return MagCalibrated; } + +protected: + void StoreAutoOffset(); + void AutocalibrateGyro(MessageBodyFrame const& msg); + + void DebugPrintLocalTemperatureTable(); + void DebugClearHeadsetTemperatureReports(); + + SensorDevice* pSensor; + + // Factory calibration data + bool MagCalibrated; + Matrix4f AccelMatrix, GyroMatrix, MagMatrix; + Vector3f AccelOffset; + + // Temperature based data + Array<Array<TemperatureReport> > TemperatureReports; + OffsetInterpolator Interpolators[3]; + + // Autocalibration data + SensorFilterf GyroFilter; + Vector3f GyroAutoOffset; + float GyroAutoTemperature; +}; + +} // namespace OVR +#endif //OVR_SensorCalibration_h diff --git a/LibOVR/Src/OVR_SensorFilter.cpp b/LibOVR/Src/OVR_SensorFilter.cpp new file mode 100644 index 0000000..2c660ae --- /dev/null +++ b/LibOVR/Src/OVR_SensorFilter.cpp @@ -0,0 +1,99 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_SensorFilter.cpp +Content : Basic filtering of sensor this->Data +Created : March 7, 2013 +Authors : Steve LaValle, Anna Yershova, Max Katsev + +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 "OVR_SensorFilter.h" + +namespace OVR { + +template <typename T> +Vector3<T> SensorFilter<T>::Median() const +{ + Vector3<T> result; + T* slice = (T*) OVR_ALLOC(this->ElemCount * sizeof(T)); + + for (int coord = 0; coord < 3; coord++) + { + for (int i = 0; i < this->ElemCount; i++) + slice[i] = this->Data[i][coord]; + result[coord] = Alg::Median(ArrayAdaptor(slice, this->ElemCount)); + } + + OVR_FREE(slice); + return result; +} + +// Only the diagonal of the covariance matrix. +template <typename T> +Vector3<T> SensorFilter<T>::Variance() const +{ + Vector3<T> mean = this->Mean(); + Vector3<T> total; + for (int i = 0; i < this->ElemCount; i++) + { + total.x += (this->Data[i].x - mean.x) * (this->Data[i].x - mean.x); + total.y += (this->Data[i].y - mean.y) * (this->Data[i].y - mean.y); + total.z += (this->Data[i].z - mean.z) * (this->Data[i].z - mean.z); + } + return total / (float) this->ElemCount; +} + +template <typename T> +Matrix3<T> SensorFilter<T>::Covariance() const +{ + Vector3<T> mean = this->Mean(); + Matrix3<T> total; + for (int i = 0; i < this->ElemCount; i++) + { + total.M[0][0] += (this->Data[i].x - mean.x) * (this->Data[i].x - mean.x); + total.M[1][0] += (this->Data[i].y - mean.y) * (this->Data[i].x - mean.x); + total.M[2][0] += (this->Data[i].z - mean.z) * (this->Data[i].x - mean.x); + total.M[1][1] += (this->Data[i].y - mean.y) * (this->Data[i].y - mean.y); + total.M[2][1] += (this->Data[i].z - mean.z) * (this->Data[i].y - mean.y); + total.M[2][2] += (this->Data[i].z - mean.z) * (this->Data[i].z - mean.z); + } + total.M[0][1] = total.M[1][0]; + total.M[0][2] = total.M[2][0]; + total.M[1][2] = total.M[2][1]; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + total.M[i][j] /= (float) this->ElemCount; + return total; +} + +template <typename T> +Vector3<T> SensorFilter<T>::PearsonCoefficient() const +{ + Matrix3<T> cov = this->Covariance(); + Vector3<T> pearson; + pearson.x = cov.M[0][1]/(sqrt(cov.M[0][0])*sqrt(cov.M[1][1])); + pearson.y = cov.M[1][2]/(sqrt(cov.M[1][1])*sqrt(cov.M[2][2])); + pearson.z = cov.M[2][0]/(sqrt(cov.M[2][2])*sqrt(cov.M[0][0])); + + return pearson; +} + +} //namespace OVR diff --git a/LibOVR/Src/OVR_SensorFilter.h b/LibOVR/Src/OVR_SensorFilter.h new file mode 100644 index 0000000..0805d57 --- /dev/null +++ b/LibOVR/Src/OVR_SensorFilter.h @@ -0,0 +1,307 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_SensorFilter.h +Content : Basic filtering of sensor data +Created : March 7, 2013 +Authors : Steve LaValle, Anna Yershova, Max Katsev + +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. + +*************************************************************************************/ + +#ifndef OVR_SensorFilter_h +#define OVR_SensorFilter_h + +#include "Kernel/OVR_Math.h" +#include "Kernel/OVR_Deque.h" +#include "Kernel/OVR_Alg.h" + +namespace OVR { + +// A base class for filters that maintains a buffer of sensor data taken over time and implements +// various simple filters, most of which are linear functions of the data history. +// Maintains the running sum of its elements for better performance on large capacity values +template <typename T> +class SensorFilterBase : public CircularBuffer<T> +{ +protected: + T RunningTotal; // Cached sum of the elements + +public: + SensorFilterBase(int capacity = CircularBuffer<T>::DefaultCapacity) + : CircularBuffer<T>(capacity), RunningTotal() + { + this->Clear(); + }; + + // The following methods are augmented to update the cached running sum value + void PushBack(const T &e) + { + CircularBuffer<T>::PushBack(e); + RunningTotal += e; + if (this->End == 0) + { + // update the cached total to avoid error accumulation + RunningTotal = T(); + for (int i = 0; i < this->ElemCount; i++) + RunningTotal += this->Data[i]; + } + } + + void PushFront(const T &e) + { + CircularBuffer<T>::PushFront(e); + RunningTotal += e; + if (this->Beginning == 0) + { + // update the cached total to avoid error accumulation + RunningTotal = T(); + for (int i = 0; i < this->ElemCount; i++) + RunningTotal += this->Data[i]; + } + } + + T PopBack() + { + T e = CircularBuffer<T>::PopBack(); + RunningTotal -= e; + return e; + } + + T PopFront() + { + T e = CircularBuffer<T>::PopFront(); + RunningTotal -= e; + return e; + } + + void Clear() + { + CircularBuffer<T>::Clear(); + RunningTotal = T(); + } + + // Simple statistics + T Total() const + { + return RunningTotal; + } + + T Mean() const + { + return this->IsEmpty() ? T() : (Total() / (float) this->ElemCount); + } + + T MeanN(int n) const + { + OVR_ASSERT(n > 0); + OVR_ASSERT(this->Capacity >= n); + T total = T(); + for (int i = 0; i < n; i++) + { + total += this->PeekBack(i); + } + return total / n; + } + + // A popular family of smoothing filters and smoothed derivatives + + T SavitzkyGolaySmooth4() + { + OVR_ASSERT(this->Capacity >= 4); + return this->PeekBack(0)*0.7f + + this->PeekBack(1)*0.4f + + this->PeekBack(2)*0.1f - + this->PeekBack(3)*0.2f; + } + + T SavitzkyGolaySmooth8() const + { + OVR_ASSERT(this->Capacity >= 8); + return this->PeekBack(0)*0.41667f + + this->PeekBack(1)*0.33333f + + this->PeekBack(2)*0.25f + + this->PeekBack(3)*0.16667f + + this->PeekBack(4)*0.08333f - + this->PeekBack(6)*0.08333f - + this->PeekBack(7)*0.16667f; + } + + T SavitzkyGolayDerivative4() const + { + OVR_ASSERT(this->Capacity >= 4); + return this->PeekBack(0)*0.3f + + this->PeekBack(1)*0.1f - + this->PeekBack(2)*0.1f - + this->PeekBack(3)*0.3f; + } + + T SavitzkyGolayDerivative5() const + { + OVR_ASSERT(this->Capacity >= 5); + return this->PeekBack(0)*0.2f + + this->PeekBack(1)*0.1f - + this->PeekBack(3)*0.1f - + this->PeekBack(4)*0.2f; + } + + T SavitzkyGolayDerivative12() const + { + OVR_ASSERT(this->Capacity >= 12); + return this->PeekBack(0)*0.03846f + + this->PeekBack(1)*0.03147f + + this->PeekBack(2)*0.02448f + + this->PeekBack(3)*0.01748f + + this->PeekBack(4)*0.01049f + + this->PeekBack(5)*0.0035f - + this->PeekBack(6)*0.0035f - + this->PeekBack(7)*0.01049f - + this->PeekBack(8)*0.01748f - + this->PeekBack(9)*0.02448f - + this->PeekBack(10)*0.03147f - + this->PeekBack(11)*0.03846f; + } + + T SavitzkyGolayDerivativeN(int n) const + { + OVR_ASSERT(this->capacity >= n); + int m = (n-1)/2; + T result = T(); + for (int k = 1; k <= m; k++) + { + int ind1 = m - k; + int ind2 = n - m + k - 1; + result += (this->PeekBack(ind1) - this->PeekBack(ind2)) * (float) k; + } + float coef = 3.0f/(m*(m+1.0f)*(2.0f*m+1.0f)); + result = result*coef; + return result; + } + + T Median() const + { + T* copy = (T*) OVR_ALLOC(this->ElemCount * sizeof(T)); + T result = Alg::Median(ArrayAdaptor(copy)); + OVR_FREE(copy); + return result; + } +}; + +// This class maintains a buffer of sensor data taken over time and implements +// various simple filters, most of which are linear functions of the data history. +template <typename T> +class SensorFilter : public SensorFilterBase<Vector3<T> > +{ +public: + SensorFilter(int capacity = SensorFilterBase<Vector3<T> >::DefaultCapacity) : SensorFilterBase<Vector3<T> >(capacity) { }; + + // Simple statistics + Vector3<T> Median() const; + Vector3<T> Variance() const; // The diagonal of covariance matrix + Matrix3<T> Covariance() const; + Vector3<T> PearsonCoefficient() const; +}; + +typedef SensorFilter<float> SensorFilterf; +typedef SensorFilter<double> SensorFilterd; + +// This filter operates on the values that are measured in the body frame and rotate with the device +class SensorFilterBodyFrame : public SensorFilterBase<Vector3d> +{ +private: + // low pass filter gain + double gain; + // sum of squared norms of the values + double runningTotalLengthSq; + // cumulative rotation quaternion + Quatd Q; + // current low pass filter output + Vector3d output; + + // make private so it isn't used by accident + // in addition to the normal SensorFilterBase::PushBack, keeps track of running sum of LengthSq + // for the purpose of variance computations + void PushBack(const Vector3d &e) + { + runningTotalLengthSq += this->IsFull() ? (e.LengthSq() - this->PeekFront().LengthSq()) : e.LengthSq(); + SensorFilterBase<Vector3d>::PushBack(e); + if (this->End == 0) + { + // update the cached total to avoid error accumulation + runningTotalLengthSq = 0; + for (int i = 0; i < this->ElemCount; i++) + runningTotalLengthSq += this->Data[i].LengthSq(); + } + } + +public: + SensorFilterBodyFrame(int capacity = SensorFilterBase<Vector3d>::DefaultCapacity) + : SensorFilterBase<Vector3d>(capacity), gain(2.5), + runningTotalLengthSq(0), Q(), output() { }; + + // return the scalar variance of the filter values (rotated to be in the same frame) + double Variance() const + { + return this->IsEmpty() ? 0 : (runningTotalLengthSq / this->ElemCount - this->Mean().LengthSq()); + } + + // return the scalar standard deviation of the filter values (rotated to be in the same frame) + double StdDev() const + { + return sqrt(Variance()); + } + + // confidence value based on the stddev of the data (between 0.0 and 1.0, more is better) + double Confidence() const + { + return Alg::Clamp(0.48 - 0.1 * log(StdDev()), 0.0, 1.0) * this->ElemCount / this->Capacity; + } + + // add a new element to the filter + // takes rotation increment since the last update + // in order to rotate the previous value to the current body frame + void Update(Vector3d value, double deltaT, Quatd deltaQ = Quatd()) + { + if (this->IsEmpty()) + { + output = value; + } + else + { + // rotate by deltaQ + output = deltaQ.Inverted().Rotate(output); + // apply low-pass filter + output += (value - output) * gain * deltaT; + } + + // put the value into the fixed frame for the stddev computation + Q = Q * deltaQ; + PushBack(Q.Rotate(output)); + } + + // returns the filter average in the current body frame + Vector3d GetFilteredValue() const + { + return Q.Inverted().Rotate(this->Mean()); + } +}; + +} //namespace OVR + +#endif // OVR_SensorFilter_h diff --git a/LibOVR/Src/OVR_SensorFusion.cpp b/LibOVR/Src/OVR_SensorFusion.cpp new file mode 100644 index 0000000..5c21178 --- /dev/null +++ b/LibOVR/Src/OVR_SensorFusion.cpp @@ -0,0 +1,904 @@ +/************************************************************************************ + +Filename : OVR_SensorFusion.cpp +Content : Methods that determine head orientation from sensor data over time +Created : October 9, 2012 +Authors : Michael Antonov, Steve LaValle, Dov Katz, Max Katsev, Dan Gierl + +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 "OVR_SensorFusion.h" +#include "Kernel/OVR_Log.h" +#include "Kernel/OVR_System.h" +#include "OVR_JSON.h" +#include "OVR_Profile.h" +#include "OVR_Stereo.h" +#include "OVR_Recording.h" + +// Temporary for debugging +bool Global_Flag_1 = true; + +//Convenient global variables to temporarily extract this data. +float TPH_CameraPoseOrientationWxyz[4]; +double TPH_CameraPoseConfidence; +double TPH_CameraPoseConfidenceThresholdOverrideIfNonZero = 0; +bool TPH_IsPositionTracked = false; + + +namespace OVR { + +const Transformd DefaultWorldFromCamera(Quatd(), Vector3d(0, 0, -1)); + +//------------------------------------------------------------------------------------- +// ***** Sensor Fusion + +SensorFusion::SensorFusion(SensorDevice* sensor) + : ExposureRecordHistory(100), LastMessageExposureFrame(NULL), + FocusDirection(Vector3d(0, 0, 0)), FocusFOV(0.0), + FAccelInImuFrame(1000), FAccelInCameraFrame(1000), FAngV(20), + EnableGravity(true), EnableYawCorrection(true), MagCalibrated(false), + EnableCameraTiltCorrection(true), + MotionTrackingEnabled(true), VisionPositionEnabled(true), + CenterPupilDepth(0.0) +{ + pHandler = new BodyFrameHandler(this); + + // And the clock is running... + LogText("*** SensorFusion Startup: TimeSeconds = %f\n", Timer::GetSeconds()); + + if (sensor) + AttachToSensor(sensor); + + Reset(); +} + +SensorFusion::~SensorFusion() +{ + delete(pHandler); +} + +bool SensorFusion::AttachToSensor(SensorDevice* sensor) +{ + pHandler->RemoveHandlerFromDevices(); + Reset(); + + if (sensor != NULL) + { + // cache mag calibration state + MagCalibrated = sensor->IsMagCalibrated(); + + // Load IMU position + Array<PositionCalibrationReport> reports; + bool result = sensor->GetAllPositionCalibrationReports(&reports); + if (result) + { + PositionCalibrationReport imu = reports[reports.GetSize() - 1]; + OVR_ASSERT(imu.PositionType == PositionCalibrationReport::PositionType_IMU); + // convert from vision to the world frame + // TBD convert rotation as necessary? + imu.Position.x *= -1.0; + imu.Position.z *= -1.0; + + ImuFromScreen = Transformd(Quatd(imu.Normal, imu.Angle), imu.Position).Inverted(); + + Recording::GetRecorder().RecordLedPositions(reports); + Recording::GetRecorder().RecordDeviceIfcVersion(sensor->GetDeviceInterfaceVersion()); + } + + // Repopulate CPFOrigin + SetCenterPupilDepth(CenterPupilDepth); + + // Subscribe to sensor updates + sensor->AddMessageHandler(pHandler); + + // Initialize the sensor state + // TBD: This is a hack to avoid a race condition if sensor status is checked immediately + // after sensor creation but before any data has flowed through. We should probably + // not depend strictly on data flow to determine capabilities like orientation and position + // tracking, or else use some sort of synchronous method to wait for data + LocklessState init; + init.StatusFlags = Status_OrientationTracked; + UpdatedState.SetState(init); + } + + return true; +} + +// Resets the current orientation +void SensorFusion::Reset() +{ + Lock::Locker lockScope(pHandler->GetHandlerLock()); + + UpdatedState.SetState(LocklessState()); + WorldFromImu = PoseState<double>(); + WorldFromImu.Pose = ImuFromCpf.Inverted(); // place CPF at the origin, not the IMU + CameraFromImu = PoseState<double>(); + VisionError = PoseState<double>(); + WorldFromCamera = DefaultWorldFromCamera; + WorldFromCameraConfidence = -1; + + ExposureRecordHistory.Clear(); + NextExposureRecord = ExposureRecord(); + LastMessageExposureFrame = MessageExposureFrame(NULL); + LastVisionAbsoluteTime = 0; + Stage = 0; + + MagRefs.Clear(); + MagRefIdx = -1; + MagCorrectionIntegralTerm = Quatd(); + AccelOffset = Vector3d(); + + FAccelInCameraFrame.Clear(); + FAccelInImuFrame.Clear(); + FAngV.Clear(); + + setNeckPivotFromPose ( WorldFromImu.Pose ); +} + +//------------------------------------------------------------------------------------- +// Vision & message processing + +void SensorFusion::OnVisionFailure() +{ + // do nothing + Recording::GetRecorder().RecordVisionSuccess(false); +} + +void SensorFusion::OnVisionPreviousFrame(const Transform<double>& cameraFromImu) +{ + // simply save the observation for use in the next OnVisionSuccess call; + // this should not have unintended side-effects for position filtering, + // since the vision time is not updated and the system keeps thinking we don't have vision yet + CameraFromImu.Pose = cameraFromImu; +} + +void SensorFusion::OnVisionSuccess(const Transform<double>& cameraFromImu, UInt32 exposureCounter) +{ + Lock::Locker lockScope(pHandler->GetHandlerLock()); + + Recording::GetRecorder().RecordVisionSuccess(true); + + LastVisionAbsoluteTime = GetTime(); + + // ********* LastVisionExposureRecord ********* + + // Skip old data and use the record that matches the exposure counter + while (!ExposureRecordHistory.IsEmpty() && + (ExposureRecordHistory.PeekFront().ExposureCounter <= exposureCounter)) + { + LastVisionExposureRecord = ExposureRecordHistory.PopFront(); + } + + // Use current values if we don't have historical data + // Right now, this will happen if we get first frame after prediction failure, + // and this exposure wasn't in the buffer. (TBD: Unlikely.. unless IMU message wasn't sent?) + if (LastVisionExposureRecord.ExposureCounter != exposureCounter) + LastVisionExposureRecord = ExposureRecord(exposureCounter, GetTime(), WorldFromImu, PoseState<double>()); + + // ********* CameraFromImu ********* + + // This is stored in the camera frame, so need to be careful when combining with the IMU data, + // which is in the world frame + + Transformd cameraFromImuPrev = CameraFromImu.Pose; + CameraFromImu.Pose = cameraFromImu; + CameraFromImu.TimeInSeconds = LastVisionExposureRecord.ExposureTime; + + // Check LastVisionExposureRecord.Delta.TimeInSeconds to avoid divide by zero, which we could (rarely) + // get if we didn't have exposures delta for history (skipped exposure counters + // due to video mode change that stalls USB, etc). + if (LastVisionExposureRecord.ImuOnlyDelta.TimeInSeconds > 0.001) + { + Vector3d visionVelocityInImuFrame = (cameraFromImu.Translation - cameraFromImuPrev.Translation) / + LastVisionExposureRecord.ImuOnlyDelta.TimeInSeconds; + // Use the accel data to estimate the velocity at the exposure time + // (as opposed to the average velocity between exposures) + Vector3d imuVelocityInWorldFrame = LastVisionExposureRecord.ImuOnlyDelta.LinearVelocity - + LastVisionExposureRecord.ImuOnlyDelta.Pose.Translation / LastVisionExposureRecord.ImuOnlyDelta.TimeInSeconds; + CameraFromImu.LinearVelocity = visionVelocityInImuFrame + + WorldFromCamera.Inverted().Rotate(imuVelocityInWorldFrame); + } + else + { + CameraFromImu.LinearVelocity = Vector3d(0,0,0); + } +} + +PoseStated SensorFusion::computeVisionError() +{ + PoseStated worldFromImuVision = WorldFromCamera * CameraFromImu; + // Here we need to compute the difference between worldFromImuVision and WorldFromImu. + // However this difference needs to be represented in the World frame, not IMU frame. + // Therefore the computation is different from simply worldFromImuVision.Pose * WorldFromImu.Pose.Inverted(). + PoseStated err; + err.Pose.Rotation = worldFromImuVision.Pose.Rotation * + LastVisionExposureRecord.WorldFromImu.Pose.Rotation.Inverted(); + err.Pose.Translation = worldFromImuVision.Pose.Translation - + LastVisionExposureRecord.WorldFromImu.Pose.Translation; + err.LinearVelocity = worldFromImuVision.LinearVelocity - + LastVisionExposureRecord.WorldFromImu.LinearVelocity; + return err; +} + +Transform<double> SensorFusion::GetVisionPrediction(UInt32 exposureCounter) +{ + Lock::Locker lockScope(pHandler->GetHandlerLock()); + + // Combine the small deltas together + // Should only be one iteration, unless we are skipping camera frames + ExposureRecord record; + PoseState<double> delta = PoseState<double>(); + + while (!ExposureRecordHistory.IsEmpty() && + (ExposureRecordHistory.PeekFront().ExposureCounter <= exposureCounter)) + { + record = ExposureRecordHistory.PopFront(); + delta.AdvanceByDelta(record.ImuOnlyDelta); + } + // Put the combine exposure record back in the history, for use in HandleVisionSuccess(...) + record.ImuOnlyDelta = delta; + ExposureRecordHistory.PushFront(record); + + Transformd result; + if (record.VisionTrackingAvailable) + { + // if the tracking is working normally, use the change in the main state (SFusion output) + // to compute the prediction + result = CameraFromImu.Pose * + LastVisionExposureRecord.WorldFromImu.Pose.Inverted() * record.WorldFromImu.Pose; + } + else + { + // if we just acquired vision, the main state probably doesn't have the correct position, + // so can't rely on it for prediction + + // solution: use the accelerometer and vision velocity to propagate the previous sample forward + // (don't forget to transform IMU to the camera frame) + result = Transform<double> + ( + CameraFromImu.Pose.Rotation * delta.Pose.Rotation, + CameraFromImu.Pose.Translation + CameraFromImu.LinearVelocity * delta.TimeInSeconds + + WorldFromCamera.Inverted().Rotate(delta.Pose.Translation) + ); + } + + return result; +} + +void SensorFusion::handleMessage(const MessageBodyFrame& msg) +{ + if (msg.Type != Message_BodyFrame || !IsMotionTrackingEnabled()) + return; + + // Put the sensor readings into convenient local variables + Vector3d gyro(msg.RotationRate); + Vector3d accel(msg.Acceleration); + Vector3d mag(msg.MagneticField); + double DeltaT = msg.TimeDelta; + + // Keep track of time + WorldFromImu.TimeInSeconds = msg.AbsoluteTimeSeconds; + // We got an update in the last 60ms and the data is not very old + bool visionIsRecent = (GetTime() - LastVisionAbsoluteTime < 0.07) && (GetVisionLatency() < 0.25); + Stage++; + + // Insert current sensor data into filter history + FAngV.PushBack(gyro); + FAccelInImuFrame.Update(accel, DeltaT, Quatd(gyro, gyro.Length() * DeltaT)); + + // Process raw inputs + // in the future the gravity offset can be calibrated using vision feedback + Vector3d accelInWorldFrame = WorldFromImu.Pose.Rotate(accel) - Vector3d(0, 9.8, 0); + + // Recompute the vision error to account for all the corrections and the new data + VisionError = computeVisionError(); + + // Update headset orientation + WorldFromImu.StoreAndIntegrateGyro(gyro, DeltaT); + // Tilt correction based on accelerometer + if (EnableGravity) + applyTiltCorrection(DeltaT); + // Yaw correction based on camera + if (EnableYawCorrection && visionIsRecent) + applyVisionYawCorrection(DeltaT); + // Yaw correction based on magnetometer + if (EnableYawCorrection && MagCalibrated) // MagCalibrated is always false for DK2 for now + applyMagYawCorrection(mag, DeltaT); + // Focus Correction + if ((FocusDirection.x != 0.0f || FocusDirection.z != 0.0f) && FocusFOV < Mathf::Pi) + applyFocusCorrection(DeltaT); + + // Update camera orientation + if (EnableCameraTiltCorrection && visionIsRecent) + applyCameraTiltCorrection(accel, DeltaT); + + // The quaternion magnitude may slowly drift due to numerical error, + // so it is periodically normalized. + if ((Stage & 0xFF) == 0) + { + WorldFromImu.Pose.Rotation.Normalize(); + WorldFromCamera.Rotation.Normalize(); + } + + // Update headset position + if (VisionPositionEnabled && visionIsRecent) + { + // Integrate UMI and velocity here up to a fixed amount of time after vision. + WorldFromImu.StoreAndIntegrateAccelerometer(accelInWorldFrame + AccelOffset, DeltaT); + // Position correction based on camera + applyPositionCorrection(DeltaT); + // Compute where the neck pivot would be. + setNeckPivotFromPose(WorldFromImu.Pose); + } + else + { + // Fall back onto internal head model + // Use the last-known neck pivot position to figure out the expected IMU position. + // (should be the opposite of SensorFusion::setNeckPivotFromPose) + WorldFromNeck.Rotation = WorldFromImu.Pose.Rotation; + WorldFromImu.Pose = WorldFromNeck * (ImuFromCpf * CpfFromNeck).Inverted(); + + // We can't trust velocity past this point. + WorldFromImu.LinearVelocity = Vector3d(0,0,0); + WorldFromImu.LinearAcceleration = accelInWorldFrame; + } + + // Compute the angular acceleration + WorldFromImu.AngularAcceleration = (FAngV.GetSize() >= 12 && DeltaT > 0) ? + (FAngV.SavitzkyGolayDerivative12() / DeltaT) : Vector3d(); + + // Update the dead reckoning state used for incremental vision tracking + NextExposureRecord.ImuOnlyDelta.StoreAndIntegrateGyro(gyro, DeltaT); + NextExposureRecord.ImuOnlyDelta.StoreAndIntegrateAccelerometer(accelInWorldFrame, DeltaT); + NextExposureRecord.ImuOnlyDelta.TimeInSeconds = WorldFromImu.TimeInSeconds - LastMessageExposureFrame.CameraTimeSeconds; + NextExposureRecord.VisionTrackingAvailable &= (VisionPositionEnabled && visionIsRecent); + + Recording::GetRecorder().LogData("sfTimeSeconds", WorldFromImu.TimeInSeconds); + Recording::GetRecorder().LogData("sfStage", (double)Stage); + Recording::GetRecorder().LogData("sfPose", WorldFromImu.Pose); + //Recorder::LogData("sfAngAcc", State.AngularAcceleration); + //Recorder::LogData("sfAngVel", State.AngularVelocity); + //Recorder::LogData("sfLinAcc", State.LinearAcceleration); + //Recorder::LogData("sfLinVel", State.LinearVelocity); + + // Store the lockless state. + LocklessState lstate; + lstate.StatusFlags = Status_OrientationTracked; + if (VisionPositionEnabled) + lstate.StatusFlags |= Status_PositionConnected; + if (VisionPositionEnabled && visionIsRecent) + lstate.StatusFlags |= Status_PositionTracked; + + //A convenient means to temporarily extract this flag + TPH_IsPositionTracked = visionIsRecent; + + lstate.State = WorldFromImu; + lstate.Temperature = msg.Temperature; + lstate.Magnetometer = mag; + UpdatedState.SetState(lstate); +} + +void SensorFusion::handleExposure(const MessageExposureFrame& msg) +{ + NextExposureRecord.ExposureCounter = msg.CameraFrameCount; + NextExposureRecord.ExposureTime = msg.CameraTimeSeconds; + NextExposureRecord.WorldFromImu = WorldFromImu; + NextExposureRecord.ImuOnlyDelta.TimeInSeconds = msg.CameraTimeSeconds - LastMessageExposureFrame.CameraTimeSeconds; + ExposureRecordHistory.PushBack(NextExposureRecord); + + // Every new exposure starts from zero + NextExposureRecord = ExposureRecord(); + LastMessageExposureFrame = msg; +} + +// If you have a known-good pose, this sets the neck pivot position. +void SensorFusion::setNeckPivotFromPose(Transformd const &worldFromImu) +{ + WorldFromNeck = worldFromImu * ImuFromCpf * CpfFromNeck; +} + +// These two functions need to be moved into Quat class +// Compute a rotation required to transform "from" into "to". +Quatd vectorAlignmentRotation(const Vector3d &from, const Vector3d &to) +{ + Vector3d axis = from.Cross(to); + if (axis.LengthSq() == 0) + // this handles both collinear and zero-length input cases + return Quatd(); + double angle = from.Angle(to); + return Quatd(axis, angle); +} + +// Compute the part of the quaternion that rotates around Y axis +Quatd extractYawRotation(const Quatd &error) +{ + if (error.y == 0) + return Quatd(); + double phi = atan2(error.w, error.y); + double alpha = Mathd::Pi - 2 * phi; + return Quatd(Axis_Y, alpha); +} + +void SensorFusion::applyPositionCorrection(double deltaT) +{ + // Each component of gainPos is equivalent to a Kalman gain of (sigma_process / sigma_observation) + const Vector3d gainPos = Vector3d(10, 10, 8); + const Vector3d gainVel = gainPos.EntrywiseMultiply(gainPos) * 0.5; + const Vector3d gainAccel = gainVel * 0.5; + const double snapThreshold = 0.1; // Large value (previously 0.01, which caused frequent jumping) + + Vector3d correctionPos, correctionVel; + if (VisionError.Pose.Translation.LengthSq() > (snapThreshold * snapThreshold) || + !(UpdatedState.GetState().StatusFlags & Status_PositionTracked)) + { + // high error or just reacquired position from vision - apply full correction + + // to know where we are right now, take the vision pose (which is slightly old) + // and update it using the imu data since then + PoseStated worldFromImuVision = WorldFromCamera * CameraFromImu; + for (unsigned int i = 0; i < ExposureRecordHistory.GetSize(); i++) + worldFromImuVision.AdvanceByDelta(ExposureRecordHistory.PeekFront(i).ImuOnlyDelta); + worldFromImuVision.AdvanceByDelta(NextExposureRecord.ImuOnlyDelta); + + correctionPos = worldFromImuVision.Pose.Translation - WorldFromImu.Pose.Translation; + correctionVel = worldFromImuVision.LinearVelocity - WorldFromImu.LinearVelocity; + AccelOffset = Vector3d(); + } + else + { + correctionPos = VisionError.Pose.Translation.EntrywiseMultiply(gainPos) * deltaT; + correctionVel = VisionError.Pose.Translation.EntrywiseMultiply(gainVel) * deltaT; + AccelOffset += VisionError.Pose.Translation.EntrywiseMultiply(gainAccel) * deltaT; + } + + WorldFromImu.Pose.Translation += correctionPos; + WorldFromImu.LinearVelocity += correctionVel; + + // Update the exposure records so that we don't apply the same correction twice + LastVisionExposureRecord.WorldFromImu.Pose.Translation += correctionPos; + LastVisionExposureRecord.WorldFromImu.LinearVelocity += correctionVel; + for (unsigned int i = 0; i < ExposureRecordHistory.GetSize(); i++) + { + PoseStated& state = ExposureRecordHistory.PeekBack(i).WorldFromImu; + state.Pose.Translation += correctionPos; + state.LinearVelocity += correctionVel; + } +} + +void SensorFusion::applyVisionYawCorrection(double deltaT) +{ + const double gain = 0.25; + const double snapThreshold = 0.1; + + Quatd yawError = extractYawRotation(VisionError.Pose.Rotation); + + Quatd correction; + if (Alg::Abs(yawError.w) < cos(snapThreshold / 2)) // angle(yawError) > snapThreshold + // high error, jump to the vision position + correction = yawError; + else + correction = yawError.Nlerp(Quatd(), gain * deltaT); + + WorldFromImu.Pose.Rotation = correction * WorldFromImu.Pose.Rotation; + + // Update the exposure records so that we don't apply the same correction twice + LastVisionExposureRecord.WorldFromImu.Pose.Rotation = correction * LastVisionExposureRecord.WorldFromImu.Pose.Rotation; + for (unsigned int i = 0; i < ExposureRecordHistory.GetSize(); i++) + { + PoseStated& state = ExposureRecordHistory.PeekBack(i).WorldFromImu; + state.Pose.Rotation = correction * state.Pose.Rotation; + } +} + +void SensorFusion::applyMagYawCorrection(Vector3d mag, double deltaT) +{ + const double minMagLengthSq = Mathd::Tolerance; // need to use a real value to discard very weak fields + const double maxMagRefDist = 0.1; + const double maxTiltError = 0.05; + const double proportionalGain = 0.01; + const double integralGain = 0.0005; + + Vector3d magInWorldFrame = WorldFromImu.Pose.Rotate(mag); + // verify that the horizontal component is sufficient + if (magInWorldFrame.x * magInWorldFrame.x + magInWorldFrame.z * magInWorldFrame.z < minMagLengthSq) + return; + magInWorldFrame.Normalize(); + + // Delete a bad point + if (MagRefIdx >= 0 && MagRefs[MagRefIdx].Score < 0) + { + MagRefs.RemoveAtUnordered(MagRefIdx); + MagRefIdx = -1; + } + + // Update the reference point if needed + if (MagRefIdx < 0 || mag.Distance(MagRefs[MagRefIdx].InImuFrame) > maxMagRefDist) + { + // Find a new one + MagRefIdx = -1; + double bestDist = maxMagRefDist; + for (unsigned int i = 0; i < MagRefs.GetSize(); i++) + { + double dist = mag.Distance(MagRefs[i].InImuFrame); + if (bestDist > dist) + { + bestDist = dist; + MagRefIdx = i; + } + } + + // Create one if needed + if (MagRefIdx < 0 && MagRefs.GetSize() < MagMaxReferences) + { + MagRefs.PushBack(MagReferencePoint(mag, WorldFromImu.Pose, 1000)); + } + } + + if (MagRefIdx >= 0) + { + Vector3d magRefInWorldFrame = MagRefs[MagRefIdx].WorldFromImu.Rotate(MagRefs[MagRefIdx].InImuFrame); + magRefInWorldFrame.Normalize(); + + // If the vertical angle is wrong, decrease the score and do nothing + if (Alg::Abs(magRefInWorldFrame.y - magInWorldFrame.y) > maxTiltError) + { + MagRefs[MagRefIdx].Score -= 1; + return; + } + + MagRefs[MagRefIdx].Score += 2; +#if 0 + // this doesn't seem to work properly, need to investigate + Quatd error = vectorAlignmentRotation(magW, magRefW); + Quatd yawError = extractYawRotation(error); +#else + // Correction is computed in the horizontal plane + magInWorldFrame.y = magRefInWorldFrame.y = 0; + Quatd yawError = vectorAlignmentRotation(magInWorldFrame, magRefInWorldFrame); +#endif + Quatd correction = yawError.Nlerp(Quatd(), proportionalGain * deltaT) * + MagCorrectionIntegralTerm.Nlerp(Quatd(), deltaT); + MagCorrectionIntegralTerm = MagCorrectionIntegralTerm * yawError.Nlerp(Quatd(), integralGain * deltaT); + + WorldFromImu.Pose.Rotation = correction * WorldFromImu.Pose.Rotation; + } +} + +void SensorFusion::applyTiltCorrection(double deltaT) +{ + const double gain = 0.25; + const double snapThreshold = 0.1; + const Vector3d up(0, 1, 0); + + Vector3d accelInWorldFrame = WorldFromImu.Pose.Rotate(FAccelInImuFrame.GetFilteredValue()); + Quatd error = vectorAlignmentRotation(accelInWorldFrame, up); + + Quatd correction; + if (FAccelInImuFrame.GetSize() == 1 || + ((Alg::Abs(error.w) < cos(snapThreshold / 2) && FAccelInImuFrame.Confidence() > 0.75))) + // full correction for start-up + // or large error with high confidence + correction = error; + else if (FAccelInImuFrame.Confidence() > 0.5) + correction = error.Nlerp(Quatd(), gain * deltaT); + else + // accelerometer is unreliable due to movement + return; + + WorldFromImu.Pose.Rotation = correction * WorldFromImu.Pose.Rotation; +} + +void SensorFusion::applyCameraTiltCorrection(Vector3d accel, double deltaT) +{ + const double snapThreshold = 0.02; // in radians + const double maxCameraPositionOffset = 0.2; + const Vector3d up(0, 1, 0), forward(0, 0, -1); + + // for startup use filtered value instead of instantaneous for stability + if (FAccelInCameraFrame.IsEmpty()) + accel = FAccelInImuFrame.GetFilteredValue(); + + Transformd cameraFromImu = WorldFromCamera.Inverted() * VisionError.Pose * WorldFromImu.Pose; + // this is what the hypothetical camera-mounted accelerometer would show + Vector3d accelInCameraFrame = cameraFromImu.Rotate(accel); + FAccelInCameraFrame.Update(accelInCameraFrame, deltaT); + Vector3d cameraAccelInWorldFrame = WorldFromCamera.Rotate(FAccelInCameraFrame.GetFilteredValue()); + + Quatd error1 = vectorAlignmentRotation(cameraAccelInWorldFrame, up); + // cancel out yaw rotation + Vector3d forwardCamera = (error1 * WorldFromCamera.Rotation).Rotate(forward); + forwardCamera.y = 0; + Quatd error2 = vectorAlignmentRotation(forwardCamera, forward); + // combined error + Quatd error = error2 * error1; + + double confidence = FAccelInCameraFrame.Confidence(); + // penalize the confidence if looking away from the camera + // TODO: smooth fall-off + if (CameraFromImu.Pose.Rotate(forward).Angle(forward) > 1) + confidence *= 0.5; + + //Convenient global variable to temporarily extract this data. + TPH_CameraPoseConfidence = confidence; + //Allow override of confidence threshold + double confidenceThreshold = 0.75f; + if (TPH_CameraPoseConfidenceThresholdOverrideIfNonZero) + { + confidenceThreshold = TPH_CameraPoseConfidenceThresholdOverrideIfNonZero; + } + + Quatd correction; + if (FAccelInCameraFrame.GetSize() == 1 || + confidence > WorldFromCameraConfidence + 0.2 || + // disabled due to false positives when moving side to side +// (Alg::Abs(error.w) < cos(5 * snapThreshold / 2) && confidence > 0.55) || + (Alg::Abs(error.w) < cos(snapThreshold / 2) && confidence > confidenceThreshold)) + { + // large error with high confidence + correction = error; + // update the confidence level + WorldFromCameraConfidence = confidence; + } + else + { + // accelerometer is unreliable due to movement + return; + } + + Transformd newWorldFromCamera(correction * WorldFromCamera.Rotation, Vector3d()); + + // compute a camera position change that together with the camera rotation would result in zero player movement + newWorldFromCamera.Translation += (WorldFromCamera * CameraFromImu.Pose).Translation - + (newWorldFromCamera * CameraFromImu.Pose).Translation; + // if the new position is too far, reset to default + // (can't hide the rotation, might as well use it to reset the position) + if (newWorldFromCamera.Translation.DistanceSq(DefaultWorldFromCamera.Translation) > maxCameraPositionOffset * maxCameraPositionOffset) + newWorldFromCamera.Translation = DefaultWorldFromCamera.Translation; + + WorldFromCamera = newWorldFromCamera; + + //Convenient global variable to temporarily extract this data. + TPH_CameraPoseOrientationWxyz[0] = (float) WorldFromCamera.Rotation.w; + TPH_CameraPoseOrientationWxyz[1] = (float) WorldFromCamera.Rotation.x; + TPH_CameraPoseOrientationWxyz[2] = (float) WorldFromCamera.Rotation.y; + TPH_CameraPoseOrientationWxyz[3] = (float) WorldFromCamera.Rotation.z; +} + +void SensorFusion::applyFocusCorrection(double deltaT) +{ + Vector3d up = Vector3d(0, 1, 0); + double gain = 0.01; + Vector3d currentDir = WorldFromImu.Pose.Rotate(Vector3d(0, 0, 1)); + + Vector3d focusYawComponent = FocusDirection.ProjectToPlane(up); + Vector3d currentYawComponent = currentDir.ProjectToPlane(up); + + double angle = focusYawComponent.Angle(currentYawComponent); + + if( angle > FocusFOV ) + { + Quatd yawError; + if ( FocusFOV != 0.0f) + { + Vector3d lFocus = Quatd(up, -FocusFOV).Rotate(focusYawComponent); + Vector3d rFocus = Quatd(up, FocusFOV).Rotate(focusYawComponent); + double lAngle = lFocus.Angle(currentYawComponent); + double rAngle = rFocus.Angle(currentYawComponent); + if(lAngle < rAngle) + { + yawError = vectorAlignmentRotation(currentDir, lFocus); + } + else + { + yawError = vectorAlignmentRotation(currentDir, rFocus); + } + } + else + { + yawError = vectorAlignmentRotation(currentYawComponent, focusYawComponent); + } + + Quatd correction = yawError.Nlerp(Quatd(), gain * deltaT); + WorldFromImu.Pose.Rotation = correction * WorldFromImu.Pose.Rotation; + } +} + +//------------------------------------------------------------------------------------ +// Focus filter setting functions + +void SensorFusion::SetFocusDirection() +{ + SetFocusDirection(WorldFromImu.Pose.Rotate(Vector3d(0.0, 0.0, 1.0))); +} + +void SensorFusion::SetFocusDirection(Vector3d direction) +{ + FocusDirection = direction; +} + +void SensorFusion::SetFocusFOV(double fov) +{ + OVR_ASSERT(fov >= 0.0); + FocusFOV = fov; +} + +void SensorFusion::ClearFocus() +{ + FocusDirection = Vector3d(0.0, 0.0, 0.0); + FocusFOV = 0.0f; +} + +//------------------------------------------------------------------------------------- +// Head model functions. + +// Sets up head-and-neck model and device-to-pupil dimensions from the user's profile. +void SensorFusion::SetUserHeadDimensions(Profile const &profile, HmdRenderInfo const &hmdRenderInfo) +{ + float neckeye[2]; + int count = profile.GetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, neckeye, 2); + // Make sure these are vaguely sensible values. + if (count == 2) + { + OVR_ASSERT ( ( neckeye[0] > 0.05f ) && ( neckeye[0] < 0.5f ) ); + OVR_ASSERT ( ( neckeye[1] > 0.05f ) && ( neckeye[1] < 0.5f ) ); + SetHeadModel ( Vector3f ( 0.0, neckeye[1], -neckeye[0] ) ); + } + + // Find the distance from the center of the screen to the "center eye" + // This center eye is used by systems like rendering & audio to represent the player, + // and they will handle the offsets needed from there to each actual eye. + + // HACK HACK HACK + // We know for DK1 the screen->lens surface distance is roughly 0.049f, and that the faceplate->lens is 0.02357f. + // We're going to assume(!!!!) that all HMDs have the same screen->faceplate distance. + // Crystal Cove was measured to be roughly 0.025 screen->faceplate which agrees with this assumption. + // TODO: do this properly! Update: Measured this at 0.02733 with a CC prototype, CES era (PT7), on 2/19/14 -Steve + float screenCenterToMidplate = 0.02733f; + float centerEyeRelief = hmdRenderInfo.GetEyeCenter().ReliefInMeters; + float centerPupilDepth = screenCenterToMidplate + hmdRenderInfo.LensSurfaceToMidplateInMeters + centerEyeRelief; + SetCenterPupilDepth ( centerPupilDepth ); + + Recording::GetRecorder().RecordUserParams(GetHeadModel(), GetCenterPupilDepth()); +} + +Vector3f SensorFusion::GetHeadModel() const +{ + return (Vector3f)CpfFromNeck.Inverted().Translation; +} + +void SensorFusion::SetHeadModel(const Vector3f &headModel, bool resetNeckPivot /*= true*/ ) +{ + Lock::Locker lockScope(pHandler->GetHandlerLock()); + // The head model should look something like (0, 0.12, -0.12), so + // these asserts are to try to prevent sign problems, as + // they can be subtle but nauseating! + OVR_ASSERT ( headModel.y > 0.0f ); + OVR_ASSERT ( headModel.z < 0.0f ); + CpfFromNeck = Transformd(Quatd(), (Vector3d)headModel).Inverted(); + if ( resetNeckPivot ) + { + setNeckPivotFromPose ( WorldFromImu.Pose ); + } +} + +float SensorFusion::GetCenterPupilDepth() const +{ + return CenterPupilDepth; +} + +void SensorFusion::SetCenterPupilDepth(float centerPupilDepth) +{ + CenterPupilDepth = centerPupilDepth; + + Transformd screenFromCpf(Quatd(), Vector3d(0, 0, centerPupilDepth)); + ImuFromCpf = ImuFromScreen * screenFromCpf; + + setNeckPivotFromPose ( WorldFromImu.Pose ); +} + +//------------------------------------------------------------------------------------- + +// This is a "perceptually tuned predictive filter", which means that it is optimized +// for improvements in the VR experience, rather than pure error. In particular, +// jitter is more perceptible at lower speeds whereas latency is more perceptible +// after a high-speed motion. Therefore, the prediction interval is dynamically +// adjusted based on speed. Significant more research is needed to further improve +// this family of filters. +static Transform<double> calcPredictedPose(const PoseState<double>& poseState, double predictionDt) +{ + Transform<double> pose = poseState.Pose; + const double linearCoef = 1.0; + Vector3d angularVelocity = poseState.AngularVelocity; + double angularSpeed = angularVelocity.Length(); + + // This could be tuned so that linear and angular are combined with different coefficients + double speed = angularSpeed + linearCoef * poseState.LinearVelocity.Length(); + + const double slope = 0.2; // The rate at which the dynamic prediction interval varies + double candidateDt = slope * speed; // TODO: Replace with smoothstep function + + double dynamicDt = predictionDt; + + // Choose the candidate if it is shorter, to improve stability + if (candidateDt < predictionDt) + dynamicDt = candidateDt; + + if (angularSpeed > 0.001) + pose.Rotation = pose.Rotation * Quatd(angularVelocity, angularSpeed * dynamicDt); + + pose.Translation += poseState.LinearVelocity * dynamicDt; + + return pose; +} + + +Transformf SensorFusion::GetPoseAtTime(double absoluteTime) const +{ + SensorState ss = GetSensorStateAtTime ( absoluteTime ); + return ss.Predicted.Pose; +} + + +SensorState SensorFusion::GetSensorStateAtTime(double absoluteTime) const +{ + const LocklessState lstate = UpdatedState.GetState(); + // Delta time from the last available data + const double pdt = absoluteTime - lstate.State.TimeInSeconds; + + SensorState ss; + ss.Recorded = PoseStatef(lstate.State); + ss.Temperature = lstate.Temperature; + ss.Magnetometer = Vector3f(lstate.Magnetometer); + ss.StatusFlags = lstate.StatusFlags; + + ss.Predicted = ss.Recorded; + ss.Predicted.TimeInSeconds = absoluteTime; + + // Do prediction logic and ImuFromCpf transformation + ss.Recorded.Pose = Transformf(lstate.State.Pose * ImuFromCpf); + ss.Predicted.Pose = Transformf(calcPredictedPose(lstate.State, pdt) * ImuFromCpf); + return ss; +} + +unsigned SensorFusion::GetStatus() const +{ + return UpdatedState.GetState().StatusFlags; +} + +//------------------------------------------------------------------------------------- + +void SensorFusion::OnMessage(const MessageBodyFrame& msg) +{ + OVR_ASSERT(!IsAttachedToSensor()); + handleMessage(msg); +} + +//------------------------------------------------------------------------------------- + +void SensorFusion::BodyFrameHandler::OnMessage(const Message& msg) +{ + Recording::GetRecorder().RecordMessage(msg); + if (msg.Type == Message_BodyFrame) + pFusion->handleMessage(static_cast<const MessageBodyFrame&>(msg)); + if (msg.Type == Message_ExposureFrame) + pFusion->handleExposure(static_cast<const MessageExposureFrame&>(msg)); +} + +} // namespace OVR diff --git a/LibOVR/Src/OVR_SensorFusion.h b/LibOVR/Src/OVR_SensorFusion.h new file mode 100644 index 0000000..2a17920 --- /dev/null +++ b/LibOVR/Src/OVR_SensorFusion.h @@ -0,0 +1,582 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_SensorFusion.h +Content : Methods that determine head orientation from sensor data over time +Created : October 9, 2012 +Authors : Michael Antonov, Steve LaValle, Dov Katz, Max Katsev, Dan Gierl + +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. + +*************************************************************************************/ + +#ifndef OVR_SensorFusion_h +#define OVR_SensorFusion_h + +#include "OVR_Device.h" +#include "OVR_SensorFilter.h" +#include "Kernel/OVR_Timer.h" +#include "Kernel/OVR_Threads.h" +#include "Kernel/OVR_Lockless.h" + +// CAPI forward declarations. +typedef struct ovrPoseStatef_ ovrPoseStatef; +typedef struct ovrSensorState_ ovrSensorState; + +namespace OVR { + +struct HmdRenderInfo; + +//------------------------------------------------------------------------------------- +// ***** Sensor State + +// These values are reported as compatible with C API. + + +// PoseState describes the complete pose, or a rigid body configuration, at a +// point in time, including first and second derivatives. It is used to specify +// instantaneous location and movement of the headset. +// SensorState is returned as a part of the sensor state. + +template<class T> +class PoseState +{ +public: + typedef typename CompatibleTypes<Transform<T> >::Type CompatibleType; + + PoseState() : TimeInSeconds(0.0) { } + // float <-> double conversion constructor. + explicit PoseState(const PoseState<typename Math<T>::OtherFloatType> &src) + : Pose(src.Pose), + AngularVelocity(src.AngularVelocity), LinearVelocity(src.LinearVelocity), + AngularAcceleration(src.AngularAcceleration), LinearAcceleration(src.LinearAcceleration), + TimeInSeconds(src.TimeInSeconds) + { } + + // C-interop support: PoseStatef <-> ovrPoseStatef + PoseState(const typename CompatibleTypes<PoseState<T> >::Type& src) + : Pose(src.Pose), + AngularVelocity(src.AngularVelocity), LinearVelocity(src.LinearVelocity), + AngularAcceleration(src.AngularAcceleration), LinearAcceleration(src.LinearAcceleration), + TimeInSeconds(src.TimeInSeconds) + { } + + operator typename CompatibleTypes<PoseState<T> >::Type () const + { + typename CompatibleTypes<PoseState<T> >::Type result; + result.Pose = Pose; + result.AngularVelocity = AngularVelocity; + result.LinearVelocity = LinearVelocity; + result.AngularAcceleration = AngularAcceleration; + result.LinearAcceleration = LinearAcceleration; + result.TimeInSeconds = TimeInSeconds; + return result; + } + + + Transform<T> Pose; + Vector3<T> AngularVelocity; + Vector3<T> LinearVelocity; + Vector3<T> AngularAcceleration; + Vector3<T> LinearAcceleration; + // Absolute time of this state sample; always a double measured in seconds. + double TimeInSeconds; + + + // ***** Helpers for Pose integration + + // Stores and integrates gyro angular velocity reading for a given time step. + void StoreAndIntegrateGyro(Vector3d angVel, double dt); + // Stores and integrates position/velocity from accelerometer reading for a given time step. + void StoreAndIntegrateAccelerometer(Vector3d linearAccel, double dt); + + // Performs integration of state by adding next state delta to it + // to produce a combined state change + void AdvanceByDelta(const PoseState<T>& delta); +}; + + + +// External API returns pose as float, but uses doubles internally for quaternion precision. +typedef PoseState<float> PoseStatef; +typedef PoseState<double> PoseStated; + + +//------------------------------------------------------------------------------------- +// ***** Sensor State + + +// Bit flags describing the current status of sensor tracking. +enum StatusBits +{ + Status_OrientationTracked = 0x0001, // Orientation is currently tracked (connected and in use). + Status_PositionTracked = 0x0002, // Position is currently tracked (false if out of range). + Status_PositionConnected = 0x0020, // Position tracking HW is conceded. + // Status_HMDConnected = 0x0080 // HMD Display is available & connected. +}; + + +// Full state of of the sensor reported by GetSensorState() at a given absolute time. +class SensorState +{ +public: + SensorState() : Temperature(0), StatusFlags(0) { } + + // C-interop support + SensorState(const ovrSensorState& s); + operator ovrSensorState () const; + + // Pose state at the time that SensorState was requested. + PoseStatef Predicted; + // Actual recorded pose configuration based on sensor sample at a + // moment closest to the requested time. + PoseStatef Recorded; + + // Calibrated magnetometer reading, in Gauss, at sample time. + Vector3f Magnetometer; + // Sensor temperature reading, in degrees Celsius, at sample time. + float Temperature; + // Sensor status described by ovrStatusBits. + unsigned int StatusFlags; +}; + + + +//------------------------------------------------------------------------------------- + +class VisionHandler +{ +public: + virtual void OnVisionSuccess(const Transform<double>& cameraFromImu, UInt32 exposureCounter) = 0; + virtual void OnVisionPreviousFrame(const Transform<double>& cameraFromImu) = 0; + virtual void OnVisionFailure() = 0; + + // Get a configuration that represents the change over a short time interval + virtual Transform<double> GetVisionPrediction(UInt32 exposureCounter) = 0; +}; + +//------------------------------------------------------------------------------------- +// ***** SensorFusion + +// SensorFusion class accumulates Sensor notification messages to keep track of +// orientation, which involves integrating the gyro and doing correction with gravity. +// Magnetometer based yaw drift correction is also supported; it is usually enabled +// automatically based on loaded magnetometer configuration. +// Orientation is reported as a quaternion, from which users can obtain either the +// rotation matrix or Euler angles. +// +// The class can operate in two ways: +// - By user manually passing MessageBodyFrame messages to the OnMessage() function. +// - By attaching SensorFusion to a SensorDevice, in which case it will +// automatically handle notifications from that device. + + +class SensorFusion : public NewOverrideBase, public VisionHandler +{ + friend class SensorFusionDebug; + + enum + { + MagMaxReferences = 1000 + }; + +public: + + // ------------------------------------------------------------------------------- + // Critical components for tiny API + + SensorFusion(SensorDevice* sensor = 0); + ~SensorFusion(); + + // Attaches this SensorFusion to the IMU sensor device, from which it will receive + // notification messages. If a sensor is attached, manual message notification + // is not necessary. Calling this function also resets SensorFusion state. + bool AttachToSensor(SensorDevice* sensor); + + // Returns true if this Sensor fusion object is attached to the IMU. + bool IsAttachedToSensor() const; + + // Sets up head-and-neck model and device-to-pupil dimensions from the user's profile and the HMD stats. + // This copes elegantly if profile is NULL. + void SetUserHeadDimensions(Profile const &profile, HmdRenderInfo const &hmdRenderInfo); + + // Get the predicted pose (orientation, position) of the center pupil frame (CPF) at a specific point in time. + Transformf GetPoseAtTime(double absoluteTime) const; + + // Get the full dynamical system state of the CPF, which includes velocities and accelerations, + // predicted at a specified absolute point in time. + SensorState GetSensorStateAtTime(double absoluteTime) const; + + // Get the sensor status (same as GetSensorStateAtTime(...).Status) + unsigned int GetStatus() const; + + // End tiny API components + // ------------------------------------------------------------------------------- + + // Resets the current orientation. + void Reset (); + + // Configuration + void EnableMotionTracking(bool enable = true) { MotionTrackingEnabled = enable; } + bool IsMotionTrackingEnabled() const { return MotionTrackingEnabled; } + + // Accelerometer/Gravity Correction Control + // Enables/disables gravity correction (on by default). + void SetGravityEnabled (bool enableGravity); + bool IsGravityEnabled () const; + + // Vision Position and Orientation Configuration + // ----------------------------------------------- + bool IsVisionPositionEnabled () const; + void SetVisionPositionEnabled (bool enableVisionPosition); + + // compensates for a tilted camera + void SetCameraTiltCorrectionEnabled(bool enable); + bool IsCameraTiltCorrectionEnabled () const; + + // Message Handling Logic + // ----------------------------------------------- + // Notifies SensorFusion object about a new BodyFrame + // message from a sensor. + // Should be called by user if not attached to sensor. + void OnMessage (const MessageBodyFrame& msg); + + + // Interaction with vision + // ----------------------------------------------- + // Handle observation from vision system (orientation, position, time) + virtual void OnVisionSuccess(const Transform<double>& cameraFromImu, UInt32 exposureCounter); + + virtual void OnVisionPreviousFrame(const Transform<double>& cameraFromImu); + virtual void OnVisionFailure(); + // Get a configuration that represents the change over a short time interval + virtual Transform<double> GetVisionPrediction(UInt32 exposureCounter); + + double GetTime () const; + double GetVisionLatency () const; + + + // Detailed head dimension control + // ----------------------------------------------- + // These are now deprecated in favour of SetUserHeadDimensions() + Vector3f GetHeadModel() const; + void SetHeadModel(const Vector3f &headModel, bool resetNeckPivot = true ); + float GetCenterPupilDepth() const; + void SetCenterPupilDepth(float centerPupilDepth); + + + // Magnetometer and Yaw Drift Section: + // --------------------------------------- + + // Enables/disables magnetometer based yaw drift correction. + // Must also have mag calibration data for this correction to work. + void SetYawCorrectionEnabled(bool enable); + // Determines if yaw correction is enabled. + bool IsYawCorrectionEnabled () const; + + // Clear the reference points associating + // mag readings with orientations + void ClearMagReferences (); + + // Sets the focus filter direction to the current HMD direction + void SetFocusDirection(); + // Sets the focus filter to a direction in the body frame. Once set, a complementary filter + // will very slowly drag the world to keep the direction of the HMD within the FOV of the focus + void SetFocusDirection(Vector3d direction); + // Sets the FOV (in radians) of the focus. When the yaw difference between the HMD's current pose + // and the focus is smaller than the FOV, the complementary filter does not act. + void SetFocusFOV(double rads); + // Turns off the focus filter (equivalent to setting the focus to 0 + void ClearFocus(); + +private: + + // ----------------------------------------------- + + class BodyFrameHandler : public NewOverrideBase, public MessageHandler + { + SensorFusion* pFusion; + public: + BodyFrameHandler(SensorFusion* fusion) + : pFusion(fusion) {} + + ~BodyFrameHandler(); + + virtual void OnMessage(const Message& msg); + virtual bool SupportsMessageType(MessageType type) const; + }; + + + // ----------------------------------------------- + + // State version stored in lockless updater "queue" and used for + // prediction by GetPoseAtTime/GetSensorStateAtTime + struct LocklessState + { + PoseState<double> State; + float Temperature; + Vector3d Magnetometer; + unsigned int StatusFlags; + + LocklessState() : Temperature(0.0), StatusFlags(0) { }; + }; + + + // ----------------------------------------------- + + // Entry describing the state of the headset at the time of an exposure as reported by the DK2 board. + // This is used in combination with the vision data for + // incremental tracking based on IMU change and for drift correction + struct ExposureRecord + { + UInt32 ExposureCounter; + double ExposureTime; + // State of the headset at the time of exposure + PoseState<double> WorldFromImu; + // Change in state since the last exposure based on IMU data only + PoseState<double> ImuOnlyDelta; + // Did we have tracking for the entire interval between exposures + bool VisionTrackingAvailable; + + ExposureRecord() : ExposureCounter(0), ExposureTime(0.0), VisionTrackingAvailable(true) { } + ExposureRecord(UInt32 exposureCounter, double exposureTime, + const PoseState<double>& worldFromImu, const PoseState<double>& imuOnlyDelta) + : ExposureCounter(exposureCounter), ExposureTime(exposureTime), + WorldFromImu(worldFromImu), ImuOnlyDelta(imuOnlyDelta), VisionTrackingAvailable(true) { } + }; + + // ----------------------------------------------- + + // Entry describing the magnetometer reference point + // Used for mag yaw correction + struct MagReferencePoint + { + Vector3d InImuFrame; + Transformd WorldFromImu; + int Score; + + MagReferencePoint() { } + MagReferencePoint(const Vector3d& inImuFrame, const Transformd& worldFromImu, int score) + : InImuFrame(inImuFrame), WorldFromImu(worldFromImu), Score(score) { } + }; + + // ----------------------------------------------- + + // The phase of the head as estimated by sensor fusion + PoseState<double> WorldFromImu; + + // State that can be read without any locks, so that high priority rendering thread + // doesn't have to worry about being blocked by a sensor/vision threads that got preempted. + LocklessUpdater<LocklessState> UpdatedState; + + // The pose we got from Vision, augmented with velocity information from numerical derivatives + PoseState<double> CameraFromImu; + // Difference between the vision and sensor fusion poses at the time of last exposure adjusted + // by all the corrections applied since then + // NB: this one is unlike all the other poses/transforms we use, since it's a difference + // between 2 WorldFromImu transforms, but is stored in the world frame, not the IMU frame + // (see computeVisionError() for details) + // For composition purposes it should be considered a WorldFromWorld transform, where the left + // side comes from vision and the right - from sensor fusion + PoseState<double> VisionError; + // Past exposure records between the last update from vision and now + // (should only be one record unless vision latency is high) + CircularBuffer<ExposureRecord> ExposureRecordHistory; + // ExposureRecord that corresponds to the last pose we got from vision + ExposureRecord LastVisionExposureRecord; + // Incomplete ExposureRecord that will go into the history buffer when + // the new MessageExposureFrame is received + ExposureRecord NextExposureRecord; + // Timings of the previous exposure, used to populate ExposureRecordHistory + MessageExposureFrame LastMessageExposureFrame; + // Time of the last vision update + double LastVisionAbsoluteTime; + + unsigned int Stage; + BodyFrameHandler *pHandler; + + Vector3d FocusDirection; + double FocusFOV; + + SensorFilterBodyFrame FAccelInImuFrame, FAccelInCameraFrame; + SensorFilterd FAngV; + + Vector3d AccelOffset; + + bool EnableGravity; + + bool EnableYawCorrection; + bool MagCalibrated; + Array<MagReferencePoint> MagRefs; + int MagRefIdx; + Quatd MagCorrectionIntegralTerm; + + bool EnableCameraTiltCorrection; + // Describes the pose of the camera in the world coordinate system + Transformd WorldFromCamera; + double WorldFromCameraConfidence; + + bool MotionTrackingEnabled; + bool VisionPositionEnabled; + + // This is a signed distance, but positive because Z increases looking inward. + // This is expressed relative to the IMU in the HMD and corresponds to the location + // of the cyclopean virtual camera focal point if both the physical and virtual + // worlds are isometrically mapped onto each other. -Steve + float CenterPupilDepth; + // Describes the position of the user eyes relative to the IMU + Transformd ImuFromCpf; + // Position of the center of the screen relative to the IMU (loaded from the headset firmware) + Transformd ImuFromScreen; + // Built-in head model for faking position using orientation only + Transformd CpfFromNeck; + // Last known base of the neck pose used for head model computations + Transformd WorldFromNeck; + + //--------------------------------------------- + + // Internal handler for messages + // bypasses error checking. + void handleMessage(const MessageBodyFrame& msg); + void handleExposure(const MessageExposureFrame& msg); + + // Compute the difference between vision and sensor fusion data + PoseStated computeVisionError(); + // Apply headset yaw correction from magnetometer + // for models without camera or when camera isn't available + void applyMagYawCorrection(Vector3d mag, double deltaT); + // Apply headset tilt correction from the accelerometer + void applyTiltCorrection(double deltaT); + // Apply headset yaw correction from the camera + void applyVisionYawCorrection(double deltaT); + // Apply headset position correction from the camera + void applyPositionCorrection(double deltaT); + // Apply camera tilt correction from the accelerometer + void applyCameraTiltCorrection(Vector3d accel, double deltaT); + // Apply camera focus correction + void applyFocusCorrection(double deltaT); + + // If you have a known-good pose, this sets the neck pivot position. + void setNeckPivotFromPose ( Transformd const &pose ); +}; + + + +//------------------------------------------------------------------------------------- +// ***** SensorFusion - Inlines + +inline bool SensorFusion::IsAttachedToSensor() const +{ + return pHandler->IsHandlerInstalled(); +} + +inline void SensorFusion::SetGravityEnabled(bool enableGravity) +{ + EnableGravity = enableGravity; +} + +inline bool SensorFusion::IsGravityEnabled() const +{ + return EnableGravity; +} + +inline void SensorFusion::SetYawCorrectionEnabled(bool enable) +{ + EnableYawCorrection = enable; +} + +inline bool SensorFusion::IsYawCorrectionEnabled() const +{ + return EnableYawCorrection; +} + +inline bool SensorFusion::IsVisionPositionEnabled() const +{ + return VisionPositionEnabled; +} + +inline void SensorFusion::SetVisionPositionEnabled(bool enableVisionPosition) +{ + VisionPositionEnabled = enableVisionPosition; +} + +inline void SensorFusion::SetCameraTiltCorrectionEnabled(bool enable) +{ + EnableCameraTiltCorrection = enable; +} + +inline bool SensorFusion::IsCameraTiltCorrectionEnabled() const +{ + return EnableCameraTiltCorrection; +} + +inline double SensorFusion::GetVisionLatency() const +{ + return LastVisionAbsoluteTime - CameraFromImu.TimeInSeconds; +} + +inline double SensorFusion::GetTime() const +{ + return Timer::GetSeconds(); +} + +inline SensorFusion::BodyFrameHandler::~BodyFrameHandler() +{ + RemoveHandlerFromDevices(); +} + +inline bool SensorFusion::BodyFrameHandler::SupportsMessageType(MessageType type) const +{ + return (type == Message_BodyFrame || type == Message_ExposureFrame); +} + + +//------------------------------------------------------------------------------------- +// ***** PoseState - Inlines + +// Stores and integrates gyro angular velocity reading for a given time step. +template<class T> +void PoseState<T>::StoreAndIntegrateGyro(Vector3d angVel, double dt) +{ + AngularVelocity = angVel; + double angle = angVel.Length() * dt; + if (angle > 0) + Pose.Rotation = Pose.Rotation * Quatd(angVel, angle); +} + +template<class T> +void PoseState<T>::StoreAndIntegrateAccelerometer(Vector3d linearAccel, double dt) +{ + LinearAcceleration = linearAccel; + Pose.Translation += LinearVelocity * dt + LinearAcceleration * (dt * dt * 0.5); + LinearVelocity += LinearAcceleration * dt; +} + +// Performs integration of state by adding next state delta to it +// to produce a combined state change +template<class T> +void PoseState<T>::AdvanceByDelta(const PoseState<T>& delta) +{ + Pose.Rotation = Pose.Rotation * delta.Pose.Rotation; + Pose.Translation += delta.Pose.Translation + LinearVelocity * delta.TimeInSeconds; + LinearVelocity += delta.LinearVelocity; + TimeInSeconds += delta.TimeInSeconds; +} + +} // namespace OVR +#endif diff --git a/LibOVR/Src/OVR_SensorFusionDebug.h b/LibOVR/Src/OVR_SensorFusionDebug.h new file mode 100644 index 0000000..0fc7eb5 --- /dev/null +++ b/LibOVR/Src/OVR_SensorFusionDebug.h @@ -0,0 +1,82 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_SensorFusionDebug.h +Content : Friend proxy to allow debugging access to SensorFusion +Created : April 16, 2014 +Authors : Dan Gierl + +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. + +*************************************************************************************/ + +#ifndef OVR_SensorFusionDebug_h +#define OVR_SensorFusionDebug_h + +#include "OVR_SensorFusion.h" + +namespace OVR { + +class SensorFusionDebug +{ +private: + + SensorFusion * sf; + +public: + + SensorFusionDebug (SensorFusion * const sf) : + sf(sf) + { + } + + // Returns the number of magnetometer reference points currently gathered + int GetNumMagRefPoints () const; + // Returns the index of the magnetometer reference point being currently used + int GetCurMagRefPointIdx () const; + // Returns a copy of all the data associated with a magnetometer reference point + // This includes it's score, the magnetometer reading as a vector, + // and the HMD's pose at the time it was gathered + void GetMagRefData (int idx, int * score, Vector3d * magBF, Quatd * magPose) const; + +}; + +//------------------------------------------------------------------------------------ +// Magnetometer reference point access functions + +int SensorFusionDebug::GetNumMagRefPoints() const +{ + return (int)sf->MagRefs.GetSize(); +} + +int SensorFusionDebug::GetCurMagRefPointIdx() const +{ + return sf->MagRefIdx; +} + +void SensorFusionDebug::GetMagRefData(int idx, int * score, Vector3d * magBF, Quatd * magPose) const +{ + OVR_ASSERT(idx >= 0 && idx < GetNumMagRefPoints()); + *score = sf->MagRefs[idx].Score; + *magBF = sf->MagRefs[idx].InImuFrame; + *magPose = sf->MagRefs[idx].WorldFromImu.Rotation; +} + +} // OVR + +#endif
\ No newline at end of file diff --git a/LibOVR/Src/OVR_SensorImpl.cpp b/LibOVR/Src/OVR_SensorImpl.cpp new file mode 100644 index 0000000..91ae7e0 --- /dev/null +++ b/LibOVR/Src/OVR_SensorImpl.cpp @@ -0,0 +1,1165 @@ +/************************************************************************************ + +Filename : OVR_SensorImpl.cpp +Content : Oculus Sensor device implementation. +Created : March 7, 2013 +Authors : Lee Cooper, Dov Katz + +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 "OVR_SensorImpl.h" +#include "OVR_Sensor2Impl.h" +#include "OVR_SensorImpl_Common.h" +#include "OVR_JSON.h" +#include "OVR_Profile.h" +#include "Kernel/OVR_Alg.h" +#include <time.h> + +// HMDDeviceDesc can be created/updated through Sensor carrying DisplayInfo. + +#include "Kernel/OVR_Timer.h" + +//extern FILE *SF_LOG_fp; + +namespace OVR { + +using namespace Alg; + +//------------------------------------------------------------------------------------- +// ***** Oculus Sensor-specific packet data structures + +enum { + Sensor_VendorId = Oculus_VendorId, + Sensor_Tracker_ProductId = Device_Tracker_ProductId, + Sensor_Tracker2_ProductId = Device_Tracker2_ProductId, + Sensor_KTracker_ProductId = Device_KTracker_ProductId, + + Sensor_BootLoader = 0x1001, + + Sensor_DefaultReportRate = 500, // Hz + Sensor_MaxReportRate = 1000 // Hz +}; + + +// Messages we care for +enum TrackerMessageType +{ + TrackerMessage_None = 0, + TrackerMessage_Sensors = 1, + TrackerMessage_Unknown = 0x100, + TrackerMessage_SizeError = 0x101, +}; + + +struct TrackerSensors +{ + UByte SampleCount; + UInt16 Timestamp; + UInt16 LastCommandID; + SInt16 Temperature; + + TrackerSample Samples[3]; + + SInt16 MagX, MagY, MagZ; + + TrackerMessageType Decode(const UByte* buffer, int size) + { + if (size < 62) + return TrackerMessage_SizeError; + + SampleCount = buffer[1]; + Timestamp = DecodeUInt16(buffer + 2); + LastCommandID = DecodeUInt16(buffer + 4); + Temperature = DecodeSInt16(buffer + 6); + + //if (SampleCount > 2) + // OVR_DEBUG_LOG_TEXT(("TackerSensor::Decode SampleCount=%d\n", SampleCount)); + + // Only unpack as many samples as there actually are + int iterationCount = (SampleCount > 2) ? 3 : SampleCount; + + for (int i = 0; i < iterationCount; i++) + { + UnpackSensor(buffer + 8 + 16 * i, &Samples[i].AccelX, &Samples[i].AccelY, &Samples[i].AccelZ); + UnpackSensor(buffer + 16 + 16 * i, &Samples[i].GyroX, &Samples[i].GyroY, &Samples[i].GyroZ); + } + + MagX = DecodeSInt16(buffer + 56); + MagY = DecodeSInt16(buffer + 58); + MagZ = DecodeSInt16(buffer + 60); + + return TrackerMessage_Sensors; + } +}; + +struct TrackerMessage +{ + TrackerMessageType Type; + TrackerSensors Sensors; +}; + + +//------------------------------------------------------------------------------------- +// ***** SensorDisplayInfoImpl +SensorDisplayInfoImpl::SensorDisplayInfoImpl() + : CommandId(0), DistortionType(Base_None) +{ + memset(Buffer, 0, PacketSize); + Buffer[0] = 9; +} + +void SensorDisplayInfoImpl::Unpack() +{ + CommandId = Buffer[1] | (UInt16(Buffer[2]) << 8); + DistortionType = Buffer[3]; + HResolution = DecodeUInt16(Buffer+4); + VResolution = DecodeUInt16(Buffer+6); + HScreenSize = DecodeUInt32(Buffer+8) * (1/1000000.f); + VScreenSize = DecodeUInt32(Buffer+12) * (1/1000000.f); + VCenter = DecodeUInt32(Buffer+16) * (1/1000000.f); + LensSeparation = DecodeUInt32(Buffer+20) * (1/1000000.f); + +#if 0 + // These are not well-measured on most devices - probably best to ignore them. + OutsideLensSurfaceToScreen[0] = DecodeUInt32(Buffer+24) * (1/1000000.f); + OutsideLensSurfaceToScreen[1] = DecodeUInt32(Buffer+28) * (1/1000000.f); + // TODO: add spline-based distortion. + // TODO: currently these values are all zeros in the HMD itself. + DistortionK[0] = DecodeFloat(Buffer+32); + DistortionK[1] = DecodeFloat(Buffer+36); + DistortionK[2] = DecodeFloat(Buffer+40); + DistortionK[3] = DecodeFloat(Buffer+44); + DistortionK[4] = DecodeFloat(Buffer+48); + DistortionK[5] = DecodeFloat(Buffer+52); +#else + // The above are either measured poorly, or don't have values at all. + // To remove the temptation to use them, set them to junk. + OutsideLensSurfaceToScreen[0] = -1.0f; + OutsideLensSurfaceToScreen[1] = -1.0f; + DistortionK[0] = -1.0f; + DistortionK[1] = -1.0f; + DistortionK[2] = -1.0f; + DistortionK[3] = -1.0f; + DistortionK[4] = -1.0f; + DistortionK[5] = -1.0f; +#endif +} + + +//------------------------------------------------------------------------------------- +// ***** SensorDeviceFactory + +SensorDeviceFactory &SensorDeviceFactory::GetInstance() +{ + static SensorDeviceFactory instance; + return instance; +} + +void SensorDeviceFactory::EnumerateDevices(EnumerateVisitor& visitor) +{ + + class SensorEnumerator : public HIDEnumerateVisitor + { + // Assign not supported; suppress MSVC warning. + void operator = (const SensorEnumerator&) { } + + DeviceFactory* pFactory; + EnumerateVisitor& ExternalVisitor; + public: + SensorEnumerator(DeviceFactory* factory, EnumerateVisitor& externalVisitor) + : pFactory(factory), ExternalVisitor(externalVisitor) { } + + virtual bool MatchVendorProduct(UInt16 vendorId, UInt16 productId) + { + return pFactory->MatchVendorProduct(vendorId, productId); + } + + virtual void Visit(HIDDevice& device, const HIDDeviceDesc& desc) + { + + if (desc.ProductId == Sensor_BootLoader) + { // If we find a sensor in boot loader mode then notify the app + // about the existence of the device, but don't allow the app + // to create or access the device + BootLoaderDeviceCreateDesc createDesc(pFactory, desc); + ExternalVisitor.Visit(createDesc); + return; + } + + SensorDeviceCreateDesc createDesc(pFactory, desc); + ExternalVisitor.Visit(createDesc); + + // Check if the sensor returns DisplayInfo. If so, try to use it to override potentially + // mismatching monitor information (in case wrong EDID is reported by splitter), + // or to create a new "virtualized" HMD Device. + + SensorDisplayInfoImpl displayInfo; + + if (device.GetFeatureReport(displayInfo.Buffer, SensorDisplayInfoImpl::PacketSize)) + { + displayInfo.Unpack(); + + // If we got display info, try to match / create HMDDevice as well + // so that sensor settings give preference. + if (displayInfo.DistortionType & SensorDisplayInfoImpl::Mask_BaseFmt) + { + SensorDeviceImpl::EnumerateHMDFromSensorDisplayInfo(displayInfo, ExternalVisitor); + } + } + } + }; + + //double start = Timer::GetProfileSeconds(); + + SensorEnumerator sensorEnumerator(this, visitor); + GetManagerImpl()->GetHIDDeviceManager()->Enumerate(&sensorEnumerator); + + //double totalSeconds = Timer::GetProfileSeconds() - start; +} + +bool SensorDeviceFactory::MatchVendorProduct(UInt16 vendorId, UInt16 productId) const +{ + return ((vendorId == Sensor_VendorId) && (productId == Sensor_Tracker_ProductId)) || + ((vendorId == Sensor_VendorId) && (productId == Sensor_Tracker2_ProductId)) || + ((vendorId == Sensor_VendorId) && (productId == Sensor_KTracker_ProductId)); +} + +bool SensorDeviceFactory::DetectHIDDevice(DeviceManager* pdevMgr, const HIDDeviceDesc& desc) +{ + if (MatchVendorProduct(desc.VendorId, desc.ProductId)) + { + if (desc.ProductId == Sensor_BootLoader) + { // If we find a sensor in boot loader mode then notify the app + // about the existence of the device, but don't allow them + // to create or access the device + BootLoaderDeviceCreateDesc createDesc(this, desc); + pdevMgr->AddDevice_NeedsLock(createDesc); + return false; // return false to allow upstream boot loader factories to catch the device + } + else + { + SensorDeviceCreateDesc createDesc(this, desc); + return pdevMgr->AddDevice_NeedsLock(createDesc).GetPtr() != NULL; + } + } + return false; +} + +//------------------------------------------------------------------------------------- +// ***** SensorDeviceCreateDesc + +DeviceBase* SensorDeviceCreateDesc::NewDeviceInstance() +{ + if (HIDDesc.ProductId == Sensor_Tracker2_ProductId) + { + return new Sensor2DeviceImpl(this); + } + + return new SensorDeviceImpl(this); +} + +bool SensorDeviceCreateDesc::GetDeviceInfo(DeviceInfo* info) const +{ + if ((info->InfoClassType != Device_Sensor) && + (info->InfoClassType != Device_None)) + return false; + + info->Type = Device_Sensor; + info->ProductName = HIDDesc.Product; + info->Manufacturer = HIDDesc.Manufacturer; + info->Version = HIDDesc.VersionNumber; + + if (info->InfoClassType == Device_Sensor) + { + SensorInfo* sinfo = (SensorInfo*)info; + sinfo->VendorId = HIDDesc.VendorId; + sinfo->ProductId = HIDDesc.ProductId; + sinfo->MaxRanges = SensorRangeImpl::GetMaxSensorRange(); + sinfo->SerialNumber = HIDDesc.SerialNumber; + } + return true; +} + +//------------------------------------------------------------------------------------- +// ***** SensorDevice + +SensorDeviceImpl::SensorDeviceImpl(SensorDeviceCreateDesc* createDesc) + : OVR::HIDDeviceImpl<OVR::SensorDevice>(createDesc, 0), + Coordinates(SensorDevice::Coord_Sensor), + HWCoordinates(SensorDevice::Coord_HMD), // HW reports HMD coordinates by default. + NextKeepAliveTickSeconds(0), + FullTimestamp(0), + MaxValidRange(SensorRangeImpl::GetMaxSensorRange()), + magCalibrated(false) +{ + SequenceValid = false; + LastSampleCount = 0; + LastTimestamp = 0; + + OldCommandId = 0; + + PrevAbsoluteTime = 0.0; + +#ifdef OVR_OS_ANDROID + pPhoneSensors = PhoneSensors::Create(); +#endif +} + +SensorDeviceImpl::~SensorDeviceImpl() +{ + // Check that Shutdown() was called. + OVR_ASSERT(!pCreateDesc->pDevice); +} + + +// Internal creation APIs. +bool SensorDeviceImpl::Initialize(DeviceBase* parent) +{ + if (HIDDeviceImpl<OVR::SensorDevice>::Initialize(parent)) + { + openDevice(); + return true; + } + + return false; +} + +void SensorDeviceImpl::openDevice() +{ + + // Read the currently configured range from sensor. + SensorRangeImpl sr(SensorRange(), 0); + + if (GetInternalDevice()->GetFeatureReport(sr.Buffer, SensorRangeImpl::PacketSize)) + { + sr.Unpack(); + sr.GetSensorRange(&CurrentRange); + // Increase the magnetometer range, since the default value is not enough in practice + CurrentRange.MaxMagneticField = 2.5f; + setRange(CurrentRange); + } + + // Read the currently configured calibration from sensor. + SensorFactoryCalibrationImpl sc; + if (GetInternalDevice()->GetFeatureReport(sc.Buffer, SensorFactoryCalibrationImpl::PacketSize)) + { + sc.Unpack(); + AccelCalibrationOffset = sc.AccelOffset; + GyroCalibrationOffset = sc.GyroOffset; + AccelCalibrationMatrix = sc.AccelMatrix; + GyroCalibrationMatrix = sc.GyroMatrix; + CalibrationTemperature = sc.Temperature; + } + + // If the sensor has "DisplayInfo" data, use HMD coordinate frame by default. + SensorDisplayInfoImpl displayInfo; + if (GetInternalDevice()->GetFeatureReport(displayInfo.Buffer, SensorDisplayInfoImpl::PacketSize)) + { + displayInfo.Unpack(); + Coordinates = (displayInfo.DistortionType & SensorDisplayInfoImpl::Mask_BaseFmt) ? + Coord_HMD : Coord_Sensor; + } + + // Read/Apply sensor config. + setCoordinateFrame(Coordinates); + setReportRate(Sensor_DefaultReportRate); + + // Set Keep-alive at 10 seconds. + SensorKeepAliveImpl skeepAlive(10 * 1000); + GetInternalDevice()->SetFeatureReport(skeepAlive.Buffer, SensorKeepAliveImpl::PacketSize); + + // Load mag calibration + MagCalibrationReport report; + bool res = GetMagCalibrationReport(&report); + if (res && report.Version > 0) + { + magCalibration = report.Calibration; + magCalibrated = true; + } +} + +void SensorDeviceImpl::closeDeviceOnError() +{ + LogText("OVR::SensorDevice - Lost connection to '%s'\n", getHIDDesc()->Path.ToCStr()); + NextKeepAliveTickSeconds = 0; +} + +void SensorDeviceImpl::Shutdown() +{ + HIDDeviceImpl<OVR::SensorDevice>::Shutdown(); + + LogText("OVR::SensorDevice - Closed '%s'\n", getHIDDesc()->Path.ToCStr()); +} + +void SensorDeviceImpl::OnInputReport(UByte* pData, UInt32 length) +{ + + bool processed = false; + if (!processed) + { + TrackerMessage message; + if (decodeTrackerMessage(&message, pData, length)) + { + processed = true; + onTrackerMessage(&message); + } + } +} + +double SensorDeviceImpl::OnTicks(double tickSeconds) +{ + if (tickSeconds >= NextKeepAliveTickSeconds) + { + // Use 3-seconds keep alive by default. + double keepAliveDelta = 3.0; + + // Set Keep-alive at 10 seconds. + SensorKeepAliveImpl skeepAlive(10 * 1000); + // OnTicks is called from background thread so we don't need to add this to the command queue. + GetInternalDevice()->SetFeatureReport(skeepAlive.Buffer, SensorKeepAliveImpl::PacketSize); + + // Emit keep-alive every few seconds. + NextKeepAliveTickSeconds = tickSeconds + keepAliveDelta; + } + return NextKeepAliveTickSeconds - tickSeconds; +} + +bool SensorDeviceImpl::SetRange(const SensorRange& range, bool waitFlag) +{ + bool result = 0; + ThreadCommandQueue * threadQueue = GetManagerImpl()->GetThreadQueue(); + + if (!waitFlag) + { + return threadQueue->PushCall(this, &SensorDeviceImpl::setRange, range); + } + + if (!threadQueue->PushCallAndWaitResult(this, + &SensorDeviceImpl::setRange, + &result, + range)) + { + return false; + } + + return result; +} + +void SensorDeviceImpl::GetRange(SensorRange* range) const +{ + Lock::Locker lockScope(GetLock()); + *range = CurrentRange; +} + +bool SensorDeviceImpl::setRange(const SensorRange& range) +{ + SensorRangeImpl sr(range); + + if (GetInternalDevice()->SetFeatureReport(sr.Buffer, SensorRangeImpl::PacketSize)) + { + Lock::Locker lockScope(GetLock()); + sr.GetSensorRange(&CurrentRange); + return true; + } + + return false; +} + +void SensorDeviceImpl::SetCoordinateFrame(CoordinateFrame coordframe) +{ + // Push call with wait. + GetManagerImpl()->GetThreadQueue()-> + PushCall(this, &SensorDeviceImpl::setCoordinateFrame, coordframe, true); +} + +SensorDevice::CoordinateFrame SensorDeviceImpl::GetCoordinateFrame() const +{ + return Coordinates; +} + +Void SensorDeviceImpl::setCoordinateFrame(CoordinateFrame coordframe) +{ + + Coordinates = coordframe; + + // Read the original coordinate frame, then try to change it. + SensorConfigImpl scfg; + if (GetInternalDevice()->GetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize)) + { + scfg.Unpack(); + } + + scfg.SetSensorCoordinates(coordframe == Coord_Sensor); + scfg.Pack(); + + GetInternalDevice()->SetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize); + + // Re-read the state, in case of older firmware that doesn't support Sensor coordinates. + if (GetInternalDevice()->GetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize)) + { + scfg.Unpack(); + HWCoordinates = scfg.IsUsingSensorCoordinates() ? Coord_Sensor : Coord_HMD; + } + else + { + HWCoordinates = Coord_HMD; + } + return 0; +} + +void SensorDeviceImpl::SetReportRate(unsigned rateHz) +{ + // Push call with wait. + GetManagerImpl()->GetThreadQueue()-> + PushCall(this, &SensorDeviceImpl::setReportRate, rateHz, true); +} + +unsigned SensorDeviceImpl::GetReportRate() const +{ + // Read the original configuration + SensorConfigImpl scfg; + if (GetInternalDevice()->GetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize)) + { + scfg.Unpack(); + return Sensor_MaxReportRate / (scfg.PacketInterval + 1); + } + return 0; // error +} + +Void SensorDeviceImpl::setReportRate(unsigned rateHz) +{ + // Read the original configuration + SensorConfigImpl scfg; + if (GetInternalDevice()->GetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize)) + { + scfg.Unpack(); + } + + if (rateHz > Sensor_MaxReportRate) + rateHz = Sensor_MaxReportRate; + else if (rateHz == 0) + rateHz = Sensor_DefaultReportRate; + + scfg.PacketInterval = UInt16((Sensor_MaxReportRate / rateHz) - 1); + + scfg.Pack(); + + GetInternalDevice()->SetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize); + return 0; +} + +void SensorDeviceImpl::GetFactoryCalibration(Vector3f* AccelOffset, Vector3f* GyroOffset, + Matrix4f* AccelMatrix, Matrix4f* GyroMatrix, + float* Temperature) +{ + *AccelOffset = AccelCalibrationOffset; + *GyroOffset = GyroCalibrationOffset; + *AccelMatrix = AccelCalibrationMatrix; + *GyroMatrix = GyroCalibrationMatrix; + *Temperature = CalibrationTemperature; +} + +bool SensorDeviceImpl::IsMagCalibrated() +{ + return magCalibrated; +} + +void SensorDeviceImpl::SetOnboardCalibrationEnabled(bool enabled) +{ + // Push call with wait. + GetManagerImpl()->GetThreadQueue()-> + PushCall(this, &SensorDeviceImpl::setOnboardCalibrationEnabled, enabled, true); +} + +Void SensorDeviceImpl::setOnboardCalibrationEnabled(bool enabled) +{ + // Read the original configuration + SensorConfigImpl scfg; + if (GetInternalDevice()->GetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize)) + { + scfg.Unpack(); + } + + if (enabled) + scfg.Flags |= (SensorConfigImpl::Flag_AutoCalibration | SensorConfigImpl::Flag_UseCalibration); + else + scfg.Flags &= ~(SensorConfigImpl::Flag_AutoCalibration | SensorConfigImpl::Flag_UseCalibration); + + scfg.Pack(); + + GetInternalDevice()->SetFeatureReport(scfg.Buffer, SensorConfigImpl::PacketSize); + return 0; +} + +void SensorDeviceImpl::AddMessageHandler(MessageHandler* handler) +{ + if (handler) + SequenceValid = false; + DeviceBase::AddMessageHandler(handler); +} + +// Sensor reports data in the following coordinate system: +// Accelerometer: 10^-4 m/s^2; X forward, Y right, Z Down. +// Gyro: 10^-4 rad/s; X positive roll right, Y positive pitch up; Z positive yaw right. + + +// We need to convert it to the following RHS coordinate system: +// X right, Y Up, Z Back (out of screen) +// +Vector3f AccelFromBodyFrameUpdate(const TrackerSensors& update, UByte sampleNumber, + bool convertHMDToSensor = false) +{ + const TrackerSample& sample = update.Samples[sampleNumber]; + float ax = (float)sample.AccelX; + float ay = (float)sample.AccelY; + float az = (float)sample.AccelZ; + + Vector3f val = convertHMDToSensor ? Vector3f(ax, az, -ay) : Vector3f(ax, ay, az); + return val * 0.0001f; +} + + +Vector3f MagFromBodyFrameUpdate(const TrackerSensors& update, + Matrix4f magCalibration, + bool convertHMDToSensor = false) +{ + float mx = (float)update.MagX; + float my = (float)update.MagY; + float mz = (float)update.MagZ; + // Note: Y and Z are swapped in comparison to the Accel. + // This accounts for DK1 sensor firmware axis swap, which should be undone in future releases. + Vector3f mag = convertHMDToSensor ? Vector3f(mx, my, -mz) : Vector3f(mx, mz, my); + mag *= 0.0001f; + // Apply calibration + return magCalibration.Transform(mag); +} + +Vector3f EulerFromBodyFrameUpdate(const TrackerSensors& update, UByte sampleNumber, + bool convertHMDToSensor = false) +{ + const TrackerSample& sample = update.Samples[sampleNumber]; + float gx = (float)sample.GyroX; + float gy = (float)sample.GyroY; + float gz = (float)sample.GyroZ; + + Vector3f val = convertHMDToSensor ? Vector3f(gx, gz, -gy) : Vector3f(gx, gy, gz); + return val * 0.0001f; +} + +bool SensorDeviceImpl::decodeTrackerMessage(TrackerMessage* message, UByte* buffer, int size) +{ + memset(message, 0, sizeof(TrackerMessage)); + + if (size < 4) + { + message->Type = TrackerMessage_SizeError; + return false; + } + + switch (buffer[0]) + { + case TrackerMessage_Sensors: + message->Type = message->Sensors.Decode(buffer, size); + break; + + default: + message->Type = TrackerMessage_Unknown; + break; + } + + return (message->Type < TrackerMessage_Unknown) && (message->Type != TrackerMessage_None); +} + +void SensorDeviceImpl::onTrackerMessage(TrackerMessage* message) +{ + if (message->Type != TrackerMessage_Sensors) + return; + + const double timeUnit = (1.0 / 1000.0); + double scaledTimeUnit = timeUnit; + TrackerSensors& s = message->Sensors; + // DK1 timestamps the first sample, so the actual device time will be later + // by the time we get the message if there are multiple samples. + int timestampAdjust = (s.SampleCount > 0) ? s.SampleCount-1 : 0; + + const double now = Timer::GetSeconds(); + double absoluteTimeSeconds = 0.0; + + + if (SequenceValid) + { + unsigned timestampDelta; + + if (s.Timestamp < LastTimestamp) + { + // The timestamp rolled around the 16 bit counter, so FullTimeStamp + // needs a high word increment. + FullTimestamp += 0x10000; + timestampDelta = ((((int)s.Timestamp) + 0x10000) - (int)LastTimestamp); + } + else + { + timestampDelta = (s.Timestamp - LastTimestamp); + } + // Update the low word of FullTimeStamp + FullTimestamp = ( FullTimestamp & ~0xffff ) | s.Timestamp; + + double deviceTime = (FullTimestamp + timestampAdjust) * timeUnit; + absoluteTimeSeconds = TimeFilter.SampleToSystemTime(deviceTime, now, PrevAbsoluteTime); + scaledTimeUnit = TimeFilter.ScaleTimeUnit(timeUnit); + PrevAbsoluteTime = absoluteTimeSeconds; + + // If we missed a small number of samples, generate the sample that would have immediately + // proceeded the current one. Re-use the IMU values from the last processed sample. + if ((timestampDelta > LastSampleCount) && (timestampDelta <= 254)) + { + if (HandlerRef.HasHandlers()) + { + MessageBodyFrame sensors(this); + + sensors.AbsoluteTimeSeconds = absoluteTimeSeconds - s.SampleCount * scaledTimeUnit; + sensors.TimeDelta = (float)((timestampDelta - LastSampleCount) * scaledTimeUnit); + sensors.Acceleration = LastAcceleration; + sensors.RotationRate = LastRotationRate; + sensors.MagneticField = LastMagneticField; + sensors.Temperature = LastTemperature; + + HandlerRef.Call(sensors); + } + } + } + else + { + LastAcceleration = Vector3f(0); + LastRotationRate = Vector3f(0); + LastMagneticField= Vector3f(0); + LastTemperature = 0; + SequenceValid = true; + + // This is our baseline sensor to host time delta, + // it will be adjusted with each new message. + FullTimestamp = s.Timestamp; + + double deviceTime = (FullTimestamp + timestampAdjust) * timeUnit; + absoluteTimeSeconds = TimeFilter.SampleToSystemTime(deviceTime, now, PrevAbsoluteTime); + scaledTimeUnit = TimeFilter.ScaleTimeUnit(timeUnit); + PrevAbsoluteTime = absoluteTimeSeconds; + } + + LastSampleCount = s.SampleCount; + LastTimestamp = s.Timestamp; + + bool convertHMDToSensor = (Coordinates == Coord_Sensor) && (HWCoordinates == Coord_HMD); + +#ifdef OVR_OS_ANDROID + // LDC - Normally we get the coordinate system from the tracker. + // Since KTracker doesn't store it we'll always assume HMD coordinate system. + convertHMDToSensor = false; +#endif + + if (HandlerRef.HasHandlers()) + { + MessageBodyFrame sensors(this); + UByte iterations = s.SampleCount; + + if (s.SampleCount > 3) + { + iterations = 3; + sensors.TimeDelta = (float)((s.SampleCount - 2) * scaledTimeUnit); + } + else + { + sensors.TimeDelta = (float)scaledTimeUnit; + } + + for (UByte i = 0; i < iterations; i++) + { + sensors.AbsoluteTimeSeconds = absoluteTimeSeconds - ( iterations - 1 - i ) * scaledTimeUnit; + sensors.Acceleration = AccelFromBodyFrameUpdate(s, i, convertHMDToSensor); + sensors.RotationRate = EulerFromBodyFrameUpdate(s, i, convertHMDToSensor); + sensors.MagneticField = MagFromBodyFrameUpdate(s, magCalibration, convertHMDToSensor); + +#ifdef OVR_OS_ANDROID + replaceWithPhoneMag(&(sensors.MagneticField)); +#endif + sensors.Temperature = s.Temperature * 0.01f; + HandlerRef.Call(sensors); + // TimeDelta for the last two sample is always fixed. + sensors.TimeDelta = (float)scaledTimeUnit; + } + + LastAcceleration = sensors.Acceleration; + LastRotationRate = sensors.RotationRate; + LastMagneticField= sensors.MagneticField; + LastTemperature = sensors.Temperature; + } + else + { + UByte i = (s.SampleCount > 3) ? 2 : (s.SampleCount - 1); + LastAcceleration = AccelFromBodyFrameUpdate(s, i, convertHMDToSensor); + LastRotationRate = EulerFromBodyFrameUpdate(s, i, convertHMDToSensor); + LastMagneticField = MagFromBodyFrameUpdate(s, magCalibration, convertHMDToSensor); + +#ifdef OVR_OS_ANDROID + replaceWithPhoneMag(&LastMagneticField); +#endif + LastTemperature = s.Temperature * 0.01f; + } +} + + +#ifdef OVR_OS_ANDROID + +void SensorDeviceImpl::replaceWithPhoneMag(Vector3f* val) +{ + + // Native calibrated. + pPhoneSensors->SetMagSource(PhoneSensors::MagnetometerSource_Native); + + Vector3f magPhone; + pPhoneSensors->GetLatestMagValue(&magPhone); + + // Phone value is in micro-Tesla. Convert it to Gauss and flip axes. + magPhone *= 10000.0f/1000000.0f; + + Vector3f res; + res.x = -magPhone.y; + res.y = magPhone.x; + res.z = magPhone.z; + + *val = res; +} +#endif + +const int MAX_DEVICE_PROFILE_MAJOR_VERSION = 1; + +// Writes the current calibration for a particular device to a device profile file +bool SensorDeviceImpl::SetMagCalibrationReport(const MagCalibrationReport &data) +{ + // Get device info + SensorInfo sinfo; + GetDeviceInfo(&sinfo); + + // A named calibration may be specified for calibration in different + // environments, otherwise the default calibration is used + const char* calibrationName = "default"; + + // Generate a mag calibration event + JSON* calibration = JSON::CreateObject(); + // (hardcoded for now) the measurement and representation method + calibration->AddStringItem("Version", "2.0"); + calibration->AddStringItem("Name", "default"); + + // time stamp the calibration + char time_str[64]; + +#ifdef OVR_OS_WIN32 + struct tm caltime; + time_t now = time(0); + localtime_s(&caltime, &now); + strftime(time_str, 64, "%Y-%m-%d %H:%M:%S", &caltime); +#else + struct tm* caltime; + time_t now = time(0); + caltime = localtime(&now); + strftime(time_str, 64, "%Y-%m-%d %H:%M:%S", caltime); +#endif + + calibration->AddStringItem("Time", time_str); + + // write the full calibration matrix + char matrix[256]; + data.Calibration.ToString(matrix, 256); + calibration->AddStringItem("CalibrationMatrix", matrix); + // save just the offset, for backwards compatibility + // this can be removed when we don't want to support 0.2.4 anymore + Vector3f center(data.Calibration.M[0][3], data.Calibration.M[1][3], data.Calibration.M[2][3]); + Matrix4f tmp = data.Calibration; tmp.M[0][3] = tmp.M[1][3] = tmp.M[2][3] = 0; tmp.M[3][3] = 1; + center = tmp.Inverted().Transform(center); + Matrix4f oldcalmat; oldcalmat.M[0][3] = center.x; oldcalmat.M[1][3] = center.y; oldcalmat.M[2][3] = center.z; + oldcalmat.ToString(matrix, 256); + calibration->AddStringItem("Calibration", matrix); + + String path = GetBaseOVRPath(true); + path += "/Devices.json"; + + // Look for a preexisting device file to edit + Ptr<JSON> root = *JSON::Load(path); + if (root) + { // Quick sanity check of the file type and format before we parse it + JSON* version = root->GetFirstItem(); + if (version && version->Name == "Oculus Device Profile Version") + { + int major = atoi(version->Value.ToCStr()); + if (major > MAX_DEVICE_PROFILE_MAJOR_VERSION) + { + // don't use the file on unsupported major version number + root->Release(); + root = NULL; + } + } + else + { + root->Release(); + root = NULL; + } + } + + JSON* device = NULL; + if (root) + { + device = root->GetFirstItem(); // skip the header + device = root->GetNextItem(device); + while (device) + { // Search for a previous calibration with the same name for this device + // and remove it before adding the new one + if (device->Name == "Device") + { + JSON* item = device->GetItemByName("Serial"); + if (item && item->Value == sinfo.SerialNumber) + { // found an entry for this device + item = device->GetNextItem(item); + while (item) + { + if (item->Name == "MagCalibration") + { + JSON* name = item->GetItemByName("Name"); + if (name && name->Value == calibrationName) + { // found a calibration of the same name + item->RemoveNode(); + item->Release(); + break; + } + } + item = device->GetNextItem(item); + } + + + /* + this is removed temporarily, since this is a sensor fusion setting, not sensor itself + should be moved to the correct place when Brant has finished the user profile implementation + // update the auto-mag flag + item = device->GetItemByName("EnableYawCorrection"); + if (item) + item->dValue = (double)EnableYawCorrection; + else + device->AddBoolItem("EnableYawCorrection", EnableYawCorrection);*/ + + break; + } + } + + device = root->GetNextItem(device); + } + } + else + { // Create a new device root + root = *JSON::CreateObject(); + root->AddStringItem("Oculus Device Profile Version", "1.0"); + } + + if (device == NULL) + { + device = JSON::CreateObject(); + device->AddStringItem("Product", sinfo.ProductName); + device->AddNumberItem("ProductID", sinfo.ProductId); + device->AddStringItem("Serial", sinfo.SerialNumber); + // removed temporarily, see above + //device->AddBoolItem("EnableYawCorrection", EnableYawCorrection); + + root->AddItem("Device", device); + } + + // Create and the add the new calibration event to the device + device->AddItem("MagCalibration", calibration); + return root->Save(path); +} + +// Loads a saved calibration for the specified device from the device profile file +bool SensorDeviceImpl::GetMagCalibrationReport(MagCalibrationReport* data) +{ + data->Version = 0; + data->Calibration.SetIdentity(); + + // Get device info + SensorInfo sinfo; + GetDeviceInfo(&sinfo); + + // A named calibration may be specified for calibration in different + // environments, otherwise the default calibration is used + const char* calibrationName = "default"; + + String path = GetBaseOVRPath(true); + path += "/Devices.json"; + + // Load the device profiles + Ptr<JSON> root = *JSON::Load(path); + if (root == NULL) + return false; + + // Quick sanity check of the file type and format before we parse it + JSON* version = root->GetFirstItem(); + if (version && version->Name == "Oculus Device Profile Version") + { + int major = atoi(version->Value.ToCStr()); + if (major > MAX_DEVICE_PROFILE_MAJOR_VERSION) + return false; // don't parse the file on unsupported major version number + } + else + { + return false; + } + + JSON* device = root->GetNextItem(version); + while (device) + { // Search for a previous calibration with the same name for this device + // and remove it before adding the new one + if (device->Name == "Device") + { + JSON* item = device->GetItemByName("Serial"); + if (item && item->Value == sinfo.SerialNumber) + { // found an entry for this device + + JSON* autoyaw = device->GetItemByName("EnableYawCorrection"); + // as a temporary HACK, return no calibration if EnableYawCorrection is off + // this will force disable yaw correction in SensorFusion + // proper solution would load the value in the Profile, which SensorFusion can access + if (autoyaw && autoyaw->dValue == 0) + return true; + + item = device->GetNextItem(item); + while (item) + { + if (item->Name == "MagCalibration") + { + JSON* calibration = item; + JSON* name = calibration->GetItemByName("Name"); + if (name && name->Value == calibrationName) + { // found a calibration with this name + + int major = 0; + JSON* version = calibration->GetItemByName("Version"); + if (version) + major = atoi(version->Value.ToCStr()); + + if (major > data->Version && major <= 2) + { + time_t now; + time(&now); + + // parse the calibration time + //time_t calibration_time = now; + JSON* caltime = calibration->GetItemByName("Time"); + if (caltime) + { + const char* caltime_str = caltime->Value.ToCStr(); + + tm ct; + memset(&ct, 0, sizeof(tm)); + +#ifdef OVR_OS_WIN32 + struct tm nowtime; + localtime_s(&nowtime, &now); + ct.tm_isdst = nowtime.tm_isdst; + sscanf_s(caltime_str, "%d-%d-%d %d:%d:%d", + &ct.tm_year, &ct.tm_mon, &ct.tm_mday, + &ct.tm_hour, &ct.tm_min, &ct.tm_sec); +#else + struct tm* nowtime = localtime(&now); + ct.tm_isdst = nowtime->tm_isdst; + sscanf(caltime_str, "%d-%d-%d %d:%d:%d", + &ct.tm_year, &ct.tm_mon, &ct.tm_mday, + &ct.tm_hour, &ct.tm_min, &ct.tm_sec); +#endif + ct.tm_year -= 1900; + ct.tm_mon--; + //calibration_time = mktime(&ct); + } + + // parse the calibration matrix + JSON* cal = calibration->GetItemByName("CalibrationMatrix"); + if (!cal) + cal = calibration->GetItemByName("Calibration"); + if (cal) + { + data->Calibration = Matrix4f::FromString(cal->Value.ToCStr()); + data->Version = (UByte)major; + } + } + } + } + item = device->GetNextItem(item); + } + + return true; + } + } + + device = root->GetNextItem(device); + } + + return true; +} + + +bool SensorDeviceImpl::SetSerialReport(const SerialReport& data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::setSerialReport, &result, data)) + { + return false; + } + + return result; +} + +bool SensorDeviceImpl::setSerialReport(const SerialReport& data) +{ + SerialImpl di(data); + return GetInternalDevice()->SetFeatureReport(di.Buffer, SerialImpl::PacketSize); +} + +bool SensorDeviceImpl::GetSerialReport(SerialReport* data) +{ + bool result; + if (!GetManagerImpl()->GetThreadQueue()-> + PushCallAndWaitResult(this, &Sensor2DeviceImpl::getSerialReport, &result, data)) + { + return false; + } + + return result; +} + +bool SensorDeviceImpl::getSerialReport(SerialReport* data) +{ + SerialImpl di; + if (GetInternalDevice()->GetFeatureReport(di.Buffer, SerialImpl::PacketSize)) + { + di.Unpack(); + *data = di.Settings; + return true; + } + + return false; +} + +} // namespace OVR diff --git a/LibOVR/Src/OVR_SensorImpl.h b/LibOVR/Src/OVR_SensorImpl.h new file mode 100644 index 0000000..f53b831 --- /dev/null +++ b/LibOVR/Src/OVR_SensorImpl.h @@ -0,0 +1,316 @@ +/************************************************************************************ + +Filename : OVR_SensorImpl.h +Content : Sensor device specific implementation. +Created : March 7, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_SensorImpl_h +#define OVR_SensorImpl_h + +#include "OVR_HIDDeviceImpl.h" +#include "OVR_SensorTimeFilter.h" +#include "OVR_Device.h" + +#ifdef OVR_OS_ANDROID +#include "OVR_PhoneSensors.h" +#endif + +namespace OVR { + +struct TrackerMessage; +class ExternalVisitor; + +//------------------------------------------------------------------------------------- +// SensorDeviceFactory enumerates Oculus Sensor devices. +class SensorDeviceFactory : public DeviceFactory +{ +public: + static SensorDeviceFactory &GetInstance(); + + // Enumerates devices, creating and destroying relevant objects in manager. + virtual void EnumerateDevices(EnumerateVisitor& visitor); + + virtual bool MatchVendorProduct(UInt16 vendorId, UInt16 productId) const; + virtual bool DetectHIDDevice(DeviceManager* pdevMgr, const HIDDeviceDesc& desc); +protected: + DeviceManager* getManager() const { return (DeviceManager*) pManager; } +}; + + +// Describes a single a Oculus Sensor device and supports creating its instance. +class SensorDeviceCreateDesc : public HIDDeviceCreateDesc +{ +public: + SensorDeviceCreateDesc(DeviceFactory* factory, const HIDDeviceDesc& hidDesc) + : HIDDeviceCreateDesc(factory, Device_Sensor, hidDesc) { } + + virtual DeviceCreateDesc* Clone() const + { + return new SensorDeviceCreateDesc(*this); + } + + virtual DeviceBase* NewDeviceInstance(); + + virtual MatchResult MatchDevice(const DeviceCreateDesc& other, + DeviceCreateDesc**) const + { + if ((other.Type == Device_Sensor) && (pFactory == other.pFactory)) + { + const SensorDeviceCreateDesc& s2 = (const SensorDeviceCreateDesc&) other; + if (MatchHIDDevice(s2.HIDDesc)) + return Match_Found; + } + return Match_None; + } + + virtual bool MatchHIDDevice(const HIDDeviceDesc& hidDesc) const + { + // should paths comparison be case insensitive? + return ((HIDDesc.Path.CompareNoCase(hidDesc.Path) == 0) && + (HIDDesc.SerialNumber == hidDesc.SerialNumber) && + (HIDDesc.VersionNumber == hidDesc.VersionNumber)); + } + + virtual bool GetDeviceInfo(DeviceInfo* info) const; +}; + +// A simple stub for notification of a sensor in Boot Loader mode +// This descriptor does not support the creation of a device, only the detection +// of its existence to warn apps that the sensor device needs firmware. +// The Boot Loader descriptor reuses and is created by the Sensor device factory +// but in the future may use a dedicated factory +class BootLoaderDeviceCreateDesc : public HIDDeviceCreateDesc +{ +public: + BootLoaderDeviceCreateDesc(DeviceFactory* factory, const HIDDeviceDesc& hidDesc) + : HIDDeviceCreateDesc(factory, Device_BootLoader, hidDesc) { } + + virtual DeviceCreateDesc* Clone() const + { + return new BootLoaderDeviceCreateDesc(*this); + } + + // Boot Loader device creation is not allowed + virtual DeviceBase* NewDeviceInstance() { return NULL; }; + + virtual MatchResult MatchDevice(const DeviceCreateDesc& other, + DeviceCreateDesc**) const + { + if ((other.Type == Device_BootLoader) && (pFactory == other.pFactory)) + { + const BootLoaderDeviceCreateDesc& s2 = (const BootLoaderDeviceCreateDesc&) other; + if (MatchHIDDevice(s2.HIDDesc)) + return Match_Found; + } + return Match_None; + } + + virtual bool MatchHIDDevice(const HIDDeviceDesc& hidDesc) const + { + // should paths comparison be case insensitive? + return ((HIDDesc.Path.CompareNoCase(hidDesc.Path) == 0) && + (HIDDesc.SerialNumber == hidDesc.SerialNumber)); + } + + virtual bool GetDeviceInfo(DeviceInfo* info) const + { + OVR_UNUSED(info); + return false; + } +}; + + +//------------------------------------------------------------------------------------- +// ***** OVR::SensorDisplayInfoImpl + +// DisplayInfo obtained from sensor; these values are used to report distortion +// settings and other coefficients. +// Older SensorDisplayInfo will have all zeros, causing the library to apply hard-coded defaults. +// Currently, only resolutions and sizes are used. +struct SensorDisplayInfoImpl +{ + enum { PacketSize = 56 }; + UByte Buffer[PacketSize]; + + enum + { + Mask_BaseFmt = 0x0f, + Mask_OptionFmts = 0xf0, + Base_None = 0, + Base_ScreenOnly = 1, + Base_Distortion = 2, + }; + + UInt16 CommandId; + + UByte DistortionType; + UInt16 HResolution, VResolution; + float HScreenSize, VScreenSize; + float VCenter; + float LensSeparation; + // Currently these values are not well-measured. + float OutsideLensSurfaceToScreen[2]; + // TODO: add DistortionEqn + // TODO: currently these values are all zeros and the + // distortion is hard-coded in HMDDeviceCreateDesc::GetDeviceInfo() + float DistortionK[6]; + + SensorDisplayInfoImpl(); + + void Unpack(); +}; + +//------------------------------------------------------------------------------------- +// ***** OVR::SensorDeviceImpl + +// Oculus Sensor interface. + +class SensorDeviceImpl : public HIDDeviceImpl<OVR::SensorDevice> +{ +public: + SensorDeviceImpl(SensorDeviceCreateDesc* createDesc); + ~SensorDeviceImpl(); + + + // DeviceCommaon interface + virtual bool Initialize(DeviceBase* parent); + virtual void Shutdown(); + + virtual void AddMessageHandler(MessageHandler* handler); + + // HIDDevice::Notifier interface. + virtual void OnInputReport(UByte* pData, UInt32 length); + virtual double OnTicks(double tickSeconds); + + // HMD-Mounted sensor has a different coordinate frame. + virtual void SetCoordinateFrame(CoordinateFrame coordframe); + virtual CoordinateFrame GetCoordinateFrame() const; + + // SensorDevice interface + virtual bool SetRange(const SensorRange& range, bool waitFlag); + virtual void GetRange(SensorRange* range) const; + + virtual void GetFactoryCalibration(Vector3f* AccelOffset, Vector3f* GyroOffset, + Matrix4f* AccelMatrix, Matrix4f* GyroMatrix, + float* Temperature); + virtual void SetOnboardCalibrationEnabled(bool enabled); + virtual bool IsMagCalibrated(); + + // Sets report rate (in Hz) of MessageBodyFrame messages (delivered through MessageHandler::OnMessage call). + // Currently supported maximum rate is 1000Hz. If the rate is set to 500 or 333 Hz then OnMessage will be + // called twice or thrice at the same 'tick'. + // If the rate is < 333 then the OnMessage / MessageBodyFrame will be called three + // times for each 'tick': the first call will contain averaged values, the second + // and third calls will provide with most recent two recorded samples. + virtual void SetReportRate(unsigned rateHz); + // Returns currently set report rate, in Hz. If 0 - error occurred. + // Note, this value may be different from the one provided for SetReportRate. The return + // value will contain the actual rate. + virtual unsigned GetReportRate() const; + + bool SetSerialReport(const SerialReport& data); + bool GetSerialReport(SerialReport* data); + + // Hack to create HMD device from sensor display info. + static void EnumerateHMDFromSensorDisplayInfo(const SensorDisplayInfoImpl& displayInfo, + DeviceFactory::EnumerateVisitor& visitor); + + // These methods actually store data in a JSON file + virtual bool SetMagCalibrationReport(const MagCalibrationReport& data); + virtual bool GetMagCalibrationReport(MagCalibrationReport* data); + +protected: + + virtual void openDevice(); + void closeDeviceOnError(); + + Void setCoordinateFrame(CoordinateFrame coordframe); + bool setRange(const SensorRange& range); + + Void setReportRate(unsigned rateHz); + + Void setOnboardCalibrationEnabled(bool enabled); + + bool setSerialReport(const SerialReport& data); + bool getSerialReport(SerialReport* data); + + // Called for decoded messages + void onTrackerMessage(TrackerMessage* message); + bool decodeTrackerMessage(TrackerMessage* message, UByte* buffer, int size); + + // Helpers to reduce casting. +/* + SensorDeviceCreateDesc* getCreateDesc() const + { return (SensorDeviceCreateDesc*)pCreateDesc.GetPtr(); } + + HIDDeviceDesc* getHIDDesc() const + { return &getCreateDesc()->HIDDesc; } +*/ + + // Set if the sensor is located on the HMD. + // Older prototype firmware doesn't support changing HW coordinates, + // so we track its state. + CoordinateFrame Coordinates; + CoordinateFrame HWCoordinates; + double NextKeepAliveTickSeconds; + + bool SequenceValid; + UInt16 LastTimestamp; + UByte LastSampleCount; + float LastTemperature; + Vector3f LastAcceleration; + Vector3f LastRotationRate; + Vector3f LastMagneticField; + + // This tracks wrap around, and should be monotonically increasing. + UInt32 FullTimestamp; + + // Current sensor range obtained from device. + SensorRange MaxValidRange; + SensorRange CurrentRange; + + // IMU calibration obtained from device. + Vector3f AccelCalibrationOffset; + Vector3f GyroCalibrationOffset; + Matrix4f AccelCalibrationMatrix; + Matrix4f GyroCalibrationMatrix; + float CalibrationTemperature; + + UInt16 OldCommandId; + + SensorTimeFilter TimeFilter; + double PrevAbsoluteTime; + +#ifdef OVR_OS_ANDROID + void replaceWithPhoneMag(Vector3f* val); + PhoneSensors* pPhoneSensors; +#endif + +private: + Matrix4f magCalibration; + bool magCalibrated; +}; + +} // namespace OVR + +#endif // OVR_SensorImpl_h diff --git a/LibOVR/Src/OVR_SensorImpl_Common.cpp b/LibOVR/Src/OVR_SensorImpl_Common.cpp new file mode 100644 index 0000000..99febe8 --- /dev/null +++ b/LibOVR/Src/OVR_SensorImpl_Common.cpp @@ -0,0 +1,245 @@ +/************************************************************************************ + +Filename : OVR_SensorImpl_Common.cpp +Content : Source common to SensorImpl and Sensor2Impl. +Created : January 21, 2014 +Authors : Lee Cooper + +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 "OVR_SensorImpl_Common.h" +#include "Kernel/OVR_Alg.h" + +namespace OVR +{ + +void UnpackSensor(const UByte* buffer, SInt32* x, SInt32* y, SInt32* z) +{ + // Sign extending trick + // from http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend + struct {SInt32 x:21;} s; + + *x = s.x = (buffer[0] << 13) | (buffer[1] << 5) | ((buffer[2] & 0xF8) >> 3); + *y = s.x = ((buffer[2] & 0x07) << 18) | (buffer[3] << 10) | (buffer[4] << 2) | + ((buffer[5] & 0xC0) >> 6); + *z = s.x = ((buffer[5] & 0x3F) << 15) | (buffer[6] << 7) | (buffer[7] >> 1); +} + +void PackSensor(UByte* buffer, SInt32 x, SInt32 y, SInt32 z) +{ + // Pack 3 32 bit integers into 8 bytes + buffer[0] = UByte(x >> 13); + buffer[1] = UByte(x >> 5); + buffer[2] = UByte((x << 3) | ((y >> 18) & 0x07)); + buffer[3] = UByte(y >> 10); + buffer[4] = UByte(y >> 2); + buffer[5] = UByte((y << 6) | ((z >> 15) & 0x3F)); + buffer[6] = UByte(z >> 7); + buffer[7] = UByte(z << 1); +} + +UInt16 SelectSensorRampValue(const UInt16* ramp, unsigned count, + float val, float factor, const char* label) +{ + UInt16 threshold = (UInt16)(val * factor); + + for (unsigned i = 0; i<count; i++) + { + if (ramp[i] >= threshold) + return ramp[i]; + } + OVR_DEBUG_LOG(("SensorDevice::SetRange - %s clamped to %0.4f", + label, float(ramp[count-1]) / factor)); + OVR_UNUSED2(factor, label); + return ramp[count-1]; +} + +SensorRangeImpl::SensorRangeImpl(const SensorRange& r, UInt16 commandId) +{ + SetSensorRange(r, commandId); +} + +void SensorRangeImpl::SetSensorRange(const SensorRange& r, UInt16 commandId) +{ + CommandId = commandId; + AccelScale = SelectSensorRampValue(AccelRangeRamp, sizeof(AccelRangeRamp)/sizeof(AccelRangeRamp[0]), + r.MaxAcceleration, (1.0f / 9.81f), "MaxAcceleration"); + GyroScale = SelectSensorRampValue(GyroRangeRamp, sizeof(GyroRangeRamp)/sizeof(GyroRangeRamp[0]), + r.MaxRotationRate, Math<float>::RadToDegreeFactor, "MaxRotationRate"); + MagScale = SelectSensorRampValue(MagRangeRamp, sizeof(MagRangeRamp)/sizeof(MagRangeRamp[0]), + r.MaxMagneticField, 1000.0f, "MaxMagneticField"); + Pack(); +} + +void SensorRangeImpl::GetSensorRange(SensorRange* r) +{ + r->MaxAcceleration = AccelScale * 9.81f; + r->MaxRotationRate = DegreeToRad((float)GyroScale); + r->MaxMagneticField= MagScale * 0.001f; +} + +SensorRange SensorRangeImpl::GetMaxSensorRange() +{ + return SensorRange(AccelRangeRamp[sizeof(AccelRangeRamp)/sizeof(AccelRangeRamp[0]) - 1] * 9.81f, + GyroRangeRamp[sizeof(GyroRangeRamp)/sizeof(GyroRangeRamp[0]) - 1] * + Math<float>::DegreeToRadFactor, + MagRangeRamp[sizeof(MagRangeRamp)/sizeof(MagRangeRamp[0]) - 1] * 0.001f); +} + +void SensorRangeImpl::Pack() +{ + Buffer[0] = 4; + Buffer[1] = UByte(CommandId & 0xFF); + Buffer[2] = UByte(CommandId >> 8); + Buffer[3] = UByte(AccelScale); + Buffer[4] = UByte(GyroScale & 0xFF); + Buffer[5] = UByte(GyroScale >> 8); + Buffer[6] = UByte(MagScale & 0xFF); + Buffer[7] = UByte(MagScale >> 8); +} + +void SensorRangeImpl::Unpack() +{ + CommandId = Buffer[1] | (UInt16(Buffer[2]) << 8); + AccelScale= Buffer[3]; + GyroScale = Buffer[4] | (UInt16(Buffer[5]) << 8); + MagScale = Buffer[6] | (UInt16(Buffer[7]) << 8); +} + +SensorConfigImpl::SensorConfigImpl() + : CommandId(0), Flags(0), PacketInterval(0), SampleRate(0) +{ + memset(Buffer, 0, PacketSize); + Buffer[0] = 2; +} + +void SensorConfigImpl::SetSensorCoordinates(bool sensorCoordinates) +{ + Flags = (Flags & ~Flag_SensorCoordinates) | (sensorCoordinates ? Flag_SensorCoordinates : 0); +} + +bool SensorConfigImpl::IsUsingSensorCoordinates() const +{ + return (Flags & Flag_SensorCoordinates) != 0; +} + +void SensorConfigImpl::Pack() +{ + Buffer[0] = 2; + Buffer[1] = UByte(CommandId & 0xFF); + Buffer[2] = UByte(CommandId >> 8); + Buffer[3] = Flags; + Buffer[4] = UByte(PacketInterval); + Buffer[5] = UByte(SampleRate & 0xFF); + Buffer[6] = UByte(SampleRate >> 8); +} + +void SensorConfigImpl::Unpack() +{ + CommandId = Buffer[1] | (UInt16(Buffer[2]) << 8); + Flags = Buffer[3]; + PacketInterval = Buffer[4]; + SampleRate = Buffer[5] | (UInt16(Buffer[6]) << 8); +} + +SensorFactoryCalibrationImpl::SensorFactoryCalibrationImpl() + : AccelOffset(), GyroOffset(), AccelMatrix(), GyroMatrix(), Temperature(0) +{ + memset(Buffer, 0, PacketSize); + Buffer[0] = 3; +} + +void SensorFactoryCalibrationImpl::Pack() +{ + SInt32 x, y, z; + + Buffer[0] = 3; + + x = SInt32(AccelOffset.x * 1e4f); + y = SInt32(AccelOffset.y * 1e4f); + z = SInt32(AccelOffset.z * 1e4f); + PackSensor(Buffer + 3, x, y, z); + + x = SInt32(GyroOffset.x * 1e4f); + y = SInt32(GyroOffset.y * 1e4f); + z = SInt32(GyroOffset.z * 1e4f); + PackSensor(Buffer + 11, x, y, z); + + // ignore the scale matrices for now +} + +void SensorFactoryCalibrationImpl::Unpack() +{ + static const float sensorMax = (1 << 20) - 1; + SInt32 x, y, z; + + UnpackSensor(Buffer + 3, &x, &y, &z); + AccelOffset.y = (float) y * 1e-4f; + AccelOffset.z = (float) z * 1e-4f; + AccelOffset.x = (float) x * 1e-4f; + + UnpackSensor(Buffer + 11, &x, &y, &z); + GyroOffset.x = (float) x * 1e-4f; + GyroOffset.y = (float) y * 1e-4f; + GyroOffset.z = (float) z * 1e-4f; + + for (int i = 0; i < 3; i++) + { + UnpackSensor(Buffer + 19 + 8 * i, &x, &y, &z); + AccelMatrix.M[i][0] = (float) x / sensorMax; + AccelMatrix.M[i][1] = (float) y / sensorMax; + AccelMatrix.M[i][2] = (float) z / sensorMax; + AccelMatrix.M[i][i] += 1.0f; + } + + for (int i = 0; i < 3; i++) + { + UnpackSensor(Buffer + 43 + 8 * i, &x, &y, &z); + GyroMatrix.M[i][0] = (float) x / sensorMax; + GyroMatrix.M[i][1] = (float) y / sensorMax; + GyroMatrix.M[i][2] = (float) z / sensorMax; + GyroMatrix.M[i][i] += 1.0f; + } + + Temperature = (float) Alg::DecodeSInt16(Buffer + 67) / 100.0f; +} + +SensorKeepAliveImpl::SensorKeepAliveImpl(UInt16 interval, UInt16 commandId) + : CommandId(commandId), KeepAliveIntervalMs(interval) +{ + Pack(); +} + +void SensorKeepAliveImpl::Pack() +{ + Buffer[0] = 8; + Buffer[1] = UByte(CommandId & 0xFF); + Buffer[2] = UByte(CommandId >> 8); + Buffer[3] = UByte(KeepAliveIntervalMs & 0xFF); + Buffer[4] = UByte(KeepAliveIntervalMs >> 8); +} + +void SensorKeepAliveImpl::Unpack() +{ + CommandId = Buffer[1] | (UInt16(Buffer[2]) << 8); + KeepAliveIntervalMs= Buffer[3] | (UInt16(Buffer[4]) << 8); +} + +} // namespace OVR diff --git a/LibOVR/Src/OVR_SensorImpl_Common.h b/LibOVR/Src/OVR_SensorImpl_Common.h new file mode 100644 index 0000000..a7091ba --- /dev/null +++ b/LibOVR/Src/OVR_SensorImpl_Common.h @@ -0,0 +1,150 @@ +/************************************************************************************ + +Filename : OVR_SensorImpl_Common.h +Content : Source common to SensorImpl and Sensor2Impl. +Created : January 21, 2014 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_SensorImpl_Common_h +#define OVR_SensorImpl_Common_h + +#include "Kernel/OVR_System.h" +#include "OVR_Device.h" + +namespace OVR +{ + +void UnpackSensor(const UByte* buffer, SInt32* x, SInt32* y, SInt32* z); +void PackSensor(UByte* buffer, SInt32 x, SInt32 y, SInt32 z); + +// Sensor HW only accepts specific maximum range values, used to maximize +// the 16-bit sensor outputs. Use these ramps to specify and report appropriate values. +const UInt16 AccelRangeRamp[] = { 2, 4, 8, 16 }; +const UInt16 GyroRangeRamp[] = { 250, 500, 1000, 2000 }; +const UInt16 MagRangeRamp[] = { 880, 1300, 1900, 2500 }; + +UInt16 SelectSensorRampValue(const UInt16* ramp, unsigned count, + float val, float factor, const char* label); + +// SensorScaleImpl provides buffer packing logic for the Sensor Range +// record that can be applied to DK1 sensor through Get/SetFeature. We expose this +// through SensorRange class, which has different units. +struct SensorRangeImpl +{ + enum { PacketSize = 8 }; + UByte Buffer[PacketSize]; + + UInt16 CommandId; + UInt16 AccelScale; + UInt16 GyroScale; + UInt16 MagScale; + + SensorRangeImpl(const SensorRange& r, UInt16 commandId = 0); + + void SetSensorRange(const SensorRange& r, UInt16 commandId = 0); + void GetSensorRange(SensorRange* r); + + static SensorRange GetMaxSensorRange(); + + void Pack(); + void Unpack(); +}; + +struct SensorConfigImpl +{ + enum { PacketSize = 7 }; + UByte Buffer[PacketSize]; + + // Flag values for Flags. + enum { + Flag_RawMode = 0x01, + Flag_CalibrationTest = 0x02, // Internal test mode + Flag_UseCalibration = 0x04, + Flag_AutoCalibration = 0x08, + Flag_MotionKeepAlive = 0x10, + Flag_CommandKeepAlive = 0x20, + Flag_SensorCoordinates = 0x40 + }; + + UInt16 CommandId; + UByte Flags; + UInt16 PacketInterval; // LDC - This should be a UByte. Fix when you have time to test it. + UInt16 SampleRate; + + SensorConfigImpl(); + + void SetSensorCoordinates(bool sensorCoordinates); + bool IsUsingSensorCoordinates() const; + + void Pack(); + void Unpack(); +}; + +struct SensorFactoryCalibrationImpl +{ + enum { PacketSize = 69 }; + UByte Buffer[PacketSize]; + + Vector3f AccelOffset; + Vector3f GyroOffset; + Matrix4f AccelMatrix; + Matrix4f GyroMatrix; + float Temperature; + + SensorFactoryCalibrationImpl(); + + void Pack(); // Not yet implemented. + void Unpack(); +}; + + +// SensorKeepAlive - feature report that needs to be sent at regular intervals for sensor +// to receive commands. +struct SensorKeepAliveImpl +{ + enum { PacketSize = 5 }; + UByte Buffer[PacketSize]; + + UInt16 CommandId; + UInt16 KeepAliveIntervalMs; + + SensorKeepAliveImpl(UInt16 interval = 0, UInt16 commandId = 0); + + void Pack(); + void Unpack(); +}; + +struct TrackerSample +{ + SInt32 AccelX, AccelY, AccelZ; + SInt32 GyroX, GyroY, GyroZ; +}; + +enum LastCommandIdFlags +{ + LastCommandId_Shutter = 1, + LastCommandId_LEDs = 2 +}; + +} // namespace OVR + +#endif // OVR_SensorImpl_Common_h diff --git a/LibOVR/Src/OVR_SensorTimeFilter.cpp b/LibOVR/Src/OVR_SensorTimeFilter.cpp new file mode 100644 index 0000000..ee0c385 --- /dev/null +++ b/LibOVR/Src/OVR_SensorTimeFilter.cpp @@ -0,0 +1,385 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_SensorTimeFilter.cpp +Content : Class to filter HMD time and convert it to system time +Created : December 20, 2013 +Author : Michael Antonov +Notes : + +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 "OVR_SensorTimeFilter.h" +#include "Kernel/OVR_Log.h" + + +#include <stdio.h> +#include <math.h> + +namespace OVR { + +// Comment out for debug logging to file +//#define OVR_TIMEFILTER_LOG_CODE( code ) code +#define OVR_TIMEFILTER_LOG_CODE( code ) + +#if defined(OVR_OS_ANDROID) + #define OVR_TIMEFILTER_LOG_FILENAME "/sdcard/TimeFilterLog.txt" +#elif defined(OVR_OS_WIN32) + #define OVR_TIMEFILTER_LOG_FILENAME "C:\\TimeFilterLog.txt" +#else + #define OVR_TIMEFILTER_LOG_FILENAME "TimeFilterLog.txt" +#endif + +OVR_TIMEFILTER_LOG_CODE( FILE* pTFLogFile = 0; ) + + +// Ideally, the following would always be true: +// - NewSampleTime > PrevSample +// - NewSampleTime < now systemTime +// - (NewSampleTime - PrevSampleTime) == integration delta, matching +// HW sample time difference + drift +// +// In practice, these issues affect us: +// - System thread can be suspended for a while +// - System de-buffering of recorded samples cause deviceTime to advance up +// much faster then system time for ~100+ samples +// - Device (DK1) and system clock granularities are high; this can +// lead to potentially having estimations in the future +// + + +// ***** TimerFilter + +SensorTimeFilter::SensorTimeFilter(const Settings& settings) +{ + FilterSettings = settings; + + ClockInitialized = false; + ClockDelta = 0; + ClockDeltaDriftPerSecond = 0; + ClockDeltaCorrectPerSecond = 0; + ClockDeltaCorrectSecondsLeft = 0; + OldClockDeltaDriftExpire = 0; + + LastLargestDeviceTime = 0; + PrevSystemTime = 0; + PastSampleResetTime = 0; + + MinWindowsCollected = 0; + MinWindowDuration = 0; // assigned later + MinWindowLastTime = 0; + MinWindowSamples = settings.MinSamples; // Force initialization + + OVR_TIMEFILTER_LOG_CODE( pTFLogFile = fopen(OVR_TIMEFILTER_LOG_FILENAME, "w+"); ) +} + + +double SensorTimeFilter::SampleToSystemTime(double sampleDeviceTime, double systemTime, + double prevResult, const char* debugTag) +{ + double clockDelta = systemTime - sampleDeviceTime + FilterSettings.ClockDeltaAdjust; + double deviceTimeDelta = sampleDeviceTime - LastLargestDeviceTime; + double result; + + // Collect a sample ClockDelta for a "MinimumWindow" or process + // the window by adjusting drift rates if it's full of samples. + // - (deviceTimeDelta < 1.0f) is a corner cases, as it would imply timestamp skip/wrap. + + if (ClockInitialized) + { + // Samples in the past commonly occur if they come from separately incrementing + // data channels. Just adjust them with ClockDelta. + + if (deviceTimeDelta < 0.0) + { + result = sampleDeviceTime + ClockDelta; + + if (result > (prevResult - 0.00001)) + goto clamp_and_log_result; + + // Consistent samples less then prevResult for indicate a back-jump or bad input. + // In this case we return prevResult for a while, then reset filter if it keeps going. + if (PastSampleResetTime < 0.0001) + { + PastSampleResetTime = systemTime + FilterSettings.PastSampleResetSeconds; + goto clamp_and_log_result; + } + else if (systemTime > PastSampleResetTime) + { + OVR_DEBUG_LOG(("SensorTimeFilter - Filtering reset due to samples in the past!\n")); + initClockSampling(sampleDeviceTime, clockDelta); + // Fall through to below, to ' PastSampleResetTime = 0.0; ' + } + else + { + goto clamp_and_log_result; + } + } + + // Most common case: Record window sample. + else if ( (deviceTimeDelta < 1.0f) && + ( (sampleDeviceTime < MinWindowLastTime) || + (MinWindowSamples < FilterSettings.MinSamples) ) ) + { + // Pick minimum ClockDelta sample. + if (clockDelta < MinWindowClockDelta) + MinWindowClockDelta = clockDelta; + MinWindowSamples++; + } + else + { + processFinishedMinWindow(sampleDeviceTime, clockDelta); + } + + PastSampleResetTime = 0.0; + } + else + { + initClockSampling(sampleDeviceTime, clockDelta); + } + + + // Clock adjustment for drift. + ClockDelta += ClockDeltaDriftPerSecond * deviceTimeDelta; + + // ClockDelta "nudging" towards last known MinWindowClockDelta. + if (ClockDeltaCorrectSecondsLeft > 0.000001) + { + double correctTimeDelta = deviceTimeDelta; + if (deviceTimeDelta > ClockDeltaCorrectSecondsLeft) + correctTimeDelta = ClockDeltaCorrectSecondsLeft; + ClockDeltaCorrectSecondsLeft -= correctTimeDelta; + + ClockDelta += ClockDeltaCorrectPerSecond * correctTimeDelta; + } + + // Record largest device time, so we know what samples to use in accumulation + // of min-window in the future. + LastLargestDeviceTime = sampleDeviceTime; + + // Compute our resulting sample time after ClockDelta adjustment. + result = sampleDeviceTime + ClockDelta; + + +clamp_and_log_result: + + OVR_TIMEFILTER_LOG_CODE( double savedResult = result; ) + + // Clamp to ensure that result >= PrevResult, or not to far in the future. + // Future clamp primarily happens in the very beginning if we are de-queuing + // system buffer full of samples. + if (result < prevResult) + { + result = prevResult; + } + if (result > (systemTime + FilterSettings.FutureClamp)) + { + result = (systemTime + FilterSettings.FutureClamp); + } + + OVR_TIMEFILTER_LOG_CODE( + + // Tag lines that were outside desired range, with '<' or '>'. + char rangeClamp = ' '; + char resultDeltaFar = ' '; + + if (savedResult > (systemTime + 0.0000001)) + rangeClamp = '>'; + if (savedResult < prevResult) + rangeClamp = '<'; + + // Tag any result delta outside desired threshold with a '*'. + if (fabs(deviceTimeDelta - (result - prevResult)) >= 0.00002) + resultDeltaFar = '*'; + + fprintf(pTFLogFile, "Res%s = %13.7f, dt = % 8.7f, ClkD = %13.6f " + "sysT = %13.6f, sysDt = %f, " + "sysDiff = % f, devT = %11.6f, ddevT = %9.6f %c%c\n", + debugTag, result, result - prevResult, ClockDelta, + systemTime, systemTime - PrevSystemTime, + -(systemTime - result), // Negatives in the past, positive > now. + sampleDeviceTime, deviceTimeDelta, rangeClamp, resultDeltaFar); + + ) // OVR_TIMEFILTER_LOG_CODE() + OVR_UNUSED(debugTag); + + // Record prior values. Useful or logging and clamping. + PrevSystemTime = systemTime; + + return result; +} + + +void SensorTimeFilter::initClockSampling(double sampleDeviceTime, double clockDelta) +{ + ClockInitialized = true; + ClockDelta = clockDelta; + ClockDeltaDriftPerSecond = 0; + OldClockDeltaDriftExpire = 0; + ClockDeltaCorrectSecondsLeft = 0; + ClockDeltaCorrectPerSecond = 0; + + MinWindowsCollected = 0; + MinWindowDuration = 0.25; + MinWindowClockDelta = clockDelta; + MinWindowLastTime = sampleDeviceTime + MinWindowDuration; + MinWindowSamples = 0; +} + + +void SensorTimeFilter::processFinishedMinWindow(double sampleDeviceTime, double clockDelta) +{ + MinRecord newRec = { MinWindowClockDelta, sampleDeviceTime }; + + double clockDeltaDiff = MinWindowClockDelta - ClockDelta; + double absClockDeltaDiff = fabs(clockDeltaDiff); + + + // Abrupt change causes Reset of minClockDelta collection. + // > 8 ms would a Large jump in a minimum sample, as those are usually stable. + // > 1 second intantaneous jump would land us here as well, as that would imply + // device being suspended, clock wrap or some other unexpected issue. + if ((absClockDeltaDiff > 0.008) || + ((sampleDeviceTime - LastLargestDeviceTime) >= 1.0)) + { + OVR_TIMEFILTER_LOG_CODE( + fprintf(pTFLogFile, + "\nMinWindow Finished: %d Samples, MinWindowClockDelta=%f, MW-CD=%f," + " ** ClockDelta Reset **\n\n", + MinWindowSamples, MinWindowClockDelta, MinWindowClockDelta-ClockDelta); + ) + + // Use old collected ClockDeltaDriftPerSecond drift value + // up to 1 minute until we collect better samples. + if (!MinRecords.IsEmpty()) + { + OldClockDeltaDriftExpire = MinRecords.GetNewest().LastSampleDeviceTime - + MinRecords.GetOldest().LastSampleDeviceTime; + if (OldClockDeltaDriftExpire > 60.0) + OldClockDeltaDriftExpire = 60.0; + OldClockDeltaDriftExpire += sampleDeviceTime; + } + + // Jump to new ClockDelta value. + if ((sampleDeviceTime - LastLargestDeviceTime) > 1.0) + ClockDelta = clockDelta; + else + ClockDelta = MinWindowClockDelta; + + ClockDeltaCorrectSecondsLeft = 0; + ClockDeltaCorrectPerSecond = 0; + + // Reset buffers, we'll be collecting a new MinWindow. + MinRecords.Reset(); + MinWindowsCollected = 0; + MinWindowDuration = 0.25; + MinWindowSamples = 0; + } + else + { + OVR_ASSERT(MinWindowSamples >= FilterSettings.MinSamples); + + double timeElapsed = 0; + + // If we have older values, use them to update clock drift in + // ClockDeltaDriftPerSecond + if (!MinRecords.IsEmpty() && (sampleDeviceTime > OldClockDeltaDriftExpire)) + { + MinRecord rec = MinRecords.GetOldest(); + + // Compute clock rate of drift. + timeElapsed = sampleDeviceTime - rec.LastSampleDeviceTime; + + // Check for divide by zero shouldn't be necessary here, but just be be safe... + if (timeElapsed > 0.000001) + { + ClockDeltaDriftPerSecond = (MinWindowClockDelta - rec.MinClockDelta) / timeElapsed; + ClockDeltaDriftPerSecond = clampRate(ClockDeltaDriftPerSecond, + FilterSettings.MaxChangeRate); + } + else + { + ClockDeltaDriftPerSecond = 0.0; + } + } + + MinRecords.AddRecord(newRec); + + + // Catchup correction nudges ClockDelta towards MinWindowClockDelta. + // These are needed because clock drift correction alone is not enough + // for past accumulated error/high-granularity clock delta changes. + // The further away we are, the stronger correction we apply. + // Correction has timeout, as we don't want it to overshoot in case + // of a large delay between samples. + + if (absClockDeltaDiff >= 0.00125) + { + // Correct large discrepancy immediately. + if (absClockDeltaDiff > 0.00175) + { + if (clockDeltaDiff > 0) + ClockDelta += (clockDeltaDiff - 0.00175); + else + ClockDelta += (clockDeltaDiff + 0.00175); + + clockDeltaDiff = MinWindowClockDelta - ClockDelta; + } + + ClockDeltaCorrectPerSecond = clockDeltaDiff; + ClockDeltaCorrectSecondsLeft = 1.0; + } + else if (absClockDeltaDiff > 0.0005) + { + ClockDeltaCorrectPerSecond = clockDeltaDiff / 8.0; + ClockDeltaCorrectSecondsLeft = 8.0; + } + else + { + ClockDeltaCorrectPerSecond = clockDeltaDiff / 15.0; + ClockDeltaCorrectSecondsLeft = 15.0; + } + + ClockDeltaCorrectPerSecond = clampRate(ClockDeltaCorrectPerSecond, + FilterSettings.MaxCorrectRate); + + OVR_TIMEFILTER_LOG_CODE( + fprintf(pTFLogFile, + "\nMinWindow Finished: %d Samples, MinWindowClockDelta=%f, MW-CD=%f," + " tileElapsed=%f, ClockChange=%f, ClockCorrect=%f\n\n", + MinWindowSamples, MinWindowClockDelta, MinWindowClockDelta-ClockDelta, + timeElapsed, ClockDeltaDriftPerSecond, ClockDeltaCorrectPerSecond); + ) + } + + // New MinClockDelta collection window. + // Switch to longer duration after first few windows. + MinWindowsCollected ++; + if (MinWindowsCollected > 5) + MinWindowDuration = 0.5; + + MinWindowClockDelta = clockDelta; + MinWindowLastTime = sampleDeviceTime + MinWindowDuration; + MinWindowSamples = 0; +} + + +} // namespace OVR + diff --git a/LibOVR/Src/OVR_SensorTimeFilter.h b/LibOVR/Src/OVR_SensorTimeFilter.h new file mode 100644 index 0000000..409fe66 --- /dev/null +++ b/LibOVR/Src/OVR_SensorTimeFilter.h @@ -0,0 +1,226 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_SensorTimeFilter.h +Content : Class to filter HMD time and convert it to system time +Created : December 20, 2013 +Author : Michael Antonov +Notes : + +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. + +************************************************************************************/ + +#ifndef OVR_SensorTimeFilter_h +#define OVR_SensorTimeFilter_h + +#include "Kernel/OVR_Types.h" + +namespace OVR { + + +//----------------------------------------------------------------------------------- +// ***** SensorTimeFilter + +// SensorTimeFilter converts sample device time, in seconds, to absolute system +// time. It filter maintains internal state to estimate the following: +// +// - Difference between system and device time values (ClockDelta). +// ~= (systemTime - deviceTime) +// - Drift rate between system and device clocks (ClockDeltaDriftPerSecond). +// +// Additionally, the following criteria are enforced: +// - Resulting samples must be increasing, compared to prevSample. +// - Returned sample time should not exceed 'now' system time by more then a fixed +// value. +// * Ideally this should be 0, however, enforcing this is hard when clocks +// have high discrete values. +// - Returned sample AbsoluteTime values deltas are very close to HW samples, +// adjusted by drift rate. Note that this is not always possible due to clamping, +// in which case it is better to use ScaleTimeUnit(deviceTimeDelta) +// for integration. +// +// Algorithm: We collect minimum ClockDelta on windows of +// consecutive samples (500 ms each set). Long term difference between sample +// set minimums is drift. ClockDelta is also continually nudged towards most recent +// minimum. + +class SensorTimeFilter +{ +public: + + // It may be desirable to configure these per device/platform. + // For example, rates can be tighter for DK2 because of microsecond clock. + struct Settings + { + Settings(int minSamples = 50, + double clockDeltaAdjust = -0.0002, // 200 mks in the past. + double futureClamp = 0.0008) + : MinSamples(minSamples), + ClockDeltaAdjust(clockDeltaAdjust), + // PastClamp(-0.032), + FutureClamp(futureClamp), + PastSampleResetSeconds(0.2), + MaxChangeRate(0.004), + MaxCorrectRate(0.004) + { } + + // Minimum number of samples in a window. Different number may be desirable + // based on how often samples come in. + int MinSamples; + + // Factor always added to ClockDelta, used to skew all values into the past by fixed + // value and reduce the chances we report a sample "in the future". + double ClockDeltaAdjust; + // How much away in a past can a sample be before being shifted closer to system time. + //double PastClamp; + // How much larger then systemTime can a value be? Set to 0 to clamp to null, + // put small positive value is better. + double FutureClamp; + + // How long (in system time) do we take to reset the system if a device sample. + // comes in the past. Generally, this should never happened, but exists as a way to + // address bad timing coming form firmware (temp CCove issue, presumably fixed) + // or buggy input. + double PastSampleResetSeconds; + + // Maximum drift change and near-term correction rates, in seconds. + double MaxChangeRate; + double MaxCorrectRate; + }; + + + SensorTimeFilter(const Settings& settings = Settings()); + + + // Convert device sample time to system time, driving clock drift estimation. + // Input: SampleTime, System Time + // Return: Absolute system time for sample + double SampleToSystemTime(double sampleDeviceTime, double systemTime, + double prevResult, const char* debugTag = ""); + + + // Scales device time to account for drift. + double ScaleTimeUnit(double deviceClockDelta) + { + return deviceClockDelta * (1.0 + ClockDeltaDriftPerSecond); + } + + // Return currently estimated difference between the clocks. + double GetClockDelta() const { return ClockDelta; } + + +private: + + void initClockSampling(double sampleDeviceTime, double clockDelta); + void processFinishedMinWindow(double sampleDeviceTime, double systemTime); + + static double clampRate(double rate, double limit) + { + if (rate > limit) + rate = limit; + else if (rate < -limit) + rate = -limit; + return rate; + } + + + // Describes minimum observed ClockDelta for sample set seen in the past. + struct MinRecord + { + double MinClockDelta; + double LastSampleDeviceTime; + }; + + // Circular buffer storing MinRecord(s) several minutes into the past. + // Oldest value here is used to help estimate drift. + class MinRecordBuffer + { + enum { BufferSize = 60*6 }; // 3 min + public: + + MinRecordBuffer() : Head(0), Tail(0) { } + + void Reset() { Head = Tail = 0; } + bool IsEmpty() const { return Head == Tail; } + + const MinRecord& GetOldest() const + { + OVR_ASSERT(!IsEmpty()); + return Records[Tail]; + } + const MinRecord& GetNewest() const + { + OVR_ASSERT(!IsEmpty()); + return Records[(BufferSize + Head - 1) % BufferSize]; + } + + void AddRecord(const MinRecord& rec) + { + Records[Head] = rec; + Head = advanceIndex(Head); + if (Head == Tail) + Tail = advanceIndex(Tail); + } + + private: + + static int advanceIndex(int index) + { + index++; + if (index >= BufferSize) + index = 0; + return index; + } + + MinRecord Records[BufferSize]; + int Head; // Location we will most recent entry, unused. + int Tail; // Oldest entry. + }; + + + Settings FilterSettings; + + // Clock correction state. + bool ClockInitialized; + double ClockDelta; + double ClockDeltaDriftPerSecond; + double ClockDeltaCorrectPerSecond; + double ClockDeltaCorrectSecondsLeft; + double OldClockDeltaDriftExpire; + + double LastLargestDeviceTime; + double PrevSystemTime; + // Used to reset timing if we get multiple "samples in the past" + double PastSampleResetTime; + + // "MinWindow" is a block of time during which minimum ClockDelta values + // are collected into MinWindowClockDelta. + int MinWindowsCollected; + double MinWindowDuration; // Device sample seconds + double MinWindowLastTime; + double MinWindowClockDelta; + int MinWindowSamples; + + // Historic buffer used to determine rate of clock change over time. + MinRecordBuffer MinRecords; +}; + +} // namespace OVR + +#endif // OVR_SensorTimeFilter_h diff --git a/LibOVR/Src/OVR_Stereo.cpp b/LibOVR/Src/OVR_Stereo.cpp new file mode 100644 index 0000000..936a02a --- /dev/null +++ b/LibOVR/Src/OVR_Stereo.cpp @@ -0,0 +1,1805 @@ +/************************************************************************************ + +Filename : OVR_Stereo.cpp +Content : Stereo rendering functions +Created : November 30, 2013 +Authors : Tom Fosyth + +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 "OVR_Stereo.h" +#include "OVR_Profile.h" +#include "Kernel/OVR_Log.h" +#include "Kernel/OVR_Alg.h" + +//To allow custom distortion to be introduced to CatMulSpline. +float (*CustomDistortion)(float) = NULL; +float (*CustomDistortionInv)(float) = NULL; + + +namespace OVR { + + +using namespace Alg; + +//----------------------------------------------------------------------------------- + +// Inputs are 4 points (pFitX[0],pFitY[0]) through (pFitX[3],pFitY[3]) +// Result is four coefficients in pResults[0] through pResults[3] such that +// y = pResult[0] + x * ( pResult[1] + x * ( pResult[2] + x * ( pResult[3] ) ) ); +// passes through all four input points. +// Return is true if it succeeded, false if it failed (because two control points +// have the same pFitX value). +bool FitCubicPolynomial ( float *pResult, const float *pFitX, const float *pFitY ) +{ + float d0 = ( ( pFitX[0]-pFitX[1] ) * ( pFitX[0]-pFitX[2] ) * ( pFitX[0]-pFitX[3] ) ); + float d1 = ( ( pFitX[1]-pFitX[2] ) * ( pFitX[1]-pFitX[3] ) * ( pFitX[1]-pFitX[0] ) ); + float d2 = ( ( pFitX[2]-pFitX[3] ) * ( pFitX[2]-pFitX[0] ) * ( pFitX[2]-pFitX[1] ) ); + float d3 = ( ( pFitX[3]-pFitX[0] ) * ( pFitX[3]-pFitX[1] ) * ( pFitX[3]-pFitX[2] ) ); + + if ( ( d0 == 0.0f ) || ( d1 == 0.0f ) || ( d2 == 0.0f ) || ( d3 == 0.0f ) ) + { + return false; + } + + float f0 = pFitY[0] / d0; + float f1 = pFitY[1] / d1; + float f2 = pFitY[2] / d2; + float f3 = pFitY[3] / d3; + + pResult[0] = -( f0*pFitX[1]*pFitX[2]*pFitX[3] + + f1*pFitX[0]*pFitX[2]*pFitX[3] + + f2*pFitX[0]*pFitX[1]*pFitX[3] + + f3*pFitX[0]*pFitX[1]*pFitX[2] ); + pResult[1] = f0*(pFitX[1]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[1]) + + f1*(pFitX[0]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[0]) + + f2*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[3] + pFitX[3]*pFitX[0]) + + f3*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[2] + pFitX[2]*pFitX[0]); + pResult[2] = -( f0*(pFitX[1]+pFitX[2]+pFitX[3]) + + f1*(pFitX[0]+pFitX[2]+pFitX[3]) + + f2*(pFitX[0]+pFitX[1]+pFitX[3]) + + f3*(pFitX[0]+pFitX[1]+pFitX[2]) ); + pResult[3] = f0 + f1 + f2 + f3; + + return true; +} + + + +float EvalCatmullRom10Spline ( float const *K, float scaledVal ) +{ + int const NumSegments = LensConfig::NumCoefficients; + + float scaledValFloor = floorf ( scaledVal ); + scaledValFloor = Alg::Max ( 0.0f, Alg::Min ( (float)(NumSegments-1), scaledValFloor ) ); + float t = scaledVal - scaledValFloor; + int k = (int)scaledValFloor; + + float p0, p1; + float m0, m1; + switch ( k ) + { + case 0: + // Curve starts at 1.0 with gradient K[1]-K[0] + p0 = 1.0f; + m0 = ( K[1] - K[0] ); // general case would have been (K[1]-K[-1])/2 + p1 = K[1]; + m1 = 0.5f * ( K[2] - K[0] ); + break; + default: + // General case + p0 = K[k ]; + m0 = 0.5f * ( K[k+1] - K[k-1] ); + p1 = K[k+1]; + m1 = 0.5f * ( K[k+2] - K[k ] ); + break; + case NumSegments-2: + // Last tangent is just the slope of the last two points. + p0 = K[NumSegments-2]; + m0 = 0.5f * ( K[NumSegments-1] - K[NumSegments-2] ); + p1 = K[NumSegments-1]; + m1 = K[NumSegments-1] - K[NumSegments-2]; + break; + case NumSegments-1: + // Beyond the last segment it's just a straight line + p0 = K[NumSegments-1]; + m0 = K[NumSegments-1] - K[NumSegments-2]; + p1 = p0 + m0; + m1 = m0; + break; + } + + float omt = 1.0f - t; + float res = ( p0 * ( 1.0f + 2.0f * t ) + m0 * t ) * omt * omt + + ( p1 * ( 1.0f + 2.0f * omt ) - m1 * omt ) * t * t; + + return res; +} + + + + +// Converts a Profile eyecup string into an eyecup enumeration +void SetEyeCup(HmdRenderInfo* renderInfo, const char* cup) +{ + if (OVR_strcmp(cup, "A") == 0) + renderInfo->EyeCups = EyeCup_DK1A; + else if (OVR_strcmp(cup, "B") == 0) + renderInfo->EyeCups = EyeCup_DK1B; + else if (OVR_strcmp(cup, "C") == 0) + renderInfo->EyeCups = EyeCup_DK1C; + else if (OVR_strcmp(cup, "Orange A") == 0) + renderInfo->EyeCups = EyeCup_OrangeA; + else if (OVR_strcmp(cup, "Red A") == 0) + renderInfo->EyeCups = EyeCup_RedA; + else if (OVR_strcmp(cup, "Pink A") == 0) + renderInfo->EyeCups = EyeCup_PinkA; + else if (OVR_strcmp(cup, "Blue A") == 0) + renderInfo->EyeCups = EyeCup_BlueA; + else + renderInfo->EyeCups = EyeCup_DK1A; +} + + + +//----------------------------------------------------------------------------------- + + +// The result is a scaling applied to the distance. +float LensConfig::DistortionFnScaleRadiusSquared (float rsq) const +{ + float scale = 1.0f; + switch ( Eqn ) + { + case Distortion_Poly4: + // This version is deprecated! Prefer one of the other two. + scale = ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) ); + break; + case Distortion_RecipPoly4: + scale = 1.0f / ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) ); + break; + case Distortion_CatmullRom10:{ + // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[10] + // evenly spaced in R^2 from 0.0 to MaxR^2 + // K[0] controls the slope at radius=0.0, rather than the actual value. + const int NumSegments = LensConfig::NumCoefficients; + OVR_ASSERT ( NumSegments <= NumCoefficients ); + float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxR * MaxR ); + scale = EvalCatmullRom10Spline ( K, scaledRsq ); + + + //Intercept, and overrule if needed + if (CustomDistortion) + { + scale = CustomDistortion(rsq); + } + + }break; + default: + OVR_ASSERT ( false ); + break; + } + return scale; +} + +// x,y,z components map to r,g,b +Vector3f LensConfig::DistortionFnScaleRadiusSquaredChroma (float rsq) const +{ + float scale = DistortionFnScaleRadiusSquared ( rsq ); + Vector3f scaleRGB; + scaleRGB.x = scale * ( 1.0f + ChromaticAberration[0] + rsq * ChromaticAberration[1] ); // Red + scaleRGB.y = scale; // Green + scaleRGB.z = scale * ( 1.0f + ChromaticAberration[2] + rsq * ChromaticAberration[3] ); // Blue + return scaleRGB; +} + +// DistortionFnInverse computes the inverse of the distortion function on an argument. +float LensConfig::DistortionFnInverse(float r) const +{ + OVR_ASSERT((r <= 20.0f)); + + float s, d; + float delta = r * 0.25f; + + // Better to start guessing too low & take longer to converge than too high + // and hit singularities. Empirically, r * 0.5f is too high in some cases. + s = r * 0.25f; + d = fabs(r - DistortionFn(s)); + + for (int i = 0; i < 20; i++) + { + float sUp = s + delta; + float sDown = s - delta; + float dUp = fabs(r - DistortionFn(sUp)); + float dDown = fabs(r - DistortionFn(sDown)); + + if (dUp < d) + { + s = sUp; + d = dUp; + } + else if (dDown < d) + { + s = sDown; + d = dDown; + } + else + { + delta *= 0.5f; + } + } + + return s; +} + + + +float LensConfig::DistortionFnInverseApprox(float r) const +{ + float rsq = r * r; + float scale = 1.0f; + switch ( Eqn ) + { + case Distortion_Poly4: + // Deprecated + OVR_ASSERT ( false ); + break; + case Distortion_RecipPoly4: + scale = 1.0f / ( InvK[0] + rsq * ( InvK[1] + rsq * ( InvK[2] + rsq * InvK[3] ) ) ); + break; + case Distortion_CatmullRom10:{ + // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[9] + // evenly spaced in R^2 from 0.0 to MaxR^2 + // K[0] controls the slope at radius=0.0, rather than the actual value. + const int NumSegments = LensConfig::NumCoefficients; + OVR_ASSERT ( NumSegments <= NumCoefficients ); + float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxInvR * MaxInvR ); + scale = EvalCatmullRom10Spline ( InvK, scaledRsq ); + + //Intercept, and overrule if needed + if (CustomDistortionInv) + { + scale = CustomDistortionInv(rsq); + } + + }break; + default: + OVR_ASSERT ( false ); + break; + } + return r * scale; +} + +void LensConfig::SetUpInverseApprox() +{ + float maxR = MaxInvR; + + switch ( Eqn ) + { + case Distortion_Poly4: + // Deprecated + OVR_ASSERT ( false ); + break; + case Distortion_RecipPoly4:{ + + float sampleR[4]; + float sampleRSq[4]; + float sampleInv[4]; + float sampleFit[4]; + + // Found heuristically... + sampleR[0] = 0.0f; + sampleR[1] = maxR * 0.4f; + sampleR[2] = maxR * 0.8f; + sampleR[3] = maxR * 1.5f; + for ( int i = 0; i < 4; i++ ) + { + sampleRSq[i] = sampleR[i] * sampleR[i]; + sampleInv[i] = DistortionFnInverse ( sampleR[i] ); + sampleFit[i] = sampleR[i] / sampleInv[i]; + } + sampleFit[0] = 1.0f; + FitCubicPolynomial ( InvK, sampleRSq, sampleFit ); + + #if 0 + // Should be a nearly exact match on the chosen points. + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[0] ) - DistortionFnInverseApprox ( sampleR[0] ) ) / maxR < 0.0001f ); + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[1] ) - DistortionFnInverseApprox ( sampleR[1] ) ) / maxR < 0.0001f ); + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[2] ) - DistortionFnInverseApprox ( sampleR[2] ) ) / maxR < 0.0001f ); + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[3] ) - DistortionFnInverseApprox ( sampleR[3] ) ) / maxR < 0.0001f ); + // Should be a decent match on the rest of the range. + const int maxCheck = 20; + for ( int i = 0; i < maxCheck; i++ ) + { + float checkR = (float)i * maxR / (float)maxCheck; + float realInv = DistortionFnInverse ( checkR ); + float testInv = DistortionFnInverseApprox ( checkR ); + float error = fabsf ( realInv - testInv ) / maxR; + OVR_ASSERT ( error < 0.1f ); + } + #endif + + }break; + case Distortion_CatmullRom10:{ + + const int NumSegments = LensConfig::NumCoefficients; + OVR_ASSERT ( NumSegments <= NumCoefficients ); + for ( int i = 1; i < NumSegments; i++ ) + { + float scaledRsq = (float)i; + float rsq = scaledRsq * MaxInvR * MaxInvR / (float)( NumSegments - 1); + float r = sqrtf ( rsq ); + float inv = DistortionFnInverse ( r ); + InvK[i] = inv / r; + InvK[0] = 1.0f; // TODO: fix this. + } + +#if 0 + const int maxCheck = 20; + for ( int i = 0; i <= maxCheck; i++ ) + { + float checkR = (float)i * MaxInvR / (float)maxCheck; + float realInv = DistortionFnInverse ( checkR ); + float testInv = DistortionFnInverseApprox ( checkR ); + float error = fabsf ( realInv - testInv ) / MaxR; + OVR_ASSERT ( error < 0.01f ); + } +#endif + + }break; + + default: + break; + } +} + + +void LensConfig::SetToIdentity() +{ + for ( int i = 0; i < NumCoefficients; i++ ) + { + K[i] = 0.0f; + InvK[i] = 0.0f; + } + Eqn = Distortion_RecipPoly4; + K[0] = 1.0f; + InvK[0] = 1.0f; + MaxR = 1.0f; + MaxInvR = 1.0f; + ChromaticAberration[0] = 0.0f; + ChromaticAberration[1] = 0.0f; + ChromaticAberration[2] = 0.0f; + ChromaticAberration[3] = 0.0f; + MetersPerTanAngleAtCenter = 0.05f; +} + + +enum LensConfigStoredVersion +{ + LCSV_CatmullRom10Version1 = 1 +}; + +// DO NOT CHANGE THESE ONCE THEY HAVE BEEN BAKED INTO FIRMWARE. +// If something needs to change, add a new one! +struct LensConfigStored_CatmullRom10Version1 +{ + // All these items must be fixed-length integers - no "float", no "int", etc. + UInt16 VersionNumber; // Must be LCSV_CatmullRom10Version1 + + UInt16 K[11]; + UInt16 MaxR; + UInt16 MetersPerTanAngleAtCenter; + UInt16 ChromaticAberration[4]; + // InvK and MaxInvR are calculated on load. +}; + +UInt16 EncodeFixedPointUInt16 ( float val, UInt16 zeroVal, int fractionalBits ) +{ + OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) ); + float valWhole = val * (float)( 1 << fractionalBits ); + valWhole += (float)zeroVal + 0.5f; + valWhole = floorf ( valWhole ); + OVR_ASSERT ( ( valWhole >= 0.0f ) && ( valWhole < (float)( 1 << 16 ) ) ); + return (UInt16)valWhole; +} + +float DecodeFixedPointUInt16 ( UInt16 val, UInt16 zeroVal, int fractionalBits ) +{ + OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) ); + float valFloat = (float)val; + valFloat -= (float)zeroVal; + valFloat *= 1.0f / (float)( 1 << fractionalBits ); + return valFloat; +} + + +// Returns true on success. +bool LoadLensConfig ( LensConfig *presult, UByte const *pbuffer, int bufferSizeInBytes ) +{ + if ( bufferSizeInBytes < 2 ) + { + // Can't even tell the version number! + return false; + } + UInt16 version = DecodeUInt16 ( pbuffer + 0 ); + switch ( version ) + { + case LCSV_CatmullRom10Version1: + { + if ( bufferSizeInBytes < (int)sizeof(LensConfigStored_CatmullRom10Version1) ) + { + return false; + } + LensConfigStored_CatmullRom10Version1 lcs; + lcs.VersionNumber = DecodeUInt16 ( pbuffer + 0 ); + for ( int i = 0; i < 11; i++ ) + { + lcs.K[i] = DecodeUInt16 ( pbuffer + 2 + 2*i ); + } + lcs.MaxR = DecodeUInt16 ( pbuffer + 24 ); + lcs.MetersPerTanAngleAtCenter = DecodeUInt16 ( pbuffer + 26 ); + for ( int i = 0; i < 4; i++ ) + { + lcs.ChromaticAberration[i] = DecodeUInt16 ( pbuffer + 28 + 2*i ); + } + OVR_COMPILER_ASSERT ( sizeof(lcs) == 36 ); + + // Convert to the real thing. + LensConfig result; + result.Eqn = Distortion_CatmullRom10; + for ( int i = 0; i < 11; i++ ) + { + // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0. + result.K[i] = DecodeFixedPointUInt16 ( lcs.K[i], 0, 14 ); + } + // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov), + // but may get arbitrarily high. tan(76)=4 is a very reasonable limit! + result.MaxR = DecodeFixedPointUInt16 ( lcs.MaxR, 0, 14 ); + // MetersPerTanAngleAtCenter is also known as focal length! + // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction) + result.MetersPerTanAngleAtCenter = DecodeFixedPointUInt16 ( lcs.MetersPerTanAngleAtCenter, 0, 16+3 ); + for ( int i = 0; i < 4; i++ ) + { + // ChromaticAberration[] are mostly 0.0something, centered on 0.0. Largest seen is 0.04, so set max to 0.125 (i.e. 3 "extra" bits of fraction) + result.ChromaticAberration[i] = DecodeFixedPointUInt16 ( lcs.ChromaticAberration[i], 0x8000, 16+3 ); + } + result.MaxInvR = result.DistortionFn ( result.MaxR ); + result.SetUpInverseApprox(); + + OVR_ASSERT ( version == lcs.VersionNumber ); + + *presult = result; + } + break; + default: + // Unknown format. + return false; + break; + } + return true; +} + +// Returns number of bytes needed. +int SaveLensConfigSizeInBytes ( LensConfig const &config ) +{ + OVR_UNUSED ( config ); + return sizeof ( LensConfigStored_CatmullRom10Version1 ); +} + +// Returns true on success. +bool SaveLensConfig ( UByte *pbuffer, int bufferSizeInBytes, LensConfig const &config ) +{ + if ( bufferSizeInBytes < (int)sizeof ( LensConfigStored_CatmullRom10Version1 ) ) + { + return false; + } + + // Construct the values. + LensConfigStored_CatmullRom10Version1 lcs; + lcs.VersionNumber = LCSV_CatmullRom10Version1; + for ( int i = 0; i < 11; i++ ) + { + // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0. + lcs.K[i] = EncodeFixedPointUInt16 ( config.K[i], 0, 14 ); + } + // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov), + // but may get arbitrarily high. tan(76)=4 is a very reasonable limit! + lcs.MaxR = EncodeFixedPointUInt16 ( config.MaxR, 0, 14 ); + // MetersPerTanAngleAtCenter is also known as focal length! + // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction) + lcs.MetersPerTanAngleAtCenter = EncodeFixedPointUInt16 ( config.MetersPerTanAngleAtCenter, 0, 16+3 ); + for ( int i = 0; i < 4; i++ ) + { + // ChromaticAberration[] are mostly 0.0something, centered on 0.0. Largest seen is 0.04, so set max to 0.125 (i.e. 3 "extra" bits of fraction) + lcs.ChromaticAberration[i] = EncodeFixedPointUInt16 ( config.ChromaticAberration[i], 0x8000, 16+3 ); + } + + + // Now store them out, sensitive to endinness. + EncodeUInt16 ( pbuffer + 0, lcs.VersionNumber ); + for ( int i = 0; i < 11; i++ ) + { + EncodeUInt16 ( pbuffer + 2 + 2*i, lcs.K[i] ); + } + EncodeUInt16 ( pbuffer + 24, lcs.MaxR ); + EncodeUInt16 ( pbuffer + 26, lcs.MetersPerTanAngleAtCenter ); + for ( int i = 0; i < 4; i++ ) + { + EncodeUInt16 ( pbuffer + 28 + 2*i, lcs.ChromaticAberration[i] ); + } + OVR_COMPILER_ASSERT ( 36 == sizeof(lcs) ); + + return true; +} + +#ifdef OVR_BUILD_DEBUG +void TestSaveLoadLensConfig ( LensConfig const &config ) +{ + OVR_ASSERT ( config.Eqn == Distortion_CatmullRom10 ); + // As a test, make sure this can be encoded and decoded correctly. + const int bufferSize = 256; + UByte buffer[bufferSize]; + OVR_ASSERT ( SaveLensConfigSizeInBytes ( config ) < bufferSize ); + bool success; + success = SaveLensConfig ( buffer, bufferSize, config ); + OVR_ASSERT ( success ); + LensConfig testConfig; + success = LoadLensConfig ( &testConfig, buffer, bufferSize ); + OVR_ASSERT ( success ); + OVR_ASSERT ( testConfig.Eqn == config.Eqn ); + for ( int i = 0; i < 11; i++ ) + { + OVR_ASSERT ( fabs ( testConfig.K[i] - config.K[i] ) < 0.0001f ); + } + OVR_ASSERT ( fabsf ( testConfig.MaxR - config.MaxR ) < 0.0001f ); + OVR_ASSERT ( fabsf ( testConfig.MetersPerTanAngleAtCenter - config.MetersPerTanAngleAtCenter ) < 0.00001f ); + for ( int i = 0; i < 4; i++ ) + { + OVR_ASSERT ( fabsf ( testConfig.ChromaticAberration[i] - config.ChromaticAberration[i] ) < 0.00001f ); + } +} +#endif + + + +//----------------------------------------------------------------------------------- + +// TBD: There is a question of whether this is the best file for CreateDebugHMDInfo. As long as there are many +// constants for HmdRenderInfo here as well it is ok. The alternative would be OVR_Common_HMDDevice.cpp, but +// that's specialized per platform... should probably move it there onces the code is in the common base class. + +HMDInfo CreateDebugHMDInfo(HmdTypeEnum hmdType) +{ + HMDInfo info; + + if ((hmdType != HmdType_DK1) && + (hmdType != HmdType_CrystalCoveProto)) + { + LogText("Debug HMDInfo - HmdType not supported. Defaulting to DK1.\n"); + hmdType = HmdType_DK1; + } + + // The alternative would be to initialize info.HmdType to HmdType_None instead. If we did that, + // code wouldn't be "maximally compatible" and devs wouldn't know what device we are + // simulating... so if differentiation becomes necessary we better add Debug flag in the future. + info.HmdType = hmdType; + info.Manufacturer = "Oculus VR"; + + switch(hmdType) + { + case HmdType_DK1: + info.ProductName = "Oculus Rift DK1"; + info.ResolutionInPixels = Sizei ( 1280, 800 ); + info.ScreenSizeInMeters = Sizef ( 0.1498f, 0.0936f ); + info.ScreenGapSizeInMeters = 0.0f; + info.CenterFromTopInMeters = 0.0468f; + info.LensSeparationInMeters = 0.0635f; + info.Shutter.Type = HmdShutter_RollingTopToBottom; + info.Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + info.Shutter.VsyncToFirstScanline = 0.000052f; + info.Shutter.FirstScanlineToLastScanline = 0.016580f; + info.Shutter.PixelSettleTime = 0.015f; + info.Shutter.PixelPersistence = ( 1.0f / 60.0f ); + break; + + case HmdType_CrystalCoveProto: + info.ProductName = "Oculus Rift Crystal Cove"; + info.ResolutionInPixels = Sizei ( 1920, 1080 ); + info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f ); + info.ScreenGapSizeInMeters = 0.0f; + info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f; + info.LensSeparationInMeters = 0.0635f; + info.Shutter.Type = HmdShutter_RollingRightToLeft; + info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + info.Shutter.VsyncToFirstScanline = 0.0000273f; + info.Shutter.FirstScanlineToLastScanline = 0.0131033f; + info.Shutter.PixelSettleTime = 0.0f; + info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync; + break; + + case HmdType_DK2: + info.ProductName = "Oculus Rift DK2"; + info.ResolutionInPixels = Sizei ( 1920, 1080 ); + info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f ); + info.ScreenGapSizeInMeters = 0.0f; + info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f; + info.LensSeparationInMeters = 0.0635f; + info.Shutter.Type = HmdShutter_RollingRightToLeft; + info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + info.Shutter.VsyncToFirstScanline = 0.0000273f; + info.Shutter.FirstScanlineToLastScanline = 0.0131033f; + info.Shutter.PixelSettleTime = 0.0f; + info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync; + break; + + default: + break; + } + + return info; +} + + + +// profile may be NULL, in which case it uses the hard-coded defaults. +HmdRenderInfo GenerateHmdRenderInfoFromHmdInfo ( HMDInfo const &hmdInfo, + Profile const *profile /*=NULL*/, + DistortionEqnType distortionType /*= Distortion_CatmullRom10*/, + EyeCupType eyeCupOverride /*= EyeCup_LAST*/ ) +{ + HmdRenderInfo renderInfo; + + renderInfo.HmdType = hmdInfo.HmdType; + renderInfo.ResolutionInPixels = hmdInfo.ResolutionInPixels; + renderInfo.ScreenSizeInMeters = hmdInfo.ScreenSizeInMeters; + renderInfo.CenterFromTopInMeters = hmdInfo.CenterFromTopInMeters; + renderInfo.ScreenGapSizeInMeters = hmdInfo.ScreenGapSizeInMeters; + renderInfo.LensSeparationInMeters = hmdInfo.LensSeparationInMeters; + + OVR_ASSERT ( sizeof(renderInfo.Shutter) == sizeof(hmdInfo.Shutter) ); // Try to keep the files in sync! + renderInfo.Shutter.Type = hmdInfo.Shutter.Type; + renderInfo.Shutter.VsyncToNextVsync = hmdInfo.Shutter.VsyncToNextVsync; + renderInfo.Shutter.VsyncToFirstScanline = hmdInfo.Shutter.VsyncToFirstScanline; + renderInfo.Shutter.FirstScanlineToLastScanline = hmdInfo.Shutter.FirstScanlineToLastScanline; + renderInfo.Shutter.PixelSettleTime = hmdInfo.Shutter.PixelSettleTime; + renderInfo.Shutter.PixelPersistence = hmdInfo.Shutter.PixelPersistence; + + renderInfo.LensDiameterInMeters = 0.035f; + renderInfo.LensSurfaceToMidplateInMeters = 0.025f; + renderInfo.EyeCups = EyeCup_DK1A; + +#if 0 // Device settings are out of date - don't use them. + if (Contents & Contents_Distortion) + { + memcpy(renderInfo.DistortionK, DistortionK, sizeof(float)*4); + renderInfo.DistortionEqn = Distortion_RecipPoly4; + } +#endif + + // Defaults in case of no user profile. + renderInfo.EyeLeft.NoseToPupilInMeters = 0.032f; + renderInfo.EyeLeft.ReliefInMeters = 0.012f; + + // 10mm eye-relief laser numbers for DK1 lenses. + // These are a decent seed for finding eye-relief and IPD. + // These are NOT used for rendering! + // Rendering distortions are now in GenerateLensConfigFromEyeRelief() + // So, if you're hacking in new distortions, don't do it here! + renderInfo.EyeLeft.Distortion.SetToIdentity(); + renderInfo.EyeLeft.Distortion.MetersPerTanAngleAtCenter = 0.0449f; + renderInfo.EyeLeft.Distortion.Eqn = Distortion_RecipPoly4; + renderInfo.EyeLeft.Distortion.K[0] = 1.0f; + renderInfo.EyeLeft.Distortion.K[1] = -0.494165344f; + renderInfo.EyeLeft.Distortion.K[2] = 0.587046423f; + renderInfo.EyeLeft.Distortion.K[3] = -0.841887126f; + renderInfo.EyeLeft.Distortion.MaxR = 1.0f; + + renderInfo.EyeLeft.Distortion.ChromaticAberration[0] = -0.006f; + renderInfo.EyeLeft.Distortion.ChromaticAberration[1] = 0.0f; + renderInfo.EyeLeft.Distortion.ChromaticAberration[2] = 0.014f; + renderInfo.EyeLeft.Distortion.ChromaticAberration[3] = 0.0f; + + renderInfo.EyeRight = renderInfo.EyeLeft; + + + // Obtain data from profile. + if ( profile != NULL ) + { + char eyecup[16]; + if (profile->GetValue(OVR_KEY_EYE_CUP, eyecup, 16)) + SetEyeCup(&renderInfo, eyecup); + } + + switch ( hmdInfo.HmdType ) + { + case HmdType_None: + case HmdType_DKProto: + case HmdType_DK1: + // Slight hack to improve usability. + // If you have a DKHD-style lens profile enabled, + // but you plug in DK1 and forget to change the profile, + // obviously you don't want those lens numbers. + if ( ( renderInfo.EyeCups != EyeCup_DK1A ) && + ( renderInfo.EyeCups != EyeCup_DK1B ) && + ( renderInfo.EyeCups != EyeCup_DK1C ) ) + { + renderInfo.EyeCups = EyeCup_DK1A; + } + break; + + case HmdType_DKHD2Proto: + renderInfo.EyeCups = EyeCup_DKHD2A; + break; + case HmdType_CrystalCoveProto: + renderInfo.EyeCups = EyeCup_PinkA; + break; + case HmdType_DK2: + renderInfo.EyeCups = EyeCup_DK2A; + break; + default: + break; + } + + if ( eyeCupOverride != EyeCup_LAST ) + { + renderInfo.EyeCups = eyeCupOverride; + } + + switch ( renderInfo.EyeCups ) + { + case EyeCup_DK1A: + case EyeCup_DK1B: + case EyeCup_DK1C: + renderInfo.LensDiameterInMeters = 0.035f; + renderInfo.LensSurfaceToMidplateInMeters = 0.02357f; + // Not strictly lens-specific, but still wise to set a reasonable default for relief. + renderInfo.EyeLeft.ReliefInMeters = 0.010f; + renderInfo.EyeRight.ReliefInMeters = 0.010f; + break; + case EyeCup_DKHD2A: + renderInfo.LensDiameterInMeters = 0.035f; + renderInfo.LensSurfaceToMidplateInMeters = 0.02357f; + // Not strictly lens-specific, but still wise to set a reasonable default for relief. + renderInfo.EyeLeft.ReliefInMeters = 0.010f; + renderInfo.EyeRight.ReliefInMeters = 0.010f; + break; + case EyeCup_PinkA: + case EyeCup_DK2A: + renderInfo.LensDiameterInMeters = 0.04f; // approximate + renderInfo.LensSurfaceToMidplateInMeters = 0.01965f; + // Not strictly lens-specific, but still wise to set a reasonable default for relief. + renderInfo.EyeLeft.ReliefInMeters = 0.012f; + renderInfo.EyeRight.ReliefInMeters = 0.012f; + break; + default: OVR_ASSERT ( false ); break; + } + + if ( profile != NULL ) + { + // Set the customized user eye position + // TBD: Maybe we should separate custom camera positioning from custom distortion rendering ?? + if (profile->GetBoolValue(OVR_KEY_CUSTOM_EYE_RENDER, true)) + { + float eye2nose[2]; + if (profile->GetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, eye2nose, 2) == 2) + { // Load per-eye half-IPD + renderInfo.EyeLeft.NoseToPupilInMeters = eye2nose[0]; + renderInfo.EyeRight.NoseToPupilInMeters = eye2nose[1]; + } + else + { // Use a centered IPD instead + float ipd = profile->GetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD); + renderInfo.EyeLeft.NoseToPupilInMeters = 0.5f * ipd; + renderInfo.EyeRight.NoseToPupilInMeters = 0.5f * ipd; + } + + float eye2plate[2]; + if (profile->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, eye2plate, 2) == 2) + { // Subtract the eye-cup height from the plate distance to get the eye-to-lens distance + // This measurement should be the the distance at maximum dial setting + // We still need to adjust with the dial offset + renderInfo.EyeLeft.ReliefInMeters = eye2plate[0] - renderInfo.LensSurfaceToMidplateInMeters; + renderInfo.EyeRight.ReliefInMeters = eye2plate[1] - renderInfo.LensSurfaceToMidplateInMeters; + + // Adjust the eye relief with the dial setting (from the assumed max eye relief) + int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, -1); + if (dial >= 0) + { + renderInfo.EyeLeft.ReliefInMeters -= ((10 - dial) * 0.001f); + renderInfo.EyeRight.ReliefInMeters -= ((10 - dial) * 0.001f); + } + } + else + { + // Set the eye relief with the user configured dial setting + int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, -1); + if (dial >= 0) + { // Assume a default of 7 to 17 mm eye relief based on the dial. This corresponds + // to the sampled and tuned distortion range on the DK1. + renderInfo.EyeLeft.ReliefInMeters = 0.007f + (dial * 0.001f); + renderInfo.EyeRight.ReliefInMeters = 0.007f + (dial * 0.001f); + } + } + } + } + + // Now we know where the eyes are relative to the lenses, we can compute a distortion for each. + // TODO: incorporate lateral offset in distortion generation. + // TODO: we used a distortion to calculate eye-relief, and now we're making a distortion from that eye-relief. Close the loop! + + for ( int eyeNum = 0; eyeNum < 2; eyeNum++ ) + { + HmdRenderInfo::EyeConfig *pHmdEyeConfig = ( eyeNum == 0 ) ? &(renderInfo.EyeLeft) : &(renderInfo.EyeRight); + + float eye_relief = pHmdEyeConfig->ReliefInMeters; + LensConfig distortionConfig = GenerateLensConfigFromEyeRelief ( eye_relief, renderInfo, distortionType ); + pHmdEyeConfig->Distortion = distortionConfig; + } + + return renderInfo; +} + + +LensConfig GenerateLensConfigFromEyeRelief ( float eyeReliefInMeters, HmdRenderInfo const &hmd, DistortionEqnType distortionType /*= Distortion_CatmullRom10*/ ) +{ + struct DistortionDescriptor + { + float EyeRelief; + // The three places we're going to sample & lerp the curve at. + // One sample is always at 0.0, and the distortion scale should be 1.0 or else! + // Only use for poly4 numbers - CR has an implicit scale. + float SampleRadius[3]; + // Where the distortion has actually been measured/calibrated out to. + // Don't try to hallucinate data out beyond here. + float MaxRadius; + // The config itself. + LensConfig Config; + }; + + DistortionDescriptor distortions[10]; + for ( unsigned int i = 0; i < sizeof(distortions)/sizeof(distortions[0]); i++ ) + { + distortions[i].Config.SetToIdentity(); + distortions[i].EyeRelief = 0.0f; + distortions[i].MaxRadius = 1.0f; + } + int numDistortions = 0; + int defaultDistortion = 0; // index of the default distortion curve to use if zero eye relief supplied + + if ( ( hmd.EyeCups == EyeCup_DK1A ) || + ( hmd.EyeCups == EyeCup_DK1B ) || + ( hmd.EyeCups == EyeCup_DK1C ) ) + { + + numDistortions = 0; + + // Tuned at minimum dial setting - extended to r^2 == 1.8 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.012760465f - 0.005f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0000f; + distortions[numDistortions].Config.K[1] = 1.06505f; + distortions[numDistortions].Config.K[2] = 1.14725f; + distortions[numDistortions].Config.K[3] = 1.2705f; + distortions[numDistortions].Config.K[4] = 1.48f; + distortions[numDistortions].Config.K[5] = 1.87f; + distortions[numDistortions].Config.K[6] = 2.534f; + distortions[numDistortions].Config.K[7] = 3.6f; + distortions[numDistortions].Config.K[8] = 5.1f; + distortions[numDistortions].Config.K[9] = 7.4f; + distortions[numDistortions].Config.K[10] = 11.0f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = sqrt(1.8f); + defaultDistortion = numDistortions; // this is the default + numDistortions++; + + // Tuned at middle dial setting + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.012760465f; // my average eye-relief + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0f; + distortions[numDistortions].Config.K[1] = 1.032407264f; + distortions[numDistortions].Config.K[2] = 1.07160462f; + distortions[numDistortions].Config.K[3] = 1.11998388f; + distortions[numDistortions].Config.K[4] = 1.1808606f; + distortions[numDistortions].Config.K[5] = 1.2590494f; + distortions[numDistortions].Config.K[6] = 1.361915f; + distortions[numDistortions].Config.K[7] = 1.5014339f; + distortions[numDistortions].Config.K[8] = 1.6986004f; + distortions[numDistortions].Config.K[9] = 1.9940577f; + distortions[numDistortions].Config.K[10] = 2.4783147f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = 1.0f; + numDistortions++; + + // Tuned at maximum dial setting + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.012760465f + 0.005f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0102f; + distortions[numDistortions].Config.K[1] = 1.0371f; + distortions[numDistortions].Config.K[2] = 1.0831f; + distortions[numDistortions].Config.K[3] = 1.1353f; + distortions[numDistortions].Config.K[4] = 1.2f; + distortions[numDistortions].Config.K[5] = 1.2851f; + distortions[numDistortions].Config.K[6] = 1.3979f; + distortions[numDistortions].Config.K[7] = 1.56f; + distortions[numDistortions].Config.K[8] = 1.8f; + distortions[numDistortions].Config.K[9] = 2.25f; + distortions[numDistortions].Config.K[10] = 3.0f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = 1.0f; + numDistortions++; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + distortions[i].Config.ChromaticAberration[0] = -0.006f; + distortions[i].Config.ChromaticAberration[1] = 0.0f; + distortions[i].Config.ChromaticAberration[2] = 0.014f; + distortions[i].Config.ChromaticAberration[3] = 0.0f; + } + } + else if ( hmd.EyeCups == EyeCup_DKHD2A ) + { + // Tuned DKHD2 lens + numDistortions = 0; + + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.010f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0f; + distortions[numDistortions].Config.K[1] = 1.0425f; + distortions[numDistortions].Config.K[2] = 1.0826f; + distortions[numDistortions].Config.K[3] = 1.130f; + distortions[numDistortions].Config.K[4] = 1.185f; + distortions[numDistortions].Config.K[5] = 1.250f; + distortions[numDistortions].Config.K[6] = 1.338f; + distortions[numDistortions].Config.K[7] = 1.455f; + distortions[numDistortions].Config.K[8] = 1.620f; + distortions[numDistortions].Config.K[9] = 1.840f; + distortions[numDistortions].Config.K[10] = 2.200f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = 1.0f; + + distortions[numDistortions].SampleRadius[0] = 0.405405405f; + distortions[numDistortions].SampleRadius[1] = 0.675675676f; + distortions[numDistortions].SampleRadius[2] = 0.945945946f; + defaultDistortion = numDistortions; // this is the default + numDistortions++; + + distortions[numDistortions] = distortions[0]; + distortions[numDistortions].EyeRelief = 0.020f; + numDistortions++; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + distortions[i].Config.ChromaticAberration[0] = -0.006f; + distortions[i].Config.ChromaticAberration[1] = 0.0f; + distortions[i].Config.ChromaticAberration[2] = 0.014f; + distortions[i].Config.ChromaticAberration[3] = 0.0f; + } + } + else if ( hmd.EyeCups == EyeCup_PinkA || hmd.EyeCups == EyeCup_DK2A ) + { + // Tuned Crystal Cove & DK2 Lens (CES & GDC) + numDistortions = 0; + + distortions[numDistortions].EyeRelief = 0.010f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.036f; + + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].Config.K[0] = 1.003f; + distortions[numDistortions].Config.K[1] = 1.02f; + distortions[numDistortions].Config.K[2] = 1.042f; + distortions[numDistortions].Config.K[3] = 1.066f; + distortions[numDistortions].Config.K[4] = 1.094f; //1.0945f; + distortions[numDistortions].Config.K[5] = 1.126f; //1.127f; + distortions[numDistortions].Config.K[6] = 1.162f; //1.167f; + distortions[numDistortions].Config.K[7] = 1.203f; //1.218f; + distortions[numDistortions].Config.K[8] = 1.25f; //1.283f; + distortions[numDistortions].Config.K[9] = 1.31f; //1.37f; + distortions[numDistortions].Config.K[10] = 1.38f; //1.48f; + distortions[numDistortions].MaxRadius = 1.0f; + + + distortions[numDistortions].SampleRadius[0] = 0.405405405f; + distortions[numDistortions].SampleRadius[1] = 0.675675676f; + distortions[numDistortions].SampleRadius[2] = 0.945945946f; + defaultDistortion = numDistortions; // this is the default + numDistortions++; + + distortions[numDistortions] = distortions[0]; + distortions[numDistortions].EyeRelief = 0.020f; + numDistortions++; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + distortions[i].Config.ChromaticAberration[0] = -0.015f; + distortions[i].Config.ChromaticAberration[1] = -0.02f; + distortions[i].Config.ChromaticAberration[2] = 0.025f; + distortions[i].Config.ChromaticAberration[3] = 0.02f; + } + } + else + { + // Unknown lens. + // Use DK1 black lens settings, just so we can continue to run with something. + distortions[0].EyeRelief = 0.005f; + distortions[0].Config.MetersPerTanAngleAtCenter = 0.043875f; + distortions[0].Config.Eqn = Distortion_RecipPoly4; + distortions[0].Config.K[0] = 1.0f; + distortions[0].Config.K[1] = -0.3999f; + distortions[0].Config.K[2] = 0.2408f; + distortions[0].Config.K[3] = -0.4589f; + distortions[0].SampleRadius[0] = 0.2f; + distortions[0].SampleRadius[1] = 0.4f; + distortions[0].SampleRadius[2] = 0.6f; + + distortions[1] = distortions[0]; + distortions[1].EyeRelief = 0.010f; + numDistortions = 2; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + // These are placeholder, they have not been tuned! + distortions[i].Config.ChromaticAberration[0] = 0.0f; + distortions[i].Config.ChromaticAberration[1] = 0.0f; + distortions[i].Config.ChromaticAberration[2] = 0.0f; + distortions[i].Config.ChromaticAberration[3] = 0.0f; + } + } + + OVR_ASSERT ( numDistortions < (sizeof(distortions)/sizeof(distortions[0])) ); + + + DistortionDescriptor *pUpper = NULL; + DistortionDescriptor *pLower = NULL; + float lerpVal = 0.0f; + if (eyeReliefInMeters == 0) + { // Use a constant default distortion if an invalid eye-relief is supplied + pLower = &(distortions[defaultDistortion]); + pUpper = &(distortions[defaultDistortion]); + lerpVal = 0.0f; + } + else + { + for ( int i = 0; i < numDistortions-1; i++ ) + { + OVR_ASSERT ( distortions[i].EyeRelief < distortions[i+1].EyeRelief ); + if ( ( distortions[i].EyeRelief <= eyeReliefInMeters ) && ( distortions[i+1].EyeRelief > eyeReliefInMeters ) ) + { + pLower = &(distortions[i]); + pUpper = &(distortions[i+1]); + lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief ); + // No break here - I want the ASSERT to check everything every time! + } + } + } + + if ( pUpper == NULL ) + { +#if 0 + // Outside the range, so extrapolate rather than interpolate. + if ( distortions[0].EyeRelief > eyeReliefInMeters ) + { + pLower = &(distortions[0]); + pUpper = &(distortions[1]); + } + else + { + OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters ); + pLower = &(distortions[numDistortions-2]); + pUpper = &(distortions[numDistortions-1]); + } + lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief ); +#else + // Do not extrapolate, just clamp - slightly worried about people putting in bogus settings. + if ( distortions[0].EyeRelief > eyeReliefInMeters ) + { + pLower = &(distortions[0]); + pUpper = &(distortions[0]); + } + else + { + OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters ); + pLower = &(distortions[numDistortions-1]); + pUpper = &(distortions[numDistortions-1]); + } + lerpVal = 0.0f; +#endif + } + float invLerpVal = 1.0f - lerpVal; + + pLower->Config.MaxR = pLower->MaxRadius; + pUpper->Config.MaxR = pUpper->MaxRadius; + + LensConfig result; + // Where is the edge of the lens - no point modelling further than this. + float maxValidRadius = invLerpVal * pLower->MaxRadius + lerpVal * pUpper->MaxRadius; + result.MaxR = maxValidRadius; + + switch ( distortionType ) + { + case Distortion_Poly4: + // Deprecated + OVR_ASSERT ( false ); + break; + case Distortion_RecipPoly4:{ + // Lerp control points and fit an equation to them. + float fitX[4]; + float fitY[4]; + fitX[0] = 0.0f; + fitY[0] = 1.0f; + for ( int ctrlPt = 1; ctrlPt < 4; ctrlPt ++ ) + { + float radiusLerp = invLerpVal * pLower->SampleRadius[ctrlPt-1] + lerpVal * pUpper->SampleRadius[ctrlPt-1]; + float radiusLerpSq = radiusLerp * radiusLerp; + float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq ); + float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq ); + fitX[ctrlPt] = radiusLerpSq; + fitY[ctrlPt] = 1.0f / ( invLerpVal * fitYLower + lerpVal * fitYUpper ); + } + + result.Eqn = Distortion_RecipPoly4; + bool bSuccess = FitCubicPolynomial ( result.K, fitX, fitY ); + OVR_ASSERT ( bSuccess ); + OVR_UNUSED ( bSuccess ); + + // Set up the fast inverse. + float maxRDist = result.DistortionFn ( maxValidRadius ); + result.MaxInvR = maxRDist; + result.SetUpInverseApprox(); + + }break; + + case Distortion_CatmullRom10:{ + + // Evenly sample & lerp points on the curve. + const int NumSegments = LensConfig::NumCoefficients; + result.MaxR = maxValidRadius; + // Directly interpolate the K0 values + result.K[0] = invLerpVal * pLower->Config.K[0] + lerpVal * pUpper->Config.K[0]; + + // Sample and interpolate the distortion curves to derive K[1] ... K[n] + for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ ) + { + float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius; + float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusSq ); + float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusSq ); + float fitLerp = invLerpVal * fitYLower + lerpVal * fitYUpper; + result.K[ctrlPt] = fitLerp; + } + + result.Eqn = Distortion_CatmullRom10; + + for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ ) + { + float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius; + float val = result.DistortionFnScaleRadiusSquared ( radiusSq ); + OVR_ASSERT ( Alg::Abs ( val - result.K[ctrlPt] ) < 0.0001f ); + OVR_UNUSED1(val); // For release build. + } + + // Set up the fast inverse. + float maxRDist = result.DistortionFn ( maxValidRadius ); + result.MaxInvR = maxRDist; + result.SetUpInverseApprox(); + + }break; + + default: OVR_ASSERT ( false ); break; + } + + + // Chromatic aberration. + result.ChromaticAberration[0] = invLerpVal * pLower->Config.ChromaticAberration[0] + lerpVal * pUpper->Config.ChromaticAberration[0]; + result.ChromaticAberration[1] = invLerpVal * pLower->Config.ChromaticAberration[1] + lerpVal * pUpper->Config.ChromaticAberration[1]; + result.ChromaticAberration[2] = invLerpVal * pLower->Config.ChromaticAberration[2] + lerpVal * pUpper->Config.ChromaticAberration[2]; + result.ChromaticAberration[3] = invLerpVal * pLower->Config.ChromaticAberration[3] + lerpVal * pUpper->Config.ChromaticAberration[3]; + + // Scale. + result.MetersPerTanAngleAtCenter = pLower->Config.MetersPerTanAngleAtCenter * invLerpVal + + pUpper->Config.MetersPerTanAngleAtCenter * lerpVal; + /* + // Commented out - Causes ASSERT with no HMD plugged in +#ifdef OVR_BUILD_DEBUG + if ( distortionType == Distortion_CatmullRom10 ) + { + TestSaveLoadLensConfig ( result ); + } +#endif + */ + return result; +} + + + + + +DistortionRenderDesc CalculateDistortionRenderDesc ( StereoEye eyeType, HmdRenderInfo const &hmd, + const LensConfig *pLensOverride /*= NULL */ ) +{ + // From eye relief, IPD and device characteristics, we get the distortion mapping. + // This distortion does the following things: + // 1. It undoes the distortion that happens at the edges of the lens. + // 2. It maps the undistorted field into "retina" space. + // So the input is a pixel coordinate - the physical pixel on the display itself. + // The output is the real-world direction of the ray from this pixel as it comes out of the lens and hits the eye. + // However we typically think of rays "coming from" the eye, so the direction (TanAngleX,TanAngleY,1) is the direction + // that the pixel appears to be in real-world space, where AngleX and AngleY are relative to the straight-ahead vector. + // If your renderer is a raytracer, you can use this vector directly (normalize as appropriate). + // However in standard rasterisers, we have rendered a 2D image and are putting it in front of the eye, + // so we then need a mapping from this space to the [-1,1] UV coordinate space, which depends on exactly + // where "in space" the app wants to put that rendertarget. + // Where in space, and how large this rendertarget is, is completely up to the app and/or user, + // though of course we can provide some useful hints. + + // TODO: Use IPD and eye relief to modify distortion (i.e. non-radial component) + // TODO: cope with lenses that don't produce collimated light. + // This means that IPD relative to the lens separation changes the light vergence, + // and so we actually need to change where the image is displayed. + + const HmdRenderInfo::EyeConfig &hmdEyeConfig = ( eyeType == StereoEye_Left ) ? hmd.EyeLeft : hmd.EyeRight; + + DistortionRenderDesc localDistortion; + localDistortion.Lens = hmdEyeConfig.Distortion; + + if ( pLensOverride != NULL ) + { + localDistortion.Lens = *pLensOverride; + } + + Sizef pixelsPerMeter(hmd.ResolutionInPixels.w / ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ), + hmd.ResolutionInPixels.h / hmd.ScreenSizeInMeters.h); + + localDistortion.PixelsPerTanAngleAtCenter = (pixelsPerMeter * localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector(); + // Same thing, scaled to [-1,1] for each eye, rather than pixels. + + localDistortion.TanEyeAngleScale = Vector2f(0.25f, 0.5f).EntrywiseMultiply( + (hmd.ScreenSizeInMeters / localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector()); + + // <--------------left eye------------------><-ScreenGapSizeInMeters-><--------------right eye-----------------> + // <------------------------------------------ScreenSizeInMeters.Width-----------------------------------------> + // <----------------LensSeparationInMeters---------------> + // <--centerFromLeftInMeters-> + // ^ + // Center of lens + + // Find the lens centers in scale of [-1,+1] (NDC) in left eye. + float visibleWidthOfOneEye = 0.5f * ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ); + float centerFromLeftInMeters = ( hmd.ScreenSizeInMeters.w - hmd.LensSeparationInMeters ) * 0.5f; + localDistortion.LensCenter.x = ( centerFromLeftInMeters / visibleWidthOfOneEye ) * 2.0f - 1.0f; + localDistortion.LensCenter.y = ( hmd.CenterFromTopInMeters / hmd.ScreenSizeInMeters.h ) * 2.0f - 1.0f; + if ( eyeType == StereoEye_Right ) + { + localDistortion.LensCenter.x = -localDistortion.LensCenter.x; + } + + return localDistortion; +} + +FovPort CalculateFovFromEyePosition ( float eyeReliefInMeters, + float offsetToRightInMeters, + float offsetDownwardsInMeters, + float lensDiameterInMeters, + float extraEyeRotationInRadians /*= 0.0f*/ ) +{ + // 2D view of things: + // |-| <--- offsetToRightInMeters (in this case, it is negative) + // |=======C=======| <--- lens surface (C=center) + // \ | _/ + // \ R _/ + // \ | _/ + // \ | _/ + // \|/ + // O <--- center of pupil + + // (technically the lens is round rather than square, so it's not correct to + // separate vertical and horizontal like this, but it's close enough) + float halfLensDiameter = lensDiameterInMeters * 0.5f; + FovPort fovPort; + fovPort.UpTan = ( halfLensDiameter + offsetDownwardsInMeters ) / eyeReliefInMeters; + fovPort.DownTan = ( halfLensDiameter - offsetDownwardsInMeters ) / eyeReliefInMeters; + fovPort.LeftTan = ( halfLensDiameter + offsetToRightInMeters ) / eyeReliefInMeters; + fovPort.RightTan = ( halfLensDiameter - offsetToRightInMeters ) / eyeReliefInMeters; + + if ( extraEyeRotationInRadians > 0.0f ) + { + // That's the basic looking-straight-ahead eye position relative to the lens. + // But if you look left, the pupil moves left as the eyeball rotates, which + // means you can see more to the right than this geometry suggests. + // So add in the bounds for the extra movement of the pupil. + + // Beyond 30 degrees does not increase FOV because the pupil starts moving backwards more than sideways. + extraEyeRotationInRadians = Alg::Min ( DegreeToRad ( 30.0f ), Alg::Max ( 0.0f, extraEyeRotationInRadians ) ); + + // The rotation of the eye is a bit more complex than a simple circle. The center of rotation + // at 13.5mm from cornea is slightly further back than the actual center of the eye. + // Additionally the rotation contains a small lateral component as the muscles pull the eye + const float eyeballCenterToPupil = 0.0135f; // center of eye rotation + const float eyeballLateralPull = 0.001f * (extraEyeRotationInRadians / DegreeToRad ( 30.0f)); // lateral motion as linear function + float extraTranslation = eyeballCenterToPupil * sinf ( extraEyeRotationInRadians ) + eyeballLateralPull; + float extraRelief = eyeballCenterToPupil * ( 1.0f - cosf ( extraEyeRotationInRadians ) ); + + fovPort.UpTan = Alg::Max ( fovPort.UpTan , ( halfLensDiameter + offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + fovPort.DownTan = Alg::Max ( fovPort.DownTan , ( halfLensDiameter - offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + fovPort.LeftTan = Alg::Max ( fovPort.LeftTan , ( halfLensDiameter + offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + fovPort.RightTan = Alg::Max ( fovPort.RightTan, ( halfLensDiameter - offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + } + + return fovPort; +} + + + +FovPort CalculateFovFromHmdInfo ( StereoEye eyeType, + DistortionRenderDesc const &distortion, + HmdRenderInfo const &hmd, + float extraEyeRotationInRadians /*= 0.0f*/ ) +{ + FovPort fovPort; + float eyeReliefInMeters; + float offsetToRightInMeters; + if ( eyeType == StereoEye_Right ) + { + eyeReliefInMeters = hmd.EyeRight.ReliefInMeters; + offsetToRightInMeters = hmd.EyeRight.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters; + } + else + { + eyeReliefInMeters = hmd.EyeLeft.ReliefInMeters; + offsetToRightInMeters = -(hmd.EyeLeft.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters); + } + + // Limit the eye-relief to 6 mm for FOV calculations since this just tends to spread off-screen + // and get clamped anyways on DK1 (but in Unity it continues to spreads and causes + // unnecessarily large render targets) + eyeReliefInMeters = Alg::Max(eyeReliefInMeters, 0.006f); + + // Central view. + fovPort = CalculateFovFromEyePosition ( eyeReliefInMeters, + offsetToRightInMeters, + 0.0f, + hmd.LensDiameterInMeters, + extraEyeRotationInRadians ); + + // clamp to the screen + fovPort = ClampToPhysicalScreenFov ( eyeType, distortion, fovPort ); + + return fovPort; +} + + + +FovPort GetPhysicalScreenFov ( StereoEye eyeType, DistortionRenderDesc const &distortion ) +{ + OVR_UNUSED1 ( eyeType ); + + FovPort resultFovPort; + + // Figure out the boundaries of the screen. We take the middle pixel of the screen, + // move to each of the four screen edges, and transform those back into TanAngle space. + Vector2f dmiddle = distortion.LensCenter; + + // The gotcha is that for some distortion functions, the map will "wrap around" + // for screen pixels that are not actually visible to the user (especially on DK1, + // which has a lot of invisible pixels), and map to pixels that are close to the middle. + // This means the edges of the screen will actually be + // "closer" than the visible bounds, so we'll clip too aggressively. + + // Solution - step gradually towards the boundary, noting the maximum distance. + struct FunctionHider + { + static FovPort FindRange ( Vector2f from, Vector2f to, int numSteps, + DistortionRenderDesc const &distortion ) + { + FovPort result; + result.UpTan = 0.0f; + result.DownTan = 0.0f; + result.LeftTan = 0.0f; + result.RightTan = 0.0f; + + float stepScale = 1.0f / ( numSteps - 1 ); + for ( int step = 0; step < numSteps; step++ ) + { + float lerpFactor = stepScale * (float)step; + Vector2f sample = from + (to - from) * lerpFactor; + Vector2f tanEyeAngle = TransformScreenNDCToTanFovSpace ( distortion, sample ); + + result.LeftTan = Alg::Max ( result.LeftTan, -tanEyeAngle.x ); + result.RightTan = Alg::Max ( result.RightTan, tanEyeAngle.x ); + result.UpTan = Alg::Max ( result.UpTan, -tanEyeAngle.y ); + result.DownTan = Alg::Max ( result.DownTan, tanEyeAngle.y ); + } + return result; + } + }; + + FovPort leftFovPort = FunctionHider::FindRange( dmiddle, Vector2f( -1.0f, dmiddle.y ), 10, distortion ); + FovPort rightFovPort = FunctionHider::FindRange( dmiddle, Vector2f( 1.0f, dmiddle.y ), 10, distortion ); + FovPort upFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, -1.0f ), 10, distortion ); + FovPort downFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, 1.0f ), 10, distortion ); + + resultFovPort.LeftTan = leftFovPort.LeftTan; + resultFovPort.RightTan = rightFovPort.RightTan; + resultFovPort.UpTan = upFovPort.UpTan; + resultFovPort.DownTan = downFovPort.DownTan; + + return resultFovPort; +} + +FovPort ClampToPhysicalScreenFov( StereoEye eyeType, DistortionRenderDesc const &distortion, + FovPort inputFovPort ) +{ + FovPort resultFovPort; + FovPort phsyicalFovPort = GetPhysicalScreenFov ( eyeType, distortion ); + resultFovPort.LeftTan = Alg::Min ( inputFovPort.LeftTan, phsyicalFovPort.LeftTan ); + resultFovPort.RightTan = Alg::Min ( inputFovPort.RightTan, phsyicalFovPort.RightTan ); + resultFovPort.UpTan = Alg::Min ( inputFovPort.UpTan, phsyicalFovPort.UpTan ); + resultFovPort.DownTan = Alg::Min ( inputFovPort.DownTan, phsyicalFovPort.DownTan ); + + return resultFovPort; +} + +Sizei CalculateIdealPixelSize ( StereoEye eyeType, DistortionRenderDesc const &distortion, + FovPort tanHalfFov, float pixelsPerDisplayPixel ) +{ + OVR_UNUSED(eyeType); // might be useful in the future if we do overlapping fovs + + Sizei result; + // TODO: if the app passes in a FOV that doesn't cover the centre, use the distortion values for the nearest edge/corner to match pixel size. + result.w = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.x * ( tanHalfFov.LeftTan + tanHalfFov.RightTan ) ); + result.h = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.y * ( tanHalfFov.UpTan + tanHalfFov.DownTan ) ); + return result; +} + +Recti GetFramebufferViewport ( StereoEye eyeType, HmdRenderInfo const &hmd ) +{ + Recti result; + result.w = hmd.ResolutionInPixels.w/2; + result.h = hmd.ResolutionInPixels.h; + result.x = 0; + result.y = 0; + if ( eyeType == StereoEye_Right ) + { + result.x = (hmd.ResolutionInPixels.w+1)/2; // Round up, not down. + } + return result; +} + + +ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov ( FovPort tanHalfFov ) +{ + float projXScale = 2.0f / ( tanHalfFov.LeftTan + tanHalfFov.RightTan ); + float projXOffset = ( tanHalfFov.LeftTan - tanHalfFov.RightTan ) * projXScale * 0.5f; + float projYScale = 2.0f / ( tanHalfFov.UpTan + tanHalfFov.DownTan ); + float projYOffset = ( tanHalfFov.UpTan - tanHalfFov.DownTan ) * projYScale * 0.5f; + + ScaleAndOffset2D result; + result.Scale = Vector2f(projXScale, projYScale); + result.Offset = Vector2f(projXOffset, projYOffset); + // Hey - why is that Y.Offset negated? + // It's because a projection matrix transforms from world coords with Y=up, + // whereas this is from NDC which is Y=down. + + return result; +} + + +ScaleAndOffset2D CreateUVScaleAndOffsetfromNDCScaleandOffset ( ScaleAndOffset2D scaleAndOffsetNDC, + Recti renderedViewport, + Sizei renderTargetSize ) +{ + // scaleAndOffsetNDC takes you to NDC space [-1,+1] within the given viewport on the rendertarget. + // We want a scale to instead go to actual UV coordinates you can sample with, + // which need [0,1] and ignore the viewport. + ScaleAndOffset2D result; + // Scale [-1,1] to [0,1] + result.Scale = scaleAndOffsetNDC.Scale * 0.5f; + result.Offset = scaleAndOffsetNDC.Offset * 0.5f + Vector2f(0.5f); + + // ...but we will have rendered to a subsection of the RT, so scale for that. + Vector2f scale( (float)renderedViewport.w / (float)renderTargetSize.w, + (float)renderedViewport.h / (float)renderTargetSize.h ); + Vector2f offset( (float)renderedViewport.x / (float)renderTargetSize.w, + (float)renderedViewport.y / (float)renderTargetSize.h ); + + result.Scale = result.Scale.EntrywiseMultiply(scale); + result.Offset = result.Offset.EntrywiseMultiply(scale) + offset; + return result; +} + + + +Matrix4f CreateProjection( bool rightHanded, FovPort tanHalfFov, + float zNear /*= 0.01f*/, float zFar /*= 10000.0f*/ ) +{ + // A projection matrix is very like a scaling from NDC, so we can start with that. + ScaleAndOffset2D scaleAndOffset = CreateNDCScaleAndOffsetFromFov ( tanHalfFov ); + + float handednessScale = 1.0f; + if ( rightHanded ) + { + handednessScale = -1.0f; + } + + Matrix4f projection; + // Produces X result, mapping clip edges to [-w,+w] + projection.M[0][0] = scaleAndOffset.Scale.x; + projection.M[0][1] = 0.0f; + projection.M[0][2] = handednessScale * scaleAndOffset.Offset.x; + projection.M[0][3] = 0.0f; + + // Produces Y result, mapping clip edges to [-w,+w] + // Hey - why is that YOffset negated? + // It's because a projection matrix transforms from world coords with Y=up, + // whereas this is derived from an NDC scaling, which is Y=down. + projection.M[1][0] = 0.0f; + projection.M[1][1] = scaleAndOffset.Scale.y; + projection.M[1][2] = handednessScale * -scaleAndOffset.Offset.y; + projection.M[1][3] = 0.0f; + + // Produces Z-buffer result - app needs to fill this in with whatever Z range it wants. + // We'll just use some defaults for now. + projection.M[2][0] = 0.0f; + projection.M[2][1] = 0.0f; + projection.M[2][2] = -handednessScale * zFar / (zNear - zFar); + projection.M[2][3] = (zFar * zNear) / (zNear - zFar); + + // Produces W result (= Z in) + projection.M[3][0] = 0.0f; + projection.M[3][1] = 0.0f; + projection.M[3][2] = handednessScale; + projection.M[3][3] = 0.0f; + + return projection; +} + + +Matrix4f CreateOrthoSubProjection ( bool rightHanded, StereoEye eyeType, + float tanHalfFovX, float tanHalfFovY, + float unitsX, float unitsY, + float distanceFromCamera, float interpupillaryDistance, + Matrix4f const &projection, + float zNear /*= 0.0f*/, float zFar /*= 0.0f*/ ) +{ + OVR_UNUSED1 ( rightHanded ); + + float orthoHorizontalOffset = interpupillaryDistance * 0.5f / distanceFromCamera; + switch ( eyeType ) + { + case StereoEye_Center: + orthoHorizontalOffset = 0.0f; + break; + case StereoEye_Left: + break; + case StereoEye_Right: + orthoHorizontalOffset = -orthoHorizontalOffset; + break; + default: OVR_ASSERT ( false ); break; + } + + // Current projection maps real-world vector (x,y,1) to the RT. + // We want to find the projection that maps the range [-FovPixels/2,FovPixels/2] to + // the physical [-orthoHalfFov,orthoHalfFov] + // Note moving the offset from M[0][2]+M[1][2] to M[0][3]+M[1][3] - this means + // we don't have to feed in Z=1 all the time. + // The horizontal offset math is a little hinky because the destination is + // actually [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset] + // So we need to first map [-FovPixels/2,FovPixels/2] to + // [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]: + // x1 = x0 * orthoHalfFov/(FovPixels/2) + orthoHorizontalOffset; + // = x0 * 2*orthoHalfFov/FovPixels + orthoHorizontalOffset; + // But then we need the sam mapping as the existing projection matrix, i.e. + // x2 = x1 * Projection.M[0][0] + Projection.M[0][2]; + // = x0 * (2*orthoHalfFov/FovPixels + orthoHorizontalOffset) * Projection.M[0][0] + Projection.M[0][2]; + // = x0 * Projection.M[0][0]*2*orthoHalfFov/FovPixels + + // orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]; + // So in the new projection matrix we need to scale by Projection.M[0][0]*2*orthoHalfFov/FovPixels and + // offset by orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]. + + float orthoScaleX = 2.0f * tanHalfFovX / unitsX; + float orthoScaleY = 2.0f * tanHalfFovY / unitsY; + Matrix4f ortho; + ortho.M[0][0] = projection.M[0][0] * orthoScaleX; + ortho.M[0][1] = 0.0f; + ortho.M[0][2] = 0.0f; + ortho.M[0][3] = -projection.M[0][2] + ( orthoHorizontalOffset * projection.M[0][0] ); + + ortho.M[1][0] = 0.0f; + ortho.M[1][1] = -projection.M[1][1] * orthoScaleY; // Note sign flip (text rendering uses Y=down). + ortho.M[1][2] = 0.0f; + ortho.M[1][3] = -projection.M[1][2]; + + if ( fabsf ( zNear - zFar ) < 0.001f ) + { + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + ortho.M[2][2] = 0.0f; + ortho.M[2][3] = zFar; + } + else + { + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + ortho.M[2][2] = zFar / (zNear - zFar); + ortho.M[2][3] = (zFar * zNear) / (zNear - zFar); + } + + // No perspective correction for ortho. + ortho.M[3][0] = 0.0f; + ortho.M[3][1] = 0.0f; + ortho.M[3][2] = 0.0f; + ortho.M[3][3] = 1.0f; + + return ortho; +} + + +//----------------------------------------------------------------------------------- +// A set of "forward-mapping" functions, mapping from framebuffer space to real-world and/or texture space. + +// This mimics the first half of the distortion shader's function. +Vector2f TransformScreenNDCToTanFovSpace( DistortionRenderDesc const &distortion, + const Vector2f &framebufferNDC ) +{ + // Scale to TanHalfFov space, but still distorted. + Vector2f tanEyeAngleDistorted; + tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x; + tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y; + // Distort. + float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x ) + + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y ); + float distortionScale = distortion.Lens.DistortionFnScaleRadiusSquared ( radiusSquared ); + Vector2f tanEyeAngle; + tanEyeAngle.x = tanEyeAngleDistorted.x * distortionScale; + tanEyeAngle.y = tanEyeAngleDistorted.y * distortionScale; + + return tanEyeAngle; +} + +// Same, with chromatic aberration correction. +void TransformScreenNDCToTanFovSpaceChroma ( Vector2f *resultR, Vector2f *resultG, Vector2f *resultB, + DistortionRenderDesc const &distortion, + const Vector2f &framebufferNDC ) +{ + // Scale to TanHalfFov space, but still distorted. + Vector2f tanEyeAngleDistorted; + tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x; + tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y; + // Distort. + float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x ) + + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y ); + Vector3f distortionScales = distortion.Lens.DistortionFnScaleRadiusSquaredChroma ( radiusSquared ); + *resultR = tanEyeAngleDistorted * distortionScales.x; + *resultG = tanEyeAngleDistorted * distortionScales.y; + *resultB = tanEyeAngleDistorted * distortionScales.z; +} + +// This mimics the second half of the distortion shader's function. +Vector2f TransformTanFovSpaceToRendertargetTexUV( StereoEyeParams const &eyeParams, + Vector2f const &tanEyeAngle ) +{ + Vector2f textureUV; + textureUV.x = tanEyeAngle.x * eyeParams.EyeToSourceUV.Scale.x + eyeParams.EyeToSourceUV.Offset.x; + textureUV.y = tanEyeAngle.y * eyeParams.EyeToSourceUV.Scale.y + eyeParams.EyeToSourceUV.Offset.y; + return textureUV; +} + +Vector2f TransformTanFovSpaceToRendertargetNDC( StereoEyeParams const &eyeParams, + Vector2f const &tanEyeAngle ) +{ + Vector2f textureNDC; + textureNDC.x = tanEyeAngle.x * eyeParams.EyeToSourceNDC.Scale.x + eyeParams.EyeToSourceNDC.Offset.x; + textureNDC.y = tanEyeAngle.y * eyeParams.EyeToSourceNDC.Scale.y + eyeParams.EyeToSourceNDC.Offset.y; + return textureNDC; +} + +Vector2f TransformScreenPixelToScreenNDC( Recti const &distortionViewport, + Vector2f const &pixel ) +{ + // Move to [-1,1] NDC coords. + Vector2f framebufferNDC; + framebufferNDC.x = -1.0f + 2.0f * ( ( pixel.x - (float)distortionViewport.x ) / (float)distortionViewport.w ); + framebufferNDC.y = -1.0f + 2.0f * ( ( pixel.y - (float)distortionViewport.y ) / (float)distortionViewport.h ); + return framebufferNDC; +} + +Vector2f TransformScreenPixelToTanFovSpace( Recti const &distortionViewport, + DistortionRenderDesc const &distortion, + Vector2f const &pixel ) +{ + return TransformScreenNDCToTanFovSpace( distortion, + TransformScreenPixelToScreenNDC( distortionViewport, pixel ) ); +} + +Vector2f TransformScreenNDCToRendertargetTexUV( DistortionRenderDesc const &distortion, + StereoEyeParams const &eyeParams, + Vector2f const &pixel ) +{ + return TransformTanFovSpaceToRendertargetTexUV ( eyeParams, + TransformScreenNDCToTanFovSpace ( distortion, pixel ) ); +} + +Vector2f TransformScreenPixelToRendertargetTexUV( Recti const &distortionViewport, + DistortionRenderDesc const &distortion, + StereoEyeParams const &eyeParams, + Vector2f const &pixel ) +{ + return TransformTanFovSpaceToRendertargetTexUV ( eyeParams, + TransformScreenPixelToTanFovSpace ( distortionViewport, distortion, pixel ) ); +} + + +//----------------------------------------------------------------------------------- +// A set of "reverse-mapping" functions, mapping from real-world and/or texture space back to the framebuffer. + +Vector2f TransformTanFovSpaceToScreenNDC( DistortionRenderDesc const &distortion, + const Vector2f &tanEyeAngle, bool usePolyApprox /*= false*/ ) +{ + float tanEyeAngleRadius = tanEyeAngle.Length(); + float tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverseApprox ( tanEyeAngleRadius ); + if ( !usePolyApprox ) + { + tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverse ( tanEyeAngleRadius ); + } + Vector2f tanEyeAngleDistorted = tanEyeAngle; + if ( tanEyeAngleRadius > 0.0f ) + { + tanEyeAngleDistorted = tanEyeAngle * ( tanEyeAngleDistortedRadius / tanEyeAngleRadius ); + } + + Vector2f framebufferNDC; + framebufferNDC.x = ( tanEyeAngleDistorted.x / distortion.TanEyeAngleScale.x ) + distortion.LensCenter.x; + framebufferNDC.y = ( tanEyeAngleDistorted.y / distortion.TanEyeAngleScale.y ) + distortion.LensCenter.y; + + return framebufferNDC; +} + +Vector2f TransformRendertargetNDCToTanFovSpace( const ScaleAndOffset2D &eyeToSourceNDC, + const Vector2f &textureNDC ) +{ + Vector2f tanEyeAngle = (textureNDC - eyeToSourceNDC.Offset) / eyeToSourceNDC.Scale; + return tanEyeAngle; +} + + + +} //namespace OVR + +//Just want to make a copy disentangled from all these namespaces! +float ExtEvalCatmullRom10Spline ( float const *K, float scaledVal ) +{ + return(OVR::EvalCatmullRom10Spline ( K, scaledVal )); +} + + diff --git a/LibOVR/Src/OVR_Stereo.h b/LibOVR/Src/OVR_Stereo.h new file mode 100644 index 0000000..dd5499c --- /dev/null +++ b/LibOVR/Src/OVR_Stereo.h @@ -0,0 +1,460 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : OVR_Stereo.h +Content : Stereo rendering functions +Created : November 30, 2013 +Authors : Tom Fosyth + +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. + +*************************************************************************************/ + +#ifndef OVR_Stereo_h +#define OVR_Stereo_h + +#include "OVR_Device.h" + +// CAPI Forward declaration. +typedef struct ovrFovPort_ ovrFovPort; +typedef struct ovrRecti_ ovrRecti; + +namespace OVR { + +//----------------------------------------------------------------------------------- +// ***** Stereo Enumerations + +// StereoEye specifies which eye we are rendering for; it is used to +// retrieve StereoEyeParams. +enum StereoEye +{ + StereoEye_Center, + StereoEye_Left, + StereoEye_Right +}; + + +//----------------------------------------------------------------------------------- +// ***** FovPort + +// FovPort describes Field Of View (FOV) of a viewport. +// This class has values for up, down, left and right, stored in +// tangent of the angle units to simplify calculations. +// +// As an example, for a standard 90 degree vertical FOV, we would +// have: { UpTan = tan(90 degrees / 2), DownTan = tan(90 degrees / 2) }. +// +// CreateFromRadians/Degrees helper functions can be used to +// access FOV in different units. + +struct FovPort +{ + float UpTan; + float DownTan; + float LeftTan; + float RightTan; + + FovPort ( float sideTan = 0.0f ) : + UpTan(sideTan), DownTan(sideTan), LeftTan(sideTan), RightTan(sideTan) { } + FovPort ( float u, float d, float l, float r ) : + UpTan(u), DownTan(d), LeftTan(l), RightTan(r) { } + + // C-interop support: FovPort <-> ovrFovPort (implementation in OVR_CAPI.cpp). + FovPort(const ovrFovPort& src); + operator ovrFovPort () const; + + + static FovPort CreateFromRadians(float horizontalFov, float verticalFov) + { + FovPort result; + result.UpTan = tanf ( verticalFov * 0.5f ); + result.DownTan = tanf ( verticalFov * 0.5f ); + result.LeftTan = tanf ( horizontalFov * 0.5f ); + result.RightTan = tanf ( horizontalFov * 0.5f ); + return result; + } + + static FovPort CreateFromDegrees(float horizontalFovDegrees, + float verticalFovDegrees) + { + return CreateFromRadians(DegreeToRad(horizontalFovDegrees), + DegreeToRad(verticalFovDegrees)); + } + + // Get Horizontal/Vertical components of Fov in radians. + float GetVerticalFovRadians() const { return atanf(UpTan) + atanf(DownTan); } + float GetHorizontalFovRadians() const { return atanf(LeftTan) + atanf(RightTan); } + // Get Horizontal/Vertical components of Fov in degrees. + float GetVerticalFovDegrees() const { return RadToDegree(GetVerticalFovRadians()); } + float GetHorizontalFovDegrees() const { return RadToDegree(GetHorizontalFovRadians()); } + + // Compute maximum tangent value among all four sides. + float GetMaxSideTan() const + { + return Alg::Max(Alg::Max(UpTan, DownTan), Alg::Max(LeftTan, RightTan)); + } + + // Converts Fov Tan angle units to [-1,1] render target NDC space + Vector2f TanAngleToRendertargetNDC(Vector2f const &tanEyeAngle); + + + // Compute per-channel minimum and maximum of Fov. + static FovPort Min(const FovPort& a, const FovPort& b) + { + FovPort fov( Alg::Min( a.UpTan , b.UpTan ), + Alg::Min( a.DownTan , b.DownTan ), + Alg::Min( a.LeftTan , b.LeftTan ), + Alg::Min( a.RightTan, b.RightTan ) ); + return fov; + } + + static FovPort Max(const FovPort& a, const FovPort& b) + { + FovPort fov( Alg::Max( a.UpTan , b.UpTan ), + Alg::Max( a.DownTan , b.DownTan ), + Alg::Max( a.LeftTan , b.LeftTan ), + Alg::Max( a.RightTan, b.RightTan ) ); + return fov; + } +}; + + + +//----------------------------------------------------------------------------------- +// ***** ScaleAndOffset + +struct ScaleAndOffset2D +{ + Vector2f Scale; + Vector2f Offset; + + ScaleAndOffset2D(float sx = 0.0f, float sy = 0.0f, float ox = 0.0f, float oy = 0.0f) + : Scale(sx, sy), Offset(ox, oy) + { } +}; + + +//----------------------------------------------------------------------------------- +// ***** Misc. utility functions. + +// Inputs are 4 points (pFitX[0],pFitY[0]) through (pFitX[3],pFitY[3]) +// Result is four coefficients in pResults[0] through pResults[3] such that +// y = pResult[0] + x * ( pResult[1] + x * ( pResult[2] + x * ( pResult[3] ) ) ); +// passes through all four input points. +// Return is true if it succeeded, false if it failed (because two control points +// have the same pFitX value). +bool FitCubicPolynomial ( float *pResult, const float *pFitX, const float *pFitY ); + +//----------------------------------------------------------------------------------- +// ***** LensConfig + +// LensConfig describes the configuration of a single lens in an HMD. +// - Eqn and K[] describe a distortion function. +// - MetersPerTanAngleAtCenter is the relationship between distance on a +// screen (at the center of the lens), and the angle variance of the light after it +// has passed through the lens. +// - ChromaticAberration is an array of parameters for controlling +// additional Red and Blue scaling in order to reduce chromatic aberration +// caused by the Rift lenses. +struct LensConfig +{ + // The result is a scaling applied to the distance from the center of the lens. + float DistortionFnScaleRadiusSquared (float rsq) const; + // x,y,z components map to r,g,b scales. + Vector3f DistortionFnScaleRadiusSquaredChroma (float rsq) const; + + // DistortionFn applies distortion to the argument. + // Input: the distance in TanAngle/NIC space from the optical center to the input pixel. + // Output: the resulting distance after distortion. + float DistortionFn(float r) const + { + return r * DistortionFnScaleRadiusSquared ( r * r ); + } + + // DistortionFnInverse computes the inverse of the distortion function on an argument. + float DistortionFnInverse(float r) const; + + // Also computes the inverse, but using a polynomial approximation. Warning - it's just an approximation! + float DistortionFnInverseApprox(float r) const; + // Sets up InvK[]. + void SetUpInverseApprox(); + + // Sets a bunch of sensible defaults. + void SetToIdentity(); + + + + enum { NumCoefficients = 11 }; + + DistortionEqnType Eqn; + float K[NumCoefficients]; + float MaxR; // The highest R you're going to query for - the curve is unpredictable beyond it. + + float MetersPerTanAngleAtCenter; + + // Additional per-channel scaling is applied after distortion: + // Index [0] - Red channel constant coefficient. + // Index [1] - Red channel r^2 coefficient. + // Index [2] - Blue channel constant coefficient. + // Index [3] - Blue channel r^2 coefficient. + float ChromaticAberration[4]; + + float InvK[NumCoefficients]; + float MaxInvR; +}; + + +// For internal use - storing and loading lens config data + +// Returns true on success. +bool LoadLensConfig ( LensConfig *presult, UByte const *pbuffer, int bufferSizeInBytes ); + +// Returns number of bytes needed. +int SaveLensConfigSizeInBytes ( LensConfig const &config ); +// Returns true on success. +bool SaveLensConfig ( UByte *pbuffer, int bufferSizeInBytes, LensConfig const &config ); + + +//----------------------------------------------------------------------------------- +// ***** DistortionRenderDesc + +// This describes distortion for a single eye in an HMD with a display, not just the lens by itself. +struct DistortionRenderDesc +{ + // The raw lens values. + LensConfig Lens; + + // These map from [-1,1] across the eye being rendered into TanEyeAngle space (but still distorted) + Vector2f LensCenter; + Vector2f TanEyeAngleScale; + // Computed from device characteristics, IPD and eye-relief. + // (not directly used for rendering, but very useful) + Vector2f PixelsPerTanAngleAtCenter; +}; + + + +//----------------------------------------------------------------------------------- +// ***** HmdRenderInfo + +// All the parts of the HMD info that are needed to set up the rendering system. + +struct HmdRenderInfo +{ + // The start of this sturucture is intentionally very similar to HMDInfo in OVER_Device.h + // However to reduce interdependencies, one does not simply #include the other. + + HmdTypeEnum HmdType; + + // Size of the entire screen + Size<int> ResolutionInPixels; + Size<float> ScreenSizeInMeters; + float ScreenGapSizeInMeters; + + // Characteristics of the lenses. + float CenterFromTopInMeters; + float LensSeparationInMeters; + float LensDiameterInMeters; + float LensSurfaceToMidplateInMeters; + EyeCupType EyeCups; + + // Timing & shutter data. All values in seconds. + struct ShutterInfo + { + HmdShutterTypeEnum Type; + float VsyncToNextVsync; // 1/framerate + float VsyncToFirstScanline; // for global shutter, vsync->shutter open. + float FirstScanlineToLastScanline; // for global shutter, will be zero. + float PixelSettleTime; // estimated. + float PixelPersistence; // Full persistence = 1/framerate. + } Shutter; + + + // These are all set from the user's profile. + struct EyeConfig + { + // Distance from center of eyeball to front plane of lens. + float ReliefInMeters; + // Distance from nose (technically, center of Rift) to the middle of the eye. + float NoseToPupilInMeters; + + LensConfig Distortion; + } EyeLeft, EyeRight; + + + HmdRenderInfo() + { + HmdType = HmdType_None; + ResolutionInPixels.w = 0; + ResolutionInPixels.h = 0; + ScreenSizeInMeters.w = 0.0f; + ScreenSizeInMeters.h = 0.0f; + ScreenGapSizeInMeters = 0.0f; + CenterFromTopInMeters = 0.0f; + LensSeparationInMeters = 0.0f; + LensDiameterInMeters = 0.0f; + LensSurfaceToMidplateInMeters = 0.0f; + Shutter.Type = HmdShutter_LAST; + Shutter.VsyncToNextVsync = 0.0f; + Shutter.VsyncToFirstScanline = 0.0f; + Shutter.FirstScanlineToLastScanline = 0.0f; + Shutter.PixelSettleTime = 0.0f; + Shutter.PixelPersistence = 0.0f; + EyeCups = EyeCup_DK1A; + EyeLeft.ReliefInMeters = 0.0f; + EyeLeft.NoseToPupilInMeters = 0.0f; + EyeLeft.Distortion.SetToIdentity(); + EyeRight = EyeLeft; + } + + // The "center eye" is the position the HMD tracking returns, + // and games will also usually use it for audio, aiming reticles, some line-of-sight tests, etc. + EyeConfig GetEyeCenter() const + { + EyeConfig result; + result.ReliefInMeters = 0.5f * ( EyeLeft.ReliefInMeters + EyeRight.ReliefInMeters ); + result.NoseToPupilInMeters = 0.0f; + result.Distortion.SetToIdentity(); + return result; + } + +}; + + + + +//----------------------------------------------------------------------------------- + +// Stateless computation functions, in somewhat recommended execution order. +// For examples on how to use many of them, see the StereoConfig::UpdateComputedState function. + +const float OVR_DEFAULT_EXTRA_EYE_ROTATION = 30.0f * Math<float>::DegreeToRadFactor; + +// Creates a dummy debug HMDInfo matching a particular HMD model. +// Useful for development without an actual HMD attached. +HMDInfo CreateDebugHMDInfo(HmdTypeEnum hmdType); + + +// profile may be NULL, in which case it uses the hard-coded defaults. +// distortionType should be left at the default unless you require something specific for your distortion shaders. +// eyeCupOverride can be EyeCup_LAST, in which case it uses the one in the profile. +HmdRenderInfo GenerateHmdRenderInfoFromHmdInfo ( HMDInfo const &hmdInfo, + Profile const *profile = NULL, + DistortionEqnType distortionType = Distortion_CatmullRom10, + EyeCupType eyeCupOverride = EyeCup_LAST ); + +LensConfig GenerateLensConfigFromEyeRelief ( float eyeReliefInMeters, HmdRenderInfo const &hmd, + DistortionEqnType distortionType = Distortion_CatmullRom10 ); + +DistortionRenderDesc CalculateDistortionRenderDesc ( StereoEye eyeType, HmdRenderInfo const &hmd, + LensConfig const *pLensOverride = NULL ); + +FovPort CalculateFovFromEyePosition ( float eyeReliefInMeters, + float offsetToRightInMeters, + float offsetDownwardsInMeters, + float lensDiameterInMeters, + float extraEyeRotationInRadians = OVR_DEFAULT_EXTRA_EYE_ROTATION); + +FovPort CalculateFovFromHmdInfo ( StereoEye eyeType, + DistortionRenderDesc const &distortion, + HmdRenderInfo const &hmd, + float extraEyeRotationInRadians = OVR_DEFAULT_EXTRA_EYE_ROTATION ); + +FovPort GetPhysicalScreenFov ( StereoEye eyeType, DistortionRenderDesc const &distortion ); + +FovPort ClampToPhysicalScreenFov ( StereoEye eyeType, DistortionRenderDesc const &distortion, + FovPort inputFovPort ); + +Sizei CalculateIdealPixelSize ( StereoEye eyeType, DistortionRenderDesc const &distortion, + FovPort fov, float pixelsPerDisplayPixel ); + +Recti GetFramebufferViewport ( StereoEye eyeType, HmdRenderInfo const &hmd ); + +Matrix4f CreateProjection ( bool rightHanded, FovPort fov, + float zNear = 0.01f, float zFar = 10000.0f ); + +Matrix4f CreateOrthoSubProjection ( bool rightHanded, StereoEye eyeType, + float tanHalfFovX, float tanHalfFovY, + float unitsX, float unitsY, float distanceFromCamera, + float interpupillaryDistance, Matrix4f const &projection, + float zNear = 0.0f, float zFar = 0.0f ); + +ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov ( FovPort fov ); + +ScaleAndOffset2D CreateUVScaleAndOffsetfromNDCScaleandOffset ( ScaleAndOffset2D scaleAndOffsetNDC, + Recti renderedViewport, + Sizei renderTargetSize ); + + +//----------------------------------------------------------------------------------- +// ***** StereoEyeParams + +// StereoEyeParams describes RenderDevice configuration needed to render +// the scene for one eye. +struct StereoEyeParams +{ + StereoEye Eye; + Matrix4f ViewAdjust; // Translation to be applied to view matrix. + + // Distortion and the VP on the physical display - the thing to run the distortion shader on. + DistortionRenderDesc Distortion; + Recti DistortionViewport; + + // Projection and VP of a particular view (you could have multiple of these). + Recti RenderedViewport; // Viewport that we render the standard scene to. + FovPort Fov; // The FOVs of this scene. + Matrix4f RenderedProjection; // Projection matrix used with this eye. + ScaleAndOffset2D EyeToSourceNDC; // Mapping from TanEyeAngle space to [-1,+1] on the rendered image. + ScaleAndOffset2D EyeToSourceUV; // Mapping from TanEyeAngle space to actual texture UV coords. +}; + + +//----------------------------------------------------------------------------------- +// A set of "forward-mapping" functions, mapping from framebuffer space to real-world and/or texture space. +Vector2f TransformScreenNDCToTanFovSpace ( DistortionRenderDesc const &distortion, + const Vector2f &framebufferNDC ); +void TransformScreenNDCToTanFovSpaceChroma ( Vector2f *resultR, Vector2f *resultG, Vector2f *resultB, + DistortionRenderDesc const &distortion, + const Vector2f &framebufferNDC ); +Vector2f TransformTanFovSpaceToRendertargetTexUV ( StereoEyeParams const &eyeParams, + Vector2f const &tanEyeAngle ); +Vector2f TransformTanFovSpaceToRendertargetNDC ( StereoEyeParams const &eyeParams, + Vector2f const &tanEyeAngle ); +Vector2f TransformScreenPixelToScreenNDC( Recti const &distortionViewport, + Vector2f const &pixel ); +Vector2f TransformScreenPixelToTanFovSpace ( Recti const &distortionViewport, + DistortionRenderDesc const &distortion, + Vector2f const &pixel ); +Vector2f TransformScreenNDCToRendertargetTexUV( DistortionRenderDesc const &distortion, + StereoEyeParams const &eyeParams, + Vector2f const &pixel ); +Vector2f TransformScreenPixelToRendertargetTexUV( Recti const &distortionViewport, + DistortionRenderDesc const &distortion, + StereoEyeParams const &eyeParams, + Vector2f const &pixel ); + +// A set of "reverse-mapping" functions, mapping from real-world and/or texture space back to the framebuffer. +// Be aware that many of these are significantly slower than their forward-mapping counterparts. +Vector2f TransformTanFovSpaceToScreenNDC( DistortionRenderDesc const &distortion, + const Vector2f &tanEyeAngle, bool usePolyApprox = false ); +Vector2f TransformRendertargetNDCToTanFovSpace( const ScaleAndOffset2D &eyeToSourceNDC, + const Vector2f &textureNDC ); + +} //namespace OVR + +#endif // OVR_Stereo_h
\ No newline at end of file diff --git a/LibOVR/Src/OVR_ThreadCommandQueue.cpp b/LibOVR/Src/OVR_ThreadCommandQueue.cpp new file mode 100644 index 0000000..bc0d7dc --- /dev/null +++ b/LibOVR/Src/OVR_ThreadCommandQueue.cpp @@ -0,0 +1,380 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_ThreadCommandQueue.cpp +Content : Command queue for operations executed on a thread +Created : October 29, 2012 + +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 "OVR_ThreadCommandQueue.h" + +namespace OVR { + + +//------------------------------------------------------------------------ +// ***** CircularBuffer + +// CircularBuffer is a FIFO buffer implemented in a single block of memory, +// which allows writing and reading variable-size data chucks. Write fails +// if buffer is full. + +class CircularBuffer +{ + enum { + AlignSize = 16, + AlignMask = AlignSize - 1 + }; + + UByte* pBuffer; + UPInt Size; + UPInt Tail; // Byte offset of next item to be popped. + UPInt Head; // Byte offset of where next push will take place. + UPInt End; // When Head < Tail, this is used instead of Size. + + inline UPInt roundUpSize(UPInt size) + { return (size + AlignMask) & ~(UPInt)AlignMask; } + +public: + + CircularBuffer(UPInt size) + : Size(size), Tail(0), Head(0), End(0) + { + pBuffer = (UByte*)OVR_ALLOC_ALIGNED(roundUpSize(size), AlignSize); + } + ~CircularBuffer() + { + // For ThreadCommands, we must consume everything before shutdown. + OVR_ASSERT(IsEmpty()); + OVR_FREE_ALIGNED(pBuffer); + } + + bool IsEmpty() const { return (Head == Tail); } + + // Allocates a state block of specified size and advances pointers, + // returning 0 if buffer is full. + UByte* Write(UPInt size); + + // Returns a pointer to next available data block; 0 if none available. + UByte* ReadBegin() + { return (Head != Tail) ? (pBuffer + Tail) : 0; } + // Consumes data of specified size; this must match size passed to Write. + void ReadEnd(UPInt size); +}; + + +// Allocates a state block of specified size and advances pointers, +// returning 0 if buffer is full. +UByte* CircularBuffer::Write(UPInt size) +{ + UByte* p = 0; + + size = roundUpSize(size); + // Since this is circular buffer, always allow at least one item. + OVR_ASSERT(size < Size/2); + + if (Head >= Tail) + { + OVR_ASSERT(End == 0); + + if (size <= (Size - Head)) + { + p = pBuffer + Head; + Head += size; + } + else if (size < Tail) + { + p = pBuffer; + End = Head; + Head = size; + OVR_ASSERT(Head != Tail); + } + } + else + { + OVR_ASSERT(End != 0); + + if ((Tail - Head) > size) + { + p = pBuffer + Head; + Head += size; + OVR_ASSERT(Head != Tail); + } + } + + return p; +} + +void CircularBuffer::ReadEnd(UPInt size) +{ + OVR_ASSERT(Head != Tail); + size = roundUpSize(size); + + Tail += size; + if (Tail == End) + { + Tail = End = 0; + } + else if (Tail == Head) + { + OVR_ASSERT(End == 0); + Tail = Head = 0; + } +} + + +//------------------------------------------------------------------------------------- +// ***** ThreadCommand + +ThreadCommand::PopBuffer::~PopBuffer() +{ + if (Size) + Destruct<ThreadCommand>(toCommand()); +} + +void ThreadCommand::PopBuffer::InitFromBuffer(void* data) +{ + ThreadCommand* cmd = (ThreadCommand*)data; + OVR_ASSERT(cmd->Size <= MaxSize); + + if (Size) + Destruct<ThreadCommand>(toCommand()); + Size = cmd->Size; + memcpy(Buffer, (void*)cmd, Size); +} + +void ThreadCommand::PopBuffer::Execute() +{ + ThreadCommand* command = toCommand(); + OVR_ASSERT(command); + command->Execute(); + if (NeedsWait()) + GetEvent()->PulseEvent(); +} + +//------------------------------------------------------------------------------------- + +class ThreadCommandQueueImpl : public NewOverrideBase +{ + typedef ThreadCommand::NotifyEvent NotifyEvent; + friend class ThreadCommandQueue; + +public: + + ThreadCommandQueueImpl(ThreadCommandQueue* queue) + : pQueue(queue), ExitEnqueued(false), ExitProcessed(false), CommandBuffer(2048) + { + } + ~ThreadCommandQueueImpl(); + + + bool PushCommand(const ThreadCommand& command); + bool PopCommand(ThreadCommand::PopBuffer* popBuffer); + + + // ExitCommand is used by notify us that Thread is shutting down. + struct ExitCommand : public ThreadCommand + { + ThreadCommandQueueImpl* pImpl; + + ExitCommand(ThreadCommandQueueImpl* impl, bool wait) + : ThreadCommand(sizeof(ExitCommand), wait, true), pImpl(impl) { } + + virtual void Execute() const + { + Lock::Locker lock(&pImpl->QueueLock); + pImpl->ExitProcessed = true; + } + virtual ThreadCommand* CopyConstruct(void* p) const + { return Construct<ExitCommand>(p, *this); } + }; + + + NotifyEvent* AllocNotifyEvent_NTS() + { + NotifyEvent* p = AvailableEvents.GetFirst(); + + if (!AvailableEvents.IsNull(p)) + p->RemoveNode(); + else + p = new NotifyEvent; + return p; + } + + void FreeNotifyEvent_NTS(NotifyEvent* p) + { + AvailableEvents.PushBack(p); + } + + void FreeNotifyEvents_NTS() + { + while(!AvailableEvents.IsEmpty()) + { + NotifyEvent* p = AvailableEvents.GetFirst(); + p->RemoveNode(); + delete p; + } + } + + ThreadCommandQueue* pQueue; + Lock QueueLock; + volatile bool ExitEnqueued; + volatile bool ExitProcessed; + List<NotifyEvent> AvailableEvents; + List<NotifyEvent> BlockedProducers; + CircularBuffer CommandBuffer; +}; + + + +ThreadCommandQueueImpl::~ThreadCommandQueueImpl() +{ + Lock::Locker lock(&QueueLock); + OVR_ASSERT(BlockedProducers.IsEmpty()); + FreeNotifyEvents_NTS(); +} + +bool ThreadCommandQueueImpl::PushCommand(const ThreadCommand& command) +{ + ThreadCommand::NotifyEvent* completeEvent = 0; + ThreadCommand::NotifyEvent* queueAvailableEvent = 0; + + // Repeat writing command into buffer until it is available. + do { + + { // Lock Scope + Lock::Locker lock(&QueueLock); + + if (queueAvailableEvent) + { + FreeNotifyEvent_NTS(queueAvailableEvent); + queueAvailableEvent = 0; + } + + // Don't allow any commands after PushExitCommand() is called. + if (ExitEnqueued && !command.ExitFlag) + return false; + + + bool bufferWasEmpty = CommandBuffer.IsEmpty(); + UByte* buffer = CommandBuffer.Write(command.GetSize()); + if (buffer) + { + ThreadCommand* c = command.CopyConstruct(buffer); + if (c->NeedsWait()) + completeEvent = c->pEvent = AllocNotifyEvent_NTS(); + // Signal-waker consumer when we add data to buffer. + if (bufferWasEmpty) + pQueue->OnPushNonEmpty_Locked(); + break; + } + + queueAvailableEvent = AllocNotifyEvent_NTS(); + BlockedProducers.PushBack(queueAvailableEvent); + } // Lock Scope + + queueAvailableEvent->Wait(); + + } while(1); + + // Command was enqueued, wait if necessary. + if (completeEvent) + { + completeEvent->Wait(); + Lock::Locker lock(&QueueLock); + FreeNotifyEvent_NTS(completeEvent); + } + + return true; +} + + +// Pops the next command from the thread queue, if any is available. +bool ThreadCommandQueueImpl::PopCommand(ThreadCommand::PopBuffer* popBuffer) +{ + Lock::Locker lock(&QueueLock); + + UByte* buffer = CommandBuffer.ReadBegin(); + if (!buffer) + { + // Notify thread while in lock scope, enabling initialization of wait. + pQueue->OnPopEmpty_Locked(); + return false; + } + + popBuffer->InitFromBuffer(buffer); + CommandBuffer.ReadEnd(popBuffer->GetSize()); + + if (!BlockedProducers.IsEmpty()) + { + ThreadCommand::NotifyEvent* queueAvailableEvent = BlockedProducers.GetFirst(); + queueAvailableEvent->RemoveNode(); + queueAvailableEvent->PulseEvent(); + // Event is freed later by waiter. + } + return true; +} + + +//------------------------------------------------------------------------------------- + +ThreadCommandQueue::ThreadCommandQueue() +{ + pImpl = new ThreadCommandQueueImpl(this); +} +ThreadCommandQueue::~ThreadCommandQueue() +{ + delete pImpl; +} + +bool ThreadCommandQueue::PushCommand(const ThreadCommand& command) +{ + return pImpl->PushCommand(command); +} + +bool ThreadCommandQueue::PopCommand(ThreadCommand::PopBuffer* popBuffer) +{ + return pImpl->PopCommand(popBuffer); +} + +void ThreadCommandQueue::PushExitCommand(bool wait) +{ + // Exit is processed in two stages: + // - First, ExitEnqueued flag is set to block further commands from queuing up. + // - Second, the actual exit call is processed on the consumer thread, flushing + // any prior commands. + // IsExiting() only returns true after exit has flushed. + { + Lock::Locker lock(&pImpl->QueueLock); + if (pImpl->ExitEnqueued) + return; + pImpl->ExitEnqueued = true; + } + + PushCommand(ThreadCommandQueueImpl::ExitCommand(pImpl, wait)); +} + +bool ThreadCommandQueue::IsExiting() const +{ + return pImpl->ExitProcessed; +} + + +} // namespace OVR diff --git a/LibOVR/Src/OVR_ThreadCommandQueue.h b/LibOVR/Src/OVR_ThreadCommandQueue.h new file mode 100644 index 0000000..9774212 --- /dev/null +++ b/LibOVR/Src/OVR_ThreadCommandQueue.h @@ -0,0 +1,319 @@ +/************************************************************************************ + +PublicHeader: None +Filename : OVR_ThreadCommandQueue.h +Content : Command queue for operations executed on a thread +Created : October 29, 2012 +Author : 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. + +************************************************************************************/ + +#ifndef OVR_ThreadCommandQueue_h +#define OVR_ThreadCommandQueue_h + +#include "Kernel/OVR_Types.h" +#include "Kernel/OVR_List.h" +#include "Kernel/OVR_Atomic.h" +#include "Kernel/OVR_Threads.h" + +namespace OVR { + +class ThreadCommand; +class ThreadCommandQueue; + + +//------------------------------------------------------------------------------------- +// ***** ThreadCommand + +// ThreadCommand is a base class implementation for commands stored in ThreadCommandQueue. +class ThreadCommand +{ +public: + + // NotifyEvent is used by ThreadCommandQueue::PushCallAndWait to notify the + // calling (producer) thread when command is completed or queue slot is available. + class NotifyEvent : public ListNode<NotifyEvent>, public NewOverrideBase + { + Event E; + public: + NotifyEvent() { } + + void Wait() { E.Wait(); } + void PulseEvent() { E.PulseEvent(); } + }; + + // ThreadCommand::PopBuffer is temporary storage for a command popped off + // by ThreadCommandQueue::PopCommand. + class PopBuffer + { + enum { MaxSize = 256 }; + + UPInt Size; + union { + UByte Buffer[MaxSize]; + UPInt Align; + }; + + ThreadCommand* toCommand() const { return (ThreadCommand*)Buffer; } + + public: + PopBuffer() : Size(0) { } + ~PopBuffer(); + + void InitFromBuffer(void* data); + + bool HasCommand() const { return Size != 0; } + UPInt GetSize() const { return Size; } + bool NeedsWait() const { return toCommand()->NeedsWait(); } + NotifyEvent* GetEvent() const { return toCommand()->pEvent; } + + // Execute the command and also notifies caller to finish waiting, + // if necessary. + void Execute(); + }; + + UInt16 Size; + bool WaitFlag; + bool ExitFlag; // Marks the last exit command. + NotifyEvent* pEvent; + + ThreadCommand(UPInt size, bool waitFlag, bool exitFlag = false) + : Size((UInt16)size), WaitFlag(waitFlag), ExitFlag(exitFlag), pEvent(0) { } + virtual ~ThreadCommand() { } + + bool NeedsWait() const { return WaitFlag; } + UPInt GetSize() const { return Size; } + + virtual void Execute() const = 0; + // Copy constructor used for serializing this to memory buffer. + virtual ThreadCommand* CopyConstruct(void* p) const = 0; +}; + + +//------------------------------------------------------------------------------------- + +// CleanType is a template that strips 'const' and '&' modifiers from the argument type; +// for example, typename CleanType<A&>::Type is equivalent to A. +template<class T> struct CleanType { typedef T Type; }; +template<class T> struct CleanType<T&> { typedef T Type; }; +template<class T> struct CleanType<const T> { typedef T Type; }; +template<class T> struct CleanType<const T&> { typedef T Type; }; + +// SelfType is a template that yields the argument type. This helps avoid conflicts with +// automatic template argument deduction for function calls when identical argument +// is already defined. +template<class T> struct SelfType { typedef T Type; }; + + + +//------------------------------------------------------------------------------------- +// ThreadCommand specializations for member functions with different number of +// arguments and argument types. + +// Used to return nothing from a ThreadCommand, to avoid problems with 'void'. +struct Void +{ + Void() {} + Void(int) {} +}; + +// ThreadCommand for member function with 0 arguments. +template<class C, class R> +class ThreadCommandMF0 : public ThreadCommand +{ + typedef R (C::*FnPtr)(); + C* pClass; + FnPtr pFn; + R* pRet; + + void executeImpl() const + { + pRet ? (void)(*pRet = (pClass->*pFn)()) : + (void)(pClass->*pFn)(); + } + +public: + ThreadCommandMF0(C* pclass, FnPtr fn, R* ret, bool needsWait) + : ThreadCommand(sizeof(ThreadCommandMF0), needsWait), + pClass(pclass), pFn(fn), pRet(ret) { } + + virtual void Execute() const { executeImpl(); } + virtual ThreadCommand* CopyConstruct(void* p) const + { return Construct<ThreadCommandMF0>(p, *this); } +}; + + +// ThreadCommand for member function with 1 argument. +template<class C, class R, class A0> +class ThreadCommandMF1 : public ThreadCommand +{ + typedef R (C::*FnPtr)(A0); + C* pClass; + FnPtr pFn; + R* pRet; + typename CleanType<A0>::Type AVal0; + + void executeImpl() const + { + pRet ? (void)(*pRet = (pClass->*pFn)(AVal0)) : + (void)(pClass->*pFn)(AVal0); + } + +public: + ThreadCommandMF1(C* pclass, FnPtr fn, R* ret, A0 a0, bool needsWait) + : ThreadCommand(sizeof(ThreadCommandMF1), needsWait), + pClass(pclass), pFn(fn), pRet(ret), AVal0(a0) { } + + virtual void Execute() const { executeImpl(); } + virtual ThreadCommand* CopyConstruct(void* p) const + { return Construct<ThreadCommandMF1>(p, *this); } +}; + +// ThreadCommand for member function with 2 arguments. +template<class C, class R, class A0, class A1> +class ThreadCommandMF2 : public ThreadCommand +{ + typedef R (C::*FnPtr)(A0, A1); + C* pClass; + FnPtr pFn; + R* pRet; + typename CleanType<A0>::Type AVal0; + typename CleanType<A1>::Type AVal1; + + void executeImpl() const + { + pRet ? (void)(*pRet = (pClass->*pFn)(AVal0, AVal1)) : + (void)(pClass->*pFn)(AVal0, AVal1); + } + +public: + ThreadCommandMF2(C* pclass, FnPtr fn, R* ret, A0 a0, A1 a1, bool needsWait) + : ThreadCommand(sizeof(ThreadCommandMF2), needsWait), + pClass(pclass), pFn(fn), pRet(ret), AVal0(a0), AVal1(a1) { } + + virtual void Execute() const { executeImpl(); } + virtual ThreadCommand* CopyConstruct(void* p) const + { return Construct<ThreadCommandMF2>(p, *this); } +}; + + +//------------------------------------------------------------------------------------- +// ***** ThreadCommandQueue + +// ThreadCommandQueue is a queue of executable function-call commands intended to be +// serviced by a single consumer thread. Commands are added to the queue with PushCall +// and removed with PopCall; they are processed in FIFO order. Multiple producer threads +// are supported and will be blocked if internal data buffer is full. + +class ThreadCommandQueue +{ +public: + + ThreadCommandQueue(); + virtual ~ThreadCommandQueue(); + + + // Pops the next command from the thread queue, if any is available. + // The command should be executed by calling popBuffer->Execute(). + // Returns 'false' if no command is available at the time of the call. + bool PopCommand(ThreadCommand::PopBuffer* popBuffer); + + // Generic implementaion of PushCommand; enqueues a command for execution. + // Returns 'false' if push failed, usually indicating thread shutdown. + bool PushCommand(const ThreadCommand& command); + + // + void PushExitCommand(bool wait); + + // Returns 'true' once ExitCommand has been processed, so the thread can shut down. + bool IsExiting() const; + + + // These two virtual functions serve as notifications for derived + // thread waiting. + virtual void OnPushNonEmpty_Locked() { } + virtual void OnPopEmpty_Locked() { } + + + // *** PushCall with no result + + // Enqueue a member function of 'this' class to be called on consumer thread. + // By default the function returns immediately; set 'wait' argument to 'true' to + // wait for completion. + template<class C, class R> + bool PushCall(R (C::*fn)(), bool wait = false) + { return PushCommand(ThreadCommandMF0<C,R>(static_cast<C*>(this), fn, 0, wait)); } + template<class C, class R, class A0> + bool PushCall(R (C::*fn)(A0), typename SelfType<A0>::Type a0, bool wait = false) + { return PushCommand(ThreadCommandMF1<C,R,A0>(static_cast<C*>(this), fn, 0, a0, wait)); } + template<class C, class R, class A0, class A1> + bool PushCall(R (C::*fn)(A0, A1), + typename SelfType<A0>::Type a0, typename SelfType<A1>::Type a1, bool wait = false) + { return PushCommand(ThreadCommandMF2<C,R,A0,A1>(static_cast<C*>(this), fn, 0, a0, a1, wait)); } + // Enqueue a specified member function call of class C. + // By default the function returns immediately; set 'wait' argument to 'true' to + // wait for completion. + template<class C, class R> + bool PushCall(C* p, R (C::*fn)(), bool wait = false) + { return PushCommand(ThreadCommandMF0<C,R>(p, fn, 0, wait)); } + template<class C, class R, class A0> + bool PushCall(C* p, R (C::*fn)(A0), typename SelfType<A0>::Type a0, bool wait = false) + { return PushCommand(ThreadCommandMF1<C,R,A0>(p, fn, 0, a0, wait)); } + template<class C, class R, class A0, class A1> + bool PushCall(C* p, R (C::*fn)(A0, A1), + typename SelfType<A0>::Type a0, typename SelfType<A1>::Type a1, bool wait = false) + { return PushCommand(ThreadCommandMF2<C,R,A0,A1>(p, fn, 0, a0, a1, wait)); } + + + // *** PushCall with Result + + // Enqueue a member function of 'this' class call and wait for call to complete + // on consumer thread before returning. + template<class C, class R> + bool PushCallAndWaitResult(R (C::*fn)(), R* ret) + { return PushCommand(ThreadCommandMF0<C,R>(static_cast<C*>(this), fn, ret, true)); } + template<class C, class R, class A0> + bool PushCallAndWaitResult(R (C::*fn)(A0), R* ret, typename SelfType<A0>::Type a0) + { return PushCommand(ThreadCommandMF1<C,R,A0>(static_cast<C*>(this), fn, ret, a0, true)); } + template<class C, class R, class A0, class A1> + bool PushCallAndWaitResult(R (C::*fn)(A0, A1), R* ret, + typename SelfType<A0>::Type a0, typename SelfType<A1>::Type a1) + { return PushCommand(ThreadCommandMF2<C,R,A0,A1>(static_cast<C*>(this), fn, ret, a0, a1, true)); } + // Enqueue a member function call for class C and wait for the call to complete + // on consumer thread before returning. + template<class C, class R> + bool PushCallAndWaitResult(C* p, R (C::*fn)(), R* ret) + { return PushCommand(ThreadCommandMF0<C,R>(p, fn, ret, true)); } + template<class C, class R, class A0> + bool PushCallAndWaitResult(C* p, R (C::*fn)(A0), R* ret, typename SelfType<A0>::Type a0) + { return PushCommand(ThreadCommandMF1<C,R,A0>(p, fn, ret, a0, true)); } + template<class C, class R, class A0, class A1> + bool PushCallAndWaitResult(C* p, R (C::*fn)(A0, A1), R* ret, + typename SelfType<A0>::Type a0, typename SelfType<A1>::Type a1) + { return PushCommand(ThreadCommandMF2<C,R,A0,A1>(p, fn, ret, a0, a1, true)); } + +private: + class ThreadCommandQueueImpl* pImpl; +}; + + +} + +#endif // OVR_ThreadCommandQueue_h diff --git a/LibOVR/Src/Util/Util_ImageWindow.cpp b/LibOVR/Src/Util/Util_ImageWindow.cpp new file mode 100644 index 0000000..cb091c7 --- /dev/null +++ b/LibOVR/Src/Util/Util_ImageWindow.cpp @@ -0,0 +1,511 @@ +/************************************************************************************ + +Filename : Util_ImageWindow.cpp +Content : An output object for windows that can display raw images for testing +Created : March 13, 2014 +Authors : Dean Beeler + +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 "../../Include/OVR.h" + +#include "Util_ImageWindow.h" + +#if defined(OVR_OS_WIN32) + +#include <Windows.h> + +#include "DWrite.h" + +typedef HRESULT (WINAPI *D2D1CreateFactoryFn)( + _In_ D2D1_FACTORY_TYPE, + _In_ REFIID, + _In_opt_ const D2D1_FACTORY_OPTIONS*, + _Out_ ID2D1Factory ** + ); + +typedef HRESULT (WINAPI *DWriteCreateFactoryFn)( + _In_ DWRITE_FACTORY_TYPE factoryType, + _In_ REFIID iid, + _Out_ IUnknown **factory + ); + + +namespace OVR { namespace Util { + +ID2D1Factory* ImageWindow::pD2DFactory = NULL; +IDWriteFactory* ImageWindow::pDWriteFactory = NULL; +ImageWindow* ImageWindow::globalWindow[4]; +int ImageWindow::windowCount = 0; + +LRESULT CALLBACK MainWndProc( + HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + switch (uMsg) + { + case WM_CREATE: + return 0; + + case WM_PAINT: + { + LONG_PTR ptr = GetWindowLongPtr( hwnd, GWLP_USERDATA ); + if( ptr ) + { + ImageWindow* iw = (ImageWindow*)ptr; + iw->OnPaint(); + } + } + + return 0; + + case WM_SIZE: + // Set the size and position of the window. + return 0; + + case WM_DESTROY: + // Clean up window-specific data objects. + return 0; + + // + // Process other messages. + // + + default: + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + //return 0; +} + +ImageWindow::ImageWindow( uint32_t width, uint32_t height ) : + frontBufferMutex( new Mutex() ) +{ + + HINSTANCE hInst = LoadLibrary( L"d2d1.dll" ); + HINSTANCE hInstWrite = LoadLibrary( L"Dwrite.dll" ); + + D2D1CreateFactoryFn createFactory = NULL; + DWriteCreateFactoryFn writeFactory = NULL; + + if( hInst ) + { + createFactory = (D2D1CreateFactoryFn)GetProcAddress( hInst, "D2D1CreateFactory" ); + } + + if( hInstWrite ) + { + writeFactory = (DWriteCreateFactoryFn)GetProcAddress( hInstWrite, "DWriteCreateFactory" ); + } + + globalWindow[windowCount] = this; + + ++windowCount; + + if( pD2DFactory == NULL && createFactory && writeFactory ) + { + createFactory( + D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory), + NULL, + &pD2DFactory + ); + + // Create a DirectWrite factory. + writeFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(pDWriteFactory), + reinterpret_cast<IUnknown **>(&pDWriteFactory) + ); + + } + + resolution = D2D1::SizeU( width, height ); + + SetWindowLongPtr( hWindow, GWLP_USERDATA, (LONG_PTR)this ); + + pRT = NULL; + greyBitmap = NULL; + colorBitmap = NULL; +} + +ImageWindow::~ImageWindow() +{ + for( int i = 0; i < MaxWindows; ++i ) + { + if( globalWindow[i] == this ) + { + globalWindow[i] = NULL; + break; + } +} + + if( greyBitmap ) + greyBitmap->Release(); + + if( colorBitmap ) + colorBitmap->Release(); + + if( pRT ) + pRT->Release(); + + { + Mutex::Locker locker( frontBufferMutex ); + + while( frames.GetSize() ) + { + Ptr<Frame> aFrame = frames.PopBack(); + } + } + + delete frontBufferMutex; + + ShowWindow( hWindow, SW_HIDE ); + DestroyWindow( hWindow ); +} + +void ImageWindow::AssociateSurface( void* surface ) +{ + // Assume an IUnknown + IUnknown* unknown = (IUnknown*)surface; + + IDXGISurface *pDxgiSurface = NULL; + HRESULT hr = unknown->QueryInterface(&pDxgiSurface); + if( hr == S_OK ) + { + D2D1_RENDER_TARGET_PROPERTIES props = + D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), + 96, + 96 + ); + + + pRT = NULL; + ID2D1RenderTarget* tmpTarget; + + hr = pD2DFactory->CreateDxgiSurfaceRenderTarget( pDxgiSurface, &props, &tmpTarget ); + + if( hr == S_OK ) + { + DXGI_SURFACE_DESC desc = {0}; + pDxgiSurface->GetDesc( &desc ); + int width = desc.Width; + int height = desc.Height; + + D2D1_SIZE_U size = D2D1::SizeU( width, height ); + + D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat( + DXGI_FORMAT_A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED + ); + + D2D1_PIXEL_FORMAT colorPixelFormat = D2D1::PixelFormat( + DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED + ); + + D2D1_BITMAP_PROPERTIES bitmapProps; + bitmapProps.dpiX = 96; + bitmapProps.dpiY = 96; + bitmapProps.pixelFormat = pixelFormat; + + D2D1_BITMAP_PROPERTIES colorBitmapProps; + colorBitmapProps.dpiX = 96; + colorBitmapProps.dpiY = 96; + colorBitmapProps.pixelFormat = colorPixelFormat; + + HRESULT result = tmpTarget->CreateBitmap( size, bitmapProps, &greyBitmap ); + if( result != S_OK ) + { + tmpTarget->Release(); + tmpTarget = NULL; + } + + result = tmpTarget->CreateBitmap( size, colorBitmapProps, &colorBitmap ); + if( result != S_OK ) + { + greyBitmap->Release(); + greyBitmap = NULL; + + tmpTarget->Release(); + tmpTarget = NULL; + } + pRT = tmpTarget; + } + } +} + +void ImageWindow::Process() +{ + if( pRT && greyBitmap ) + { + OnPaint(); + + pRT->Flush(); + } +} + +void ImageWindow::Complete() +{ + Mutex::Locker locker( frontBufferMutex ); + + if( frames.IsEmpty() ) + return; + + if( frames.PeekBack(0)->ready ) + return; + + Ptr<Frame> frame = frames.PeekBack(0); + + frame->ready = true; +} + +void ImageWindow::OnPaint() +{ + Mutex::Locker locker( frontBufferMutex ); + + // Nothing to do + if( frames.IsEmpty() ) + return; + + if( !frames.PeekFront(0)->ready ) + return; + + Ptr<Frame> currentFrame = frames.PopFront(); + + Ptr<Frame> nextFrame = NULL; + + if( !frames.IsEmpty() ) + nextFrame = frames.PeekFront(0); + + while( nextFrame && nextFrame->ready ) + { + // Free up the current frame since it's been removed from the deque + currentFrame = frames.PopFront(); + + if( frames.IsEmpty() ) + break; + + nextFrame = frames.PeekFront(0); + } + + if( currentFrame->imageData ) + greyBitmap->CopyFromMemory( NULL, currentFrame->imageData, currentFrame->width ); + + if( currentFrame->colorImageData ) + colorBitmap->CopyFromMemory( NULL, currentFrame->colorImageData, currentFrame->colorPitch ); + + pRT->BeginDraw(); + + pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + pRT->Clear( D2D1::ColorF(D2D1::ColorF::Black) ); + + // This will mirror our image + D2D1_MATRIX_3X2_F m; + m._11 = -1; m._12 = 0; + m._21 = 0; m._22 = 1; + m._31 = 0; m._32 = 0; + pRT->SetTransform( m ); + + ID2D1SolidColorBrush* whiteBrush; + + pRT->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::White, 1.0f), &whiteBrush ); + + if( currentFrame->imageData ) + { + pRT->FillOpacityMask( greyBitmap, whiteBrush, + D2D1_OPACITY_MASK_CONTENT_TEXT_NATURAL, + D2D1::RectF( -(FLOAT)resolution.width, 0.0f, (FLOAT)0.0f, (FLOAT)resolution.height ), + //D2D1::RectF( 0.0f, 0.0f, (FLOAT)0.0f, (FLOAT)resolution.height ), + D2D1::RectF( 0.0f, 0.0f, (FLOAT)resolution.width, (FLOAT)resolution.height ) ); + } + else if( currentFrame->colorImageData ) + { + pRT->DrawBitmap( colorBitmap, + D2D1::RectF( -(FLOAT)resolution.width, 0.0f, (FLOAT)0.0f, (FLOAT)resolution.height ) ); + + } + + pRT->SetTransform(D2D1::Matrix3x2F::Identity()); + + whiteBrush->Release(); + + Array<CirclePlot>::Iterator it; + + for( it = currentFrame->plots.Begin(); it != currentFrame->plots.End(); ++it ) + { + ID2D1SolidColorBrush* aBrush; + + pRT->CreateSolidColorBrush( D2D1::ColorF( it->r, it->g, it->b), &aBrush ); + + D2D1_ELLIPSE ellipse; + ellipse.point.x = it->x; + ellipse.point.y = it->y; + ellipse.radiusX = it->radius; + ellipse.radiusY = it->radius; + + if( it->fill ) + pRT->FillEllipse( &ellipse, aBrush ); + else + pRT->DrawEllipse( &ellipse, aBrush ); + + aBrush->Release(); + } + + static const WCHAR msc_fontName[] = L"Verdana"; + static const FLOAT msc_fontSize = 20; + + IDWriteTextFormat* textFormat = NULL; + + // Create a DirectWrite text format object. + pDWriteFactory->CreateTextFormat( + msc_fontName, + NULL, + DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + msc_fontSize, + L"", //locale + &textFormat + ); + + D2D1_SIZE_F renderTargetSize = pRT->GetSize(); + + Array<TextPlot>::Iterator textIt; + for( textIt = currentFrame->textLines.Begin(); textIt != currentFrame->textLines.End(); ++textIt ) + { + ID2D1SolidColorBrush* aBrush; + + pRT->CreateSolidColorBrush( D2D1::ColorF( textIt->r, textIt->g, textIt->b), &aBrush ); + + WCHAR* tmpString = (WCHAR*)calloc( textIt->text.GetLength(), sizeof( WCHAR ) ); + for( unsigned i = 0; i < textIt->text.GetLength(); ++i ) + { + tmpString[i] = (WCHAR)textIt->text.GetCharAt( i ); + } + + pRT->DrawTextW( tmpString, (UINT32)textIt->text.GetLength(), textFormat, + D2D1::RectF(textIt->x, textIt->y, renderTargetSize.width, renderTargetSize.height), aBrush ); + + free( tmpString ); + + aBrush->Release(); + } + + if( textFormat ) + textFormat->Release(); + + pRT->EndDraw(); + + pRT->Flush(); +} + +Ptr<Frame> ImageWindow::lastUnreadyFrame() +{ + static int framenumber = 0; + + if( frames.GetSize() && !frames.PeekBack( 0 )->ready ) + return frames.PeekBack( 0 ); + + // Create a new frame if an unready one doesn't already exist + Ptr<Frame> tmpFrame = *new Frame( framenumber ); + frames.PushBack( tmpFrame ); + + ++framenumber; + + return tmpFrame; +} + +void ImageWindow::UpdateImageBW( const uint8_t* imageData, uint32_t width, uint32_t height ) +{ + if( pRT && greyBitmap ) + { + Mutex::Locker locker( frontBufferMutex ); + + Ptr<Frame> frame = lastUnreadyFrame(); + frame->imageData = malloc( width * height ); + frame->width = width; + frame->height = height; + memcpy( frame->imageData, imageData, width * height ); + } +} + +void ImageWindow::UpdateImageRGBA( const uint8_t* imageData, uint32_t width, uint32_t height, uint32_t pitch ) +{ + if( pRT && colorBitmap ) + { + Mutex::Locker locker( frontBufferMutex ); + + Ptr<Frame> frame = lastUnreadyFrame(); + frame->colorImageData = malloc( pitch * height ); + frame->width = width; + frame->height = height; + frame->colorPitch = pitch; + memcpy( frame->colorImageData, imageData, pitch * height ); + } +} + +void ImageWindow::addCircle( float x, float y, float radius, float r, float g, float b, bool fill ) +{ + if( pRT ) + { + CirclePlot cp; + + cp.x = x; + cp.y = y; + cp.radius = radius; + cp.r = r; + cp.g = g; + cp.b = b; + cp.fill = fill; + + Mutex::Locker locker( frontBufferMutex ); + + Ptr<Frame> frame = lastUnreadyFrame(); + frame->plots.PushBack( cp ); + } + +} + +void ImageWindow::addText( float x, float y, float r, float g, float b, OVR::String text ) +{ + if( pRT ) + { + TextPlot tp; + + tp.x = x; + tp.y = y; + tp.r = r; + tp.g = g; + tp.b = b; + tp.text = text; + + Mutex::Locker locker( frontBufferMutex ); + Ptr<Frame> frame = lastUnreadyFrame(); + frame->textLines.PushBack( tp ); + } +} + +}} + +#endif //defined(OVR_OS_WIN32)
\ No newline at end of file diff --git a/LibOVR/Src/Util/Util_ImageWindow.h b/LibOVR/Src/Util/Util_ImageWindow.h new file mode 100644 index 0000000..4b88959 --- /dev/null +++ b/LibOVR/Src/Util/Util_ImageWindow.h @@ -0,0 +1,200 @@ +/************************************************************************************ + +Filename : Util_ImageWindow.h +Content : An output object for windows that can display raw images for testing +Created : March 13, 2014 +Authors : Dean Beeler + +Copyright : Copyright 2014 Oculus, 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. + +*************************************************************************************/ + +#ifndef UTIL_IMAGEWINDOW_H +#define UTIL_IMAGEWINDOW_H + +#if defined(OVR_OS_WIN32) +#define WIN32_LEAN_AND_MEAN 1 +#include <windows.h> +#include <d2d1.h> +#include <dwrite.h> +#endif + +#include "../../Include/OVR.h" +#include "../Kernel/OVR_Hash.h" +#include "../Kernel/OVR_Array.h" +#include "../Kernel/OVR_Threads.h" +#include "../Kernel/OVR_Deque.h" + +#include <stdint.h> + +namespace OVR { namespace Util { + + typedef struct + { + float x; + float y; + float radius; + float r; + float g; + float b; + bool fill; + } CirclePlot; + + typedef struct + { + float x; + float y; + float r; + float g; + float b; + OVR::String text; + } TextPlot; + +class Frame : virtual public RefCountBaseV<Frame> + { +public: + + Frame( int frame ) : + frameNumber( frame ), + imageData( NULL ), + colorImageData( NULL ), + plots(), + textLines(), + width( 0 ), + height( 0 ), + colorPitch( 0 ), + ready( false ) + { + + } + + ~Frame() + { + if( imageData ) + free( imageData ); + if( colorImageData ) + free( colorImageData ); + + plots.ClearAndRelease(); + textLines.ClearAndRelease(); + } + + int frameNumber; + + Array<CirclePlot> plots; + Array<TextPlot> textLines; + void* imageData; + void* colorImageData; + int width; + int height; + int colorPitch; + bool ready; +}; + +#if defined(OVR_OS_WIN32) +class ImageWindow +{ + HWND hWindow; + ID2D1RenderTarget* pRT; + D2D1_SIZE_U resolution; + + Mutex* frontBufferMutex; + + InPlaceMutableDeque< Ptr<Frame> > frames; + + ID2D1Bitmap* greyBitmap; + ID2D1Bitmap* colorBitmap; + +public: + // constructors + ImageWindow(); + ImageWindow( uint32_t width, uint32_t height ); + virtual ~ImageWindow(); + + void GetResolution( size_t& width, size_t& height ) { width = resolution.width; height = resolution.height; } + + void OnPaint(); // Called by Windows when it receives a WM_PAINT message + + void UpdateImage( const uint8_t* imageData, uint32_t width, uint32_t height ) { UpdateImageBW( imageData, width, height ); } + void UpdateImageBW( const uint8_t* imageData, uint32_t width, uint32_t height ); + void UpdateImageRGBA( const uint8_t* imageData, uint32_t width, uint32_t height, uint32_t pitch ); + void Complete(); // Called by drawing thread to submit a frame + + void Process(); // Called by rendering thread to do window processing + + void AssociateSurface( void* surface ); + + void addCircle( float x , float y, float radius, float r, float g, float b, bool fill ); + void addText( float x, float y, float r, float g, float b, OVR::String text ); + + static ImageWindow* GlobalWindow( int window ) { return globalWindow[window]; } + static int WindowCount() { return windowCount; } + +private: + + Ptr<Frame> lastUnreadyFrame(); + + static const int MaxWindows = 4; + static ImageWindow* globalWindow[MaxWindows]; + static int windowCount; + static ID2D1Factory* pD2DFactory; + static IDWriteFactory* pDWriteFactory; +}; + +#else + +class ImageWindow +{ +public: + // constructors + ImageWindow() {} + ImageWindow( uint32_t width, uint32_t height ) { OVR_UNUSED( width ); OVR_UNUSED( height ); } + virtual ~ImageWindow() { } + + void GetResolution( size_t& width, size_t& height ) { width = 0; height = 0; } + + void OnPaint() { } + + void UpdateImage( const uint8_t* imageData, uint32_t width, uint32_t height ) { UpdateImageBW( imageData, width, height ); } + void UpdateImageBW( const uint8_t* imageData, uint32_t width, uint32_t height ) { } + void UpdateImageRGBA( const uint8_t* imageData, uint32_t width, uint32_t height, uint32_t pitch ) { } + void Complete() { } + + void Process() { } + + void AssociateSurface( void* surface ) { } + + void addCircle( float x , float y, float radius, float r, float g, float b, bool fill ) { } + void addText( float x, float y, float r, float g, float b, OVR::String text ) { } + + static ImageWindow* GlobalWindow( int window ) { return globalWindow[window]; } + static int WindowCount() { return windowCount; } + +private: + + static const int MaxWindows = 4; + static ImageWindow* globalWindow[4]; + static int windowCount; +}; + +#endif + +}} // namespace OVR::Util + + +#endif
\ No newline at end of file diff --git a/LibOVR/Src/Util/Util_Interface.cpp b/LibOVR/Src/Util/Util_Interface.cpp new file mode 100644 index 0000000..d96423c --- /dev/null +++ b/LibOVR/Src/Util/Util_Interface.cpp @@ -0,0 +1,34 @@ +/************************************************************************************ + +Filename : Util_Interface.cpp +Content : Simple interface, utilised by internal demos, + with access to wider SDK as needed. + Located in the body of the SDK to ensure updated + when new SDK features are added. +Created : February 20, 2014 +Authors : Tom Heath + +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 "Util_Interface.h" + + + +//Files left in to ease its possible return......
\ No newline at end of file diff --git a/LibOVR/Src/Util/Util_Interface.h b/LibOVR/Src/Util/Util_Interface.h new file mode 100644 index 0000000..1bbf638 --- /dev/null +++ b/LibOVR/Src/Util/Util_Interface.h @@ -0,0 +1,37 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : Util_Interface.h +Content : Simple interface, utilised by internal demos, + with access to wider SDK as needed. + Located in the body of the SDK to ensure updated + when new SDK features are added. +Created : February 20, 2014 +Authors : Tom Heath + +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. + +*************************************************************************************/ + +#ifndef OVR_Util_Interface_h +#define OVR_Util_Interface_h +#include "../../Src/OVR_CAPI.h" + +//Files left in to ease its possible return...... + +#endif diff --git a/LibOVR/Src/Util/Util_LatencyTest.cpp b/LibOVR/Src/Util/Util_LatencyTest.cpp new file mode 100644 index 0000000..3017c72 --- /dev/null +++ b/LibOVR/Src/Util/Util_LatencyTest.cpp @@ -0,0 +1,570 @@ +/************************************************************************************ + +Filename : Util_LatencyTest.cpp +Content : Wraps the lower level LatencyTester interface and adds functionality. +Created : February 14, 2013 +Authors : Lee Cooper + +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 "Util_LatencyTest.h" + +#include "../Kernel/OVR_Log.h" +#include "../Kernel/OVR_Timer.h" + +namespace OVR { namespace Util { + +static const UInt32 TIME_TO_WAIT_FOR_SETTLE_PRE_CALIBRATION = 16*10; +static const UInt32 TIME_TO_WAIT_FOR_SETTLE_POST_CALIBRATION = 16*10; +static const UInt32 TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT = 16*5; +static const UInt32 TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS = 16*5; +static const UInt32 DEFAULT_NUMBER_OF_SAMPLES = 10; // For both color 1->2 and color 2->1 transitions. +static const UInt32 INITIAL_SAMPLES_TO_IGNORE = 4; +static const UInt32 TIMEOUT_WAITING_FOR_TEST_STARTED = 1000; +static const UInt32 TIMEOUT_WAITING_FOR_COLOR_DETECTED = 4000; +static const Color CALIBRATE_BLACK(0, 0, 0); +static const Color CALIBRATE_WHITE(255, 255, 255); +static const Color COLOR1(0, 0, 0); +static const Color COLOR2(255, 255, 255); +static const Color SENSOR_DETECT_THRESHOLD(128, 255, 255); +static const float BIG_FLOAT = 1000000.0f; +static const float SMALL_FLOAT = -1000000.0f; + +//------------------------------------------------------------------------------------- +// ***** LatencyTest + +LatencyTest::LatencyTest(LatencyTestDevice* device) + : Handler(getThis()) +{ + if (device != NULL) + { + SetDevice(device); + } + + reset(); + + srand(Timer::GetTicksMs()); +} + +LatencyTest::~LatencyTest() +{ + clearMeasurementResults(); +} + +bool LatencyTest::SetDevice(LatencyTestDevice* device) +{ + + if (device != Device) + { + Handler.RemoveHandlerFromDevices(); + + Device = device; + + if (Device != NULL) + { + Device->AddMessageHandler(&Handler); + + // Set trigger threshold. + LatencyTestConfiguration configuration(SENSOR_DETECT_THRESHOLD, false); // No samples streaming. + Device->SetConfiguration(configuration, true); + + // Set display to initial (3 dashes). + LatencyTestDisplay ltd(2, 0x40400040); + Device->SetDisplay(ltd); + } + } + + return true; +} + +UInt32 LatencyTest::getRandomComponent(UInt32 range) +{ + UInt32 val = rand() % range; + return val; +} + +void LatencyTest::BeginTest() +{ + if (State == State_WaitingForButton) + { + // Set color to black and wait a while. + RenderColor = CALIBRATE_BLACK; + + State = State_WaitingForSettlePreCalibrationColorBlack; + OVR_DEBUG_LOG(("State_WaitingForButton -> State_WaitingForSettlePreCalibrationColorBlack.")); + + setTimer(TIME_TO_WAIT_FOR_SETTLE_PRE_CALIBRATION); + } +} + +void LatencyTest::handleMessage(const Message& msg, LatencyTestMessageType latencyTestMessage) +{ + // For debugging. +/* if (msg.Type == Message_LatencyTestSamples) + { + MessageLatencyTestSamples* pSamples = (MessageLatencyTestSamples*) &msg; + + if (pSamples->Samples.GetSize() > 0) + { + // Just show the first one for now. + Color c = pSamples->Samples[0]; + OVR_DEBUG_LOG(("%d %d %d", c.R, c.G, c.B)); + } + return; + } +*/ + + if (latencyTestMessage == LatencyTest_Timer) + { + if (!Device) + { + reset(); + return; + } + + if (State == State_WaitingForSettlePreCalibrationColorBlack) + { + // Send calibrate message to device and wait a while. + Device->SetCalibrate(CALIBRATE_BLACK); + + State = State_WaitingForSettlePostCalibrationColorBlack; + OVR_DEBUG_LOG(("State_WaitingForSettlePreCalibrationColorBlack -> State_WaitingForSettlePostCalibrationColorBlack.")); + + setTimer(TIME_TO_WAIT_FOR_SETTLE_POST_CALIBRATION); + } + else if (State == State_WaitingForSettlePostCalibrationColorBlack) + { + // Change color to white and wait a while. + RenderColor = CALIBRATE_WHITE; + + State = State_WaitingForSettlePreCalibrationColorWhite; + OVR_DEBUG_LOG(("State_WaitingForSettlePostCalibrationColorBlack -> State_WaitingForSettlePreCalibrationColorWhite.")); + + setTimer(TIME_TO_WAIT_FOR_SETTLE_PRE_CALIBRATION); + } + else if (State == State_WaitingForSettlePreCalibrationColorWhite) + { + // Send calibrate message to device and wait a while. + Device->SetCalibrate(CALIBRATE_WHITE); + + State = State_WaitingForSettlePostCalibrationColorWhite; + OVR_DEBUG_LOG(("State_WaitingForSettlePreCalibrationColorWhite -> State_WaitingForSettlePostCalibrationColorWhite.")); + + setTimer(TIME_TO_WAIT_FOR_SETTLE_POST_CALIBRATION); + } + else if (State == State_WaitingForSettlePostCalibrationColorWhite) + { + // Calibration is done. Switch to color 1 and wait for it to settle. + RenderColor = COLOR1; + + State = State_WaitingForSettlePostMeasurement; + OVR_DEBUG_LOG(("State_WaitingForSettlePostCalibrationColorWhite -> State_WaitingForSettlePostMeasurement.")); + + UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS); + setTimer(waitTime); + } + else if (State == State_WaitingForSettlePostMeasurement) + { + // Prepare for next measurement. + + // Create a new result object. + MeasurementResult* pResult = new MeasurementResult(); + Results.PushBack(pResult); + + State = State_WaitingToTakeMeasurement; + OVR_DEBUG_LOG(("State_WaitingForSettlePostMeasurement -> State_WaitingToTakeMeasurement.")); + } + else if (State == State_WaitingForTestStarted) + { + // We timed out waiting for 'TestStarted'. Abandon this measurement and setup for the next. + getActiveResult()->TimedOutWaitingForTestStarted = true; + + State = State_WaitingForSettlePostMeasurement; + OVR_DEBUG_LOG(("** Timed out waiting for 'TestStarted'.")); + OVR_DEBUG_LOG(("State_WaitingForTestStarted -> State_WaitingForSettlePostMeasurement.")); + + UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS); + setTimer(waitTime); + } + else if (State == State_WaitingForColorDetected) + { + // We timed out waiting for 'ColorDetected'. Abandon this measurement and setup for the next. + getActiveResult()->TimedOutWaitingForColorDetected = true; + + State = State_WaitingForSettlePostMeasurement; + OVR_DEBUG_LOG(("** Timed out waiting for 'ColorDetected'.")); + OVR_DEBUG_LOG(("State_WaitingForColorDetected -> State_WaitingForSettlePostMeasurement.")); + + UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS); + setTimer(waitTime); + } + } + else if (latencyTestMessage == LatencyTest_ProcessInputs) + { + if (State == State_WaitingToTakeMeasurement) + { + if (!Device) + { + reset(); + return; + } + + // Send 'StartTest' feature report with opposite target color. + if (RenderColor == COLOR1) + { + RenderColor = COLOR2; + } + else + { + RenderColor = COLOR1; + } + + getActiveResult()->TargetColor = RenderColor; + + // Record time so we can determine usb roundtrip time. + getActiveResult()->StartTestSeconds = Timer::GetSeconds(); + + Device->SetStartTest(RenderColor); + + State = State_WaitingForTestStarted; + OVR_DEBUG_LOG(("State_WaitingToTakeMeasurement -> State_WaitingForTestStarted.")); + + setTimer(TIMEOUT_WAITING_FOR_TEST_STARTED); + + LatencyTestDisplay ltd(2, 0x40090040); + Device->SetDisplay(ltd); + } + } + else if (msg.Type == Message_LatencyTestButton) + { + BeginTest(); + } + else if (msg.Type == Message_LatencyTestStarted) + { + if (State == State_WaitingForTestStarted) + { + clearTimer(); + + // Record time so we can determine usb roundtrip time. + getActiveResult()->TestStartedSeconds = Timer::GetSeconds(); + + State = State_WaitingForColorDetected; + OVR_DEBUG_LOG(("State_WaitingForTestStarted -> State_WaitingForColorDetected.")); + + setTimer(TIMEOUT_WAITING_FOR_COLOR_DETECTED); + } + } + else if (msg.Type == Message_LatencyTestColorDetected) + { + if (State == State_WaitingForColorDetected) + { + // Record time to detect color. + MessageLatencyTestColorDetected* pDetected = (MessageLatencyTestColorDetected*) &msg; + UInt16 elapsedTime = pDetected->Elapsed; + OVR_DEBUG_LOG(("Time to 'ColorDetected' = %d", elapsedTime)); + + getActiveResult()->DeviceMeasuredElapsedMilliS = elapsedTime; + + if (areResultsComplete()) + { + // We're done. + processResults(); + reset(); + } + else + { + // Run another measurement. + State = State_WaitingForSettlePostMeasurement; + OVR_DEBUG_LOG(("State_WaitingForColorDetected -> State_WaitingForSettlePostMeasurement.")); + + UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS); + setTimer(waitTime); + + LatencyTestDisplay ltd(2, 0x40400040); + Device->SetDisplay(ltd); + } + } + } + else if (msg.Type == Message_DeviceRemoved) + { + reset(); + } +} + +LatencyTest::MeasurementResult* LatencyTest::getActiveResult() +{ + OVR_ASSERT(!Results.IsEmpty()); + return Results.GetLast(); +} + +void LatencyTest::setTimer(UInt32 timeMilliS) +{ + ActiveTimerMilliS = timeMilliS; +} + +void LatencyTest::clearTimer() +{ + ActiveTimerMilliS = 0; +} + +void LatencyTest::reset() +{ + clearMeasurementResults(); + State = State_WaitingForButton; + + HaveOldTime = false; + ActiveTimerMilliS = 0; +} + +void LatencyTest::clearMeasurementResults() +{ + while(!Results.IsEmpty()) + { + MeasurementResult* pElem = Results.GetFirst(); + pElem->RemoveNode(); + delete pElem; + } +} + +LatencyTest::LatencyTestHandler::~LatencyTestHandler() +{ + RemoveHandlerFromDevices(); +} + +void LatencyTest::LatencyTestHandler::OnMessage(const Message& msg) +{ + pLatencyTestUtil->handleMessage(msg); +} + +void LatencyTest::ProcessInputs() +{ + updateForTimeouts(); + handleMessage(Message(), LatencyTest_ProcessInputs); +} + +bool LatencyTest::DisplayScreenColor(Color& colorToDisplay) +{ + updateForTimeouts(); + + if (State == State_WaitingForButton) + { + return false; + } + + colorToDisplay = RenderColor; + return true; +} + +const char* LatencyTest::GetResultsString() +{ + if (!ResultsString.IsEmpty() && ReturnedResultString != ResultsString.ToCStr()) + { + ReturnedResultString = ResultsString; + return ReturnedResultString.ToCStr(); + } + + return NULL; +} + +bool LatencyTest::areResultsComplete() +{ + UInt32 initialMeasurements = 0; + + UInt32 measurements1to2 = 0; + UInt32 measurements2to1 = 0; + + MeasurementResult* pCurr = Results.GetFirst(); + while(true) + { + // Process. + if (!pCurr->TimedOutWaitingForTestStarted && + !pCurr->TimedOutWaitingForColorDetected) + { + initialMeasurements++; + + if (initialMeasurements > INITIAL_SAMPLES_TO_IGNORE) + { + if (pCurr->TargetColor == COLOR2) + { + measurements1to2++; + } + else + { + measurements2to1++; + } + } + } + + if (Results.IsLast(pCurr)) + { + break; + } + pCurr = Results.GetNext(pCurr); + } + + if (measurements1to2 >= DEFAULT_NUMBER_OF_SAMPLES && + measurements2to1 >= DEFAULT_NUMBER_OF_SAMPLES) + { + return true; + } + + return false; +} + +void LatencyTest::processResults() +{ + + UInt32 minTime1To2 = UINT_MAX; + UInt32 maxTime1To2 = 0; + float averageTime1To2 = 0.0f; + UInt32 minTime2To1 = UINT_MAX; + UInt32 maxTime2To1 = 0; + float averageTime2To1 = 0.0f; + + float minUSBTripMilliS = BIG_FLOAT; + float maxUSBTripMilliS = SMALL_FLOAT; + float averageUSBTripMilliS = 0.0f; + UInt32 countUSBTripTime = 0; + + UInt32 measurementsCount = 0; + UInt32 measurements1to2 = 0; + UInt32 measurements2to1 = 0; + + MeasurementResult* pCurr = Results.GetFirst(); + UInt32 count = 0; + while(true) + { + count++; + + if (!pCurr->TimedOutWaitingForTestStarted && + !pCurr->TimedOutWaitingForColorDetected) + { + measurementsCount++; + + if (measurementsCount > INITIAL_SAMPLES_TO_IGNORE) + { + if (pCurr->TargetColor == COLOR2) + { + measurements1to2++; + + if (measurements1to2 <= DEFAULT_NUMBER_OF_SAMPLES) + { + UInt32 elapsed = pCurr->DeviceMeasuredElapsedMilliS; + + minTime1To2 = Alg::Min(elapsed, minTime1To2); + maxTime1To2 = Alg::Max(elapsed, maxTime1To2); + + averageTime1To2 += (float) elapsed; + } + } + else + { + measurements2to1++; + + if (measurements2to1 <= DEFAULT_NUMBER_OF_SAMPLES) + { + UInt32 elapsed = pCurr->DeviceMeasuredElapsedMilliS; + + minTime2To1 = Alg::Min(elapsed, minTime2To1); + maxTime2To1 = Alg::Max(elapsed, maxTime2To1); + + averageTime2To1 += (float) elapsed; + } + } + + float usbRountripElapsedMilliS = Timer::MsPerSecond * (float) (pCurr->TestStartedSeconds - pCurr->StartTestSeconds); + minUSBTripMilliS = Alg::Min(usbRountripElapsedMilliS, minUSBTripMilliS); + maxUSBTripMilliS = Alg::Max(usbRountripElapsedMilliS, maxUSBTripMilliS); + averageUSBTripMilliS += usbRountripElapsedMilliS; + countUSBTripTime++; + } + } + + if (measurements1to2 >= DEFAULT_NUMBER_OF_SAMPLES && + measurements2to1 >= DEFAULT_NUMBER_OF_SAMPLES) + { + break; + } + + if (Results.IsLast(pCurr)) + { + break; + } + pCurr = Results.GetNext(pCurr); + } + + averageTime1To2 /= (float) DEFAULT_NUMBER_OF_SAMPLES; + averageTime2To1 /= (float) DEFAULT_NUMBER_OF_SAMPLES; + + averageUSBTripMilliS /= countUSBTripTime; + + float finalResult = 0.5f * (averageTime1To2 + averageTime2To1); + finalResult += averageUSBTripMilliS; + + ResultsString.Clear(); + ResultsString.AppendFormat("RESULT=%.1f (add half Tracker period) [b->w %d|%.1f|%d] [w->b %d|%.1f|%d] [usb rndtrp %.1f|%.1f|%.1f] [cnt %d] [tmouts %d]", + finalResult, + minTime1To2, averageTime1To2, maxTime1To2, + minTime2To1, averageTime2To1, maxTime2To1, + minUSBTripMilliS, averageUSBTripMilliS, maxUSBTripMilliS, + DEFAULT_NUMBER_OF_SAMPLES*2, count - measurementsCount); + + // Display result on latency tester display. + LatencyTestDisplay ltd(1, (int)finalResult); + Device->SetDisplay(ltd); +} + +void LatencyTest::updateForTimeouts() +{ + if (!HaveOldTime) + { + HaveOldTime = true; + OldTime = Timer::GetTicksMs(); + return; + } + + UInt32 newTime = Timer::GetTicksMs(); + UInt32 elapsedMilliS = newTime - OldTime; + if (newTime < OldTime) + { + elapsedMilliS = OldTime - newTime; + elapsedMilliS = UINT_MAX - elapsedMilliS; + } + OldTime = newTime; + + elapsedMilliS = Alg::Min(elapsedMilliS, (UInt32) 100); // Clamp at 100mS in case we're not being called very often. + + + if (ActiveTimerMilliS == 0) + { + return; + } + + if (elapsedMilliS >= ActiveTimerMilliS) + { + ActiveTimerMilliS = 0; + handleMessage(Message(), LatencyTest_Timer); + return; + } + + ActiveTimerMilliS -= elapsedMilliS; +} + +}} // namespace OVR::Util diff --git a/LibOVR/Src/Util/Util_LatencyTest.h b/LibOVR/Src/Util/Util_LatencyTest.h new file mode 100644 index 0000000..0844603 --- /dev/null +++ b/LibOVR/Src/Util/Util_LatencyTest.h @@ -0,0 +1,173 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : Util_LatencyTest.h +Content : Wraps the lower level LatencyTesterDevice and adds functionality. +Created : February 14, 2013 +Authors : Lee Cooper + +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. + +*************************************************************************************/ + +#ifndef OVR_Util_LatencyTest_h +#define OVR_Util_LatencyTest_h + +#include "../OVR_Device.h" + +#include "../Kernel/OVR_String.h" +#include "../Kernel/OVR_List.h" + +namespace OVR { namespace Util { + + +//------------------------------------------------------------------------------------- +// ***** LatencyTest +// +// LatencyTest utility class wraps the low level LatencyTestDevice and manages the scheduling +// of a latency test. A single test is composed of a series of individual latency measurements +// which are used to derive min, max, and an average latency value. +// +// Developers are required to call the following methods: +// SetDevice - Sets the LatencyTestDevice to be used for the tests. +// ProcessInputs - This should be called at the same place in the code where the game engine +// reads the headset orientation from LibOVR (typically done by calling +// 'GetOrientation' on the SensorFusion object). Calling this at the right time +// enables us to measure the same latency that occurs for headset orientation +// changes. +// DisplayScreenColor - The latency tester works by sensing the color of the pixels directly +// beneath it. The color of these pixels can be set by drawing a small +// quad at the end of the rendering stage. The quad should be small +// such that it doesn't significantly impact the rendering of the scene, +// but large enough to be 'seen' by the sensor. See the SDK +// documentation for more information. +// GetResultsString - Call this to get a string containing the most recent results. +// If the string has already been gotten then NULL will be returned. +// The string pointer will remain valid until the next time this +// method is called. +// + +class LatencyTest : public NewOverrideBase +{ +public: + LatencyTest(LatencyTestDevice* device = NULL); + ~LatencyTest(); + + // Set the Latency Tester device that we'll use to send commands to and receive + // notification messages from. + bool SetDevice(LatencyTestDevice* device); + + // Returns true if this LatencyTestUtil has a Latency Tester device. + bool HasDevice() const + { return Handler.IsHandlerInstalled(); } + + void ProcessInputs(); + bool DisplayScreenColor(Color& colorToDisplay); + const char* GetResultsString(); + + bool IsMeasuringNow() const { return (State != State_WaitingForButton); } + + // Begin test. Equivalent to pressing the button on the latency tester. + void BeginTest(); + +private: + LatencyTest* getThis() { return this; } + + enum LatencyTestMessageType + { + LatencyTest_None, + LatencyTest_Timer, + LatencyTest_ProcessInputs, + }; + + UInt32 getRandomComponent(UInt32 range); + void handleMessage(const Message& msg, LatencyTestMessageType latencyTestMessage = LatencyTest_None); + void reset(); + void setTimer(UInt32 timeMilliS); + void clearTimer(); + + class LatencyTestHandler : public MessageHandler + { + LatencyTest* pLatencyTestUtil; + public: + LatencyTestHandler(LatencyTest* latencyTester) : pLatencyTestUtil(latencyTester) { } + ~LatencyTestHandler(); + + virtual void OnMessage(const Message& msg); + }; + + bool areResultsComplete(); + void processResults(); + void updateForTimeouts(); + + Ptr<LatencyTestDevice> Device; + LatencyTestHandler Handler; + + enum TesterState + { + State_WaitingForButton, + State_WaitingForSettlePreCalibrationColorBlack, + State_WaitingForSettlePostCalibrationColorBlack, + State_WaitingForSettlePreCalibrationColorWhite, + State_WaitingForSettlePostCalibrationColorWhite, + State_WaitingToTakeMeasurement, + State_WaitingForTestStarted, + State_WaitingForColorDetected, + State_WaitingForSettlePostMeasurement + }; + TesterState State; + + bool HaveOldTime; + UInt32 OldTime; + UInt32 ActiveTimerMilliS; + + Color RenderColor; + + struct MeasurementResult : public ListNode<MeasurementResult>, public NewOverrideBase + { + MeasurementResult() + : DeviceMeasuredElapsedMilliS(0), + TimedOutWaitingForTestStarted(false), + TimedOutWaitingForColorDetected(false), + StartTestSeconds(0.0), + TestStartedSeconds(0.0) + {} + + Color TargetColor; + + UInt32 DeviceMeasuredElapsedMilliS; + + bool TimedOutWaitingForTestStarted; + bool TimedOutWaitingForColorDetected; + + double StartTestSeconds; + double TestStartedSeconds; + }; + + List<MeasurementResult> Results; + void clearMeasurementResults(); + + MeasurementResult* getActiveResult(); + + StringBuffer ResultsString; + String ReturnedResultString; +}; + +}} // namespace OVR::Util + +#endif // OVR_Util_LatencyTest_h diff --git a/LibOVR/Src/Util/Util_LatencyTest2.cpp b/LibOVR/Src/Util/Util_LatencyTest2.cpp new file mode 100644 index 0000000..6fc8b1f --- /dev/null +++ b/LibOVR/Src/Util/Util_LatencyTest2.cpp @@ -0,0 +1,191 @@ +/************************************************************************************ + +Filename : Util_LatencyTest2.cpp +Content : Wraps the lower level LatencyTester interface for DK2 and adds functionality. +Created : March 10, 2014 +Authors : Volga Aksoy + +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 "Util_LatencyTest2.h" + +#include "../OVR_CAPI.h" +#include "../Kernel/OVR_Log.h" +#include "../Kernel/OVR_Timer.h" + + +namespace OVR { namespace Util { + +//------------------------------------------------------------------------------------- +// ***** LatencyTest2 + +LatencyTest2::LatencyTest2(SensorDevice* device) + : Handler(getThis()) + , TestActive(false) + , StartTiming(-1) + , LatencyMeasuredInSeconds(-1) + , LastPixelReadMsg(NULL) + , RenderColorValue(0) + , NumMsgsBeforeSettle(0) + , NumTestsSuccessful(0) +{ + if (device != NULL) + { + SetSensorDevice(device); + } +} + +LatencyTest2::~LatencyTest2() +{ + HmdDevice = NULL; + LatencyTesterDev = NULL; + + Handler.RemoveHandlerFromDevices(); +} + +bool LatencyTest2::SetSensorDevice(SensorDevice* device) +{ + Lock::Locker devLocker(&TesterLock); + + // Enable/Disable pixel read from HMD + if (device != HmdDevice) + { + Handler.RemoveHandlerFromDevices(); + + HmdDevice = device; + + if (HmdDevice != NULL) + { + HmdDevice->AddMessageHandler(&Handler); + } + } + + return true; +} + +bool LatencyTest2::SetDisplayDevice(LatencyTestDevice* device) +{ + Lock::Locker devLocker(&TesterLock); + + if (device != LatencyTesterDev) + { + LatencyTesterDev = device; + if (LatencyTesterDev != NULL) + { + // Set display to initial (3 dashes). + LatencyTestDisplay ltd(2, 0x40400040); + LatencyTesterDev->SetDisplay(ltd); + } + } + + return true; +} + +void LatencyTest2::BeginTest(double startTime) +{ + Lock::Locker devLocker(&TesterLock); + + if (!TestActive) + { + TestActive = true; + NumMsgsBeforeSettle = 0; + + // Go to next pixel value + //RenderColorValue = (RenderColorValue == 0) ? 255 : 0; + RenderColorValue = (RenderColorValue + LT2_ColorIncrement) % 256; + RawStartTiming = LastPixelReadMsg.RawSensorTime; + + if (startTime > 0.0) + StartTiming = startTime; + else + StartTiming = ovr_GetTimeInSeconds(); + + } +} + +void LatencyTest2::handleMessage(const MessagePixelRead& msg) +{ + Lock::Locker devLocker(&TesterLock); + + // Hold onto the last message as we will use this when we start a new test + LastPixelReadMsg = msg; + + // If color readback index is valid, store it in the lock-less queue. + int readbackIndex = 0; + if (FrameTimeRecord::ColorToReadbackIndex(&readbackIndex, msg.PixelReadValue)) + { + RecentFrameSet.AddValue(readbackIndex, msg.FrameTimeSeconds); + LockessRecords.SetState(RecentFrameSet); + } + + NumMsgsBeforeSettle++; + + if (TestActive) + { + int pixelValueDiff = RenderColorValue - LastPixelReadMsg.PixelReadValue; + int rawTimeDiff = LastPixelReadMsg.RawFrameTime - RawStartTiming; + + if (pixelValueDiff < LT2_PixelTestThreshold && pixelValueDiff > -LT2_PixelTestThreshold) + { + TestActive = false; + + LatencyMeasuredInSeconds = LastPixelReadMsg.FrameTimeSeconds - StartTiming; + RawLatencyMeasured = rawTimeDiff; + //LatencyMeasuredInSeconds = RawLatencyMeasured / 1000000.0; + + if(LatencyTesterDev && (NumTestsSuccessful % 5) == 0) + { + int displayNum = (int)(RawLatencyMeasured / 100.0); + //int displayNum = NumMsgsBeforeSettle; + //int displayNum = (int)(LatencyMeasuredInSeconds * 1000.0); + LatencyTestDisplay ltd(1, displayNum); + LatencyTesterDev->SetDisplay(ltd); + } + + NumTestsSuccessful++; + } + else if (TestActive && (rawTimeDiff / 1000) > LT2_TimeoutWaitingForColorDetected) + { + TestActive = false; + LatencyMeasuredInSeconds = -1; + } + } +} + +LatencyTest2::PixelReadHandler::~PixelReadHandler() +{ + RemoveHandlerFromDevices(); +} + +void LatencyTest2::PixelReadHandler::OnMessage(const Message& msg) +{ + if(msg.Type == Message_PixelRead) + pLatencyTestUtil->handleMessage(static_cast<const MessagePixelRead&>(msg)); +} + +bool LatencyTest2::DisplayScreenColor(Color& colorToDisplay) +{ + Lock::Locker devLocker(&TesterLock); + colorToDisplay = Color(RenderColorValue, RenderColorValue, RenderColorValue, 255); + + return TestActive; +} + +}} // namespace OVR::Util diff --git a/LibOVR/Src/Util/Util_LatencyTest2.h b/LibOVR/Src/Util/Util_LatencyTest2.h new file mode 100644 index 0000000..61e8477 --- /dev/null +++ b/LibOVR/Src/Util/Util_LatencyTest2.h @@ -0,0 +1,238 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : Util_LatencyTest2.h +Content : Wraps the lower level LatencyTester interface for DK2 and adds functionality. +Created : March 10, 2014 +Authors : Volga Aksoy + +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. + +*************************************************************************************/ + +#ifndef OVR_Util_LatencyTest2_h +#define OVR_Util_LatencyTest2_h + +#include "../OVR_Device.h" + +#include "../Kernel/OVR_String.h" +#include "../Kernel/OVR_List.h" +#include "../Kernel/OVR_Lockless.h" + +namespace OVR { namespace Util { + + +enum { + LT2_ColorIncrement = 32, + LT2_PixelTestThreshold = LT2_ColorIncrement / 3, + LT2_IncrementCount = 256 / LT2_ColorIncrement, + LT2_TimeoutWaitingForColorDetected = 1000 // 1 second +}; + +//------------------------------------------------------------------------------------- + +// Describes frame scanout time used for latency testing. +struct FrameTimeRecord +{ + int ReadbackIndex; + double TimeSeconds; + + // Utility functions to convert color to readBack indices and back. + // The purpose of ReadbackIndex is to allow direct comparison by value. + + static bool ColorToReadbackIndex(int *readbackIndex, unsigned char color) + { + int compareColor = color - LT2_ColorIncrement/2; + int index = color / LT2_ColorIncrement; // Use color without subtraction due to rounding. + int delta = compareColor - index * LT2_ColorIncrement; + + if ((delta < LT2_PixelTestThreshold) && (delta > -LT2_PixelTestThreshold)) + { + *readbackIndex = index; + return true; + } + return false; + } + + static unsigned char ReadbackIndexToColor(int readbackIndex) + { + OVR_ASSERT(readbackIndex < LT2_IncrementCount); + return (unsigned char)(readbackIndex * LT2_ColorIncrement + LT2_ColorIncrement/2); + } +}; + +// FrameTimeRecordSet is a container holding multiple consecutive frame timing records +// returned from the lock-less state. Used by FrameTimeManager. + +struct FrameTimeRecordSet +{ + enum { + RecordCount = 4, + RecordMask = RecordCount - 1 + }; + FrameTimeRecord Records[RecordCount]; + int NextWriteIndex; + + FrameTimeRecordSet() + { + NextWriteIndex = 0; + memset(this, 0, sizeof(FrameTimeRecordSet)); + } + + void AddValue(int readValue, double timeSeconds) + { + Records[NextWriteIndex].ReadbackIndex = readValue; + Records[NextWriteIndex].TimeSeconds = timeSeconds; + NextWriteIndex ++; + if (NextWriteIndex == RecordCount) + NextWriteIndex = 0; + } + // Matching should be done starting from NextWrite index + // until wrap-around + + const FrameTimeRecord& operator [] (int i) const + { + return Records[(NextWriteIndex + i) & RecordMask]; + } + + const FrameTimeRecord& GetMostRecentFrame() + { + return Records[(NextWriteIndex - 1) & RecordMask]; + } + + // Advances I to absolute color index + bool FindReadbackIndex(int* i, int readbackIndex) const + { + for (; *i < RecordCount; (*i)++) + { + if ((*this)[*i].ReadbackIndex == readbackIndex) + return true; + } + return false; + } + + bool IsAllZeroes() const + { + for (int i = 0; i < RecordCount; i++) + if (Records[i].ReadbackIndex != 0) + return false; + return true; + } +}; + + +//------------------------------------------------------------------------------------- +// ***** LatencyTest2 +// +// LatencyTest2 utility class wraps the low level SensorDevice and manages the scheduling +// of a latency test. A single test is composed of a series of individual latency measurements +// which are used to derive min, max, and an average latency value. +// +// Developers are required to call the following methods: +// SetDevice - Sets the SensorDevice to be used for the tests. +// ProcessInputs - This should be called at the same place in the code where the game engine +// reads the headset orientation from LibOVR (typically done by calling +// 'GetOrientation' on the SensorFusion object). Calling this at the right time +// enables us to measure the same latency that occurs for headset orientation +// changes. +// DisplayScreenColor - The latency tester works by sensing the color of the pixels directly +// beneath it. The color of these pixels can be set by drawing a small +// quad at the end of the rendering stage. The quad should be small +// such that it doesn't significantly impact the rendering of the scene, +// but large enough to be 'seen' by the sensor. See the SDK +// documentation for more information. +// GetResultsString - Call this to get a string containing the most recent results. +// If the string has already been gotten then NULL will be returned. +// The string pointer will remain valid until the next time this +// method is called. +// + +class LatencyTest2 : public NewOverrideBase +{ +public: + LatencyTest2(SensorDevice* device = NULL); + ~LatencyTest2(); + + // Set the Latency Tester device that we'll use to send commands to and receive + // notification messages from. + bool SetSensorDevice(SensorDevice* device); + bool SetDisplayDevice(LatencyTestDevice* device); + + // Returns true if this LatencyTestUtil has a Latency Tester device. + bool HasDisplayDevice() const { return LatencyTesterDev.GetPtr() != NULL; } + bool HasDevice() const { return Handler.IsHandlerInstalled(); } + + bool DisplayScreenColor(Color& colorToDisplay); + //const char* GetResultsString(); + + // Begin test. Equivalent to pressing the button on the latency tester. + void BeginTest(double startTime = -1.0f); + bool IsMeasuringNow() const { return TestActive; } + double GetMeasuredLatency() const { return LatencyMeasuredInSeconds; } + +// + FrameTimeRecordSet GetLocklessState() { return LockessRecords.GetState(); } + +private: + LatencyTest2* getThis() { return this; } + + enum LatencyTestMessageType + { + LatencyTest_None, + LatencyTest_Timer, + LatencyTest_ProcessInputs, + }; + + void handleMessage(const MessagePixelRead& msg); + + class PixelReadHandler : public MessageHandler + { + LatencyTest2* pLatencyTestUtil; + public: + PixelReadHandler(LatencyTest2* latencyTester) : pLatencyTestUtil(latencyTester) { } + ~PixelReadHandler(); + + virtual void OnMessage(const Message& msg); + }; + PixelReadHandler Handler; + + Ptr<SensorDevice> HmdDevice; + Ptr<LatencyTestDevice> LatencyTesterDev; + + Lock TesterLock; + bool TestActive; + unsigned char RenderColorValue; + MessagePixelRead LastPixelReadMsg; + double StartTiming; + unsigned int RawStartTiming; + UInt32 RawLatencyMeasured; + double LatencyMeasuredInSeconds; + int NumMsgsBeforeSettle; + unsigned int NumTestsSuccessful; + + // MA: + // Frames are added here, then copied into lockess state + FrameTimeRecordSet RecentFrameSet; + LocklessUpdater<FrameTimeRecordSet> LockessRecords; +}; + + + +}} // namespace OVR::Util + +#endif // OVR_Util_LatencyTest2_h diff --git a/LibOVR/Src/Util/Util_Render_Stereo.cpp b/LibOVR/Src/Util/Util_Render_Stereo.cpp new file mode 100644 index 0000000..e84381e --- /dev/null +++ b/LibOVR/Src/Util/Util_Render_Stereo.cpp @@ -0,0 +1,1472 @@ +/************************************************************************************ + +Filename : Util_Render_Stereo.cpp +Content : Stereo rendering configuration implementation +Created : October 22, 2012 +Authors : Michael Antonov, Andrew Reisse, Tom Forsyth + +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 "Util_Render_Stereo.h" +#include "../OVR_SensorFusion.h" + +namespace OVR { namespace Util { namespace Render { + + +//----------------------------------------------------------------------------------- +// **** Useful debug functions. + +char const* GetDebugNameEyeCupType ( EyeCupType eyeCupType ) +{ + switch ( eyeCupType ) + { + case EyeCup_DK1A: return "DK1 A"; break; + case EyeCup_DK1B: return "DK1 B"; break; + case EyeCup_DK1C: return "DK1 C"; break; + case EyeCup_DKHD2A: return "DKHD2 A"; break; + case EyeCup_OrangeA: return "Orange A"; break; + case EyeCup_RedA: return "Red A"; break; + case EyeCup_PinkA: return "Pink A"; break; + case EyeCup_BlueA: return "Blue A"; break; + case EyeCup_Delilah1A: return "Delilah 1 A"; break; + case EyeCup_Delilah2A: return "Delilah 2 A"; break; + case EyeCup_JamesA: return "James A"; break; + case EyeCup_SunMandalaA: return "Sun Mandala A"; break; + case EyeCup_DK2A: return "DK2 A"; break; + case EyeCup_LAST: return "LAST"; break; + default: OVR_ASSERT ( false ); return "Error"; break; + } +} + +char const* GetDebugNameHmdType ( HmdTypeEnum hmdType ) +{ + switch ( hmdType ) + { + case HmdType_None: return "None"; break; + case HmdType_DK1: return "DK1"; break; + case HmdType_DKProto: return "DK1 prototype"; break; + case HmdType_DKHDProto: return "DK HD prototype 1"; break; + case HmdType_DKHDProto566Mi: return "DK HD prototype 566 Mi"; break; + case HmdType_DKHD2Proto: return "DK HD prototype 585"; break; + case HmdType_CrystalCoveProto: return "Crystal Cove"; break; + case HmdType_DK2: return "DK2"; break; + case HmdType_Unknown: return "Unknown"; break; + case HmdType_LAST: return "LAST"; break; + default: OVR_ASSERT ( false ); return "Error"; break; + } +} + + +//----------------------------------------------------------------------------------- +// **** Internal pipeline functions. + +struct DistortionAndFov +{ + DistortionRenderDesc Distortion; + FovPort Fov; +}; + +static DistortionAndFov CalculateDistortionAndFovInternal ( StereoEye eyeType, HmdRenderInfo const &hmd, + LensConfig const *pLensOverride = NULL, + FovPort const *pTanHalfFovOverride = NULL, + float extraEyeRotationInRadians = OVR_DEFAULT_EXTRA_EYE_ROTATION ) +{ + // pLensOverride can be NULL, which means no override. + + DistortionRenderDesc localDistortion = CalculateDistortionRenderDesc ( eyeType, hmd, pLensOverride ); + FovPort fov = CalculateFovFromHmdInfo ( eyeType, localDistortion, hmd, extraEyeRotationInRadians ); + // Here the app or the user would optionally clamp this visible fov to a smaller number if + // they want more perf or resolution and are willing to give up FOV. + // They may also choose to clamp UDLR differently e.g. to get cinemascope-style views. + if ( pTanHalfFovOverride != NULL ) + { + fov = *pTanHalfFovOverride; + } + + // Here we could call ClampToPhysicalScreenFov(), but we do want people + // to be able to play with larger-than-screen views. + // The calling app can always do the clamping itself. + DistortionAndFov result; + result.Distortion = localDistortion; + result.Fov = fov; + + return result; +} + + +static Recti CalculateViewportInternal ( StereoEye eyeType, + Sizei const actualRendertargetSurfaceSize, + Sizei const requestedRenderedPixelSize, + bool bRendertargetSharedByBothEyes, + bool bMonoRenderingMode = false ) +{ + Recti renderedViewport; + if ( bMonoRenderingMode || !bRendertargetSharedByBothEyes || (eyeType == StereoEye_Center) ) + { + // One eye per RT. + renderedViewport.x = 0; + renderedViewport.y = 0; + renderedViewport.w = Alg::Min ( actualRendertargetSurfaceSize.w, requestedRenderedPixelSize.w ); + renderedViewport.h = Alg::Min ( actualRendertargetSurfaceSize.h, requestedRenderedPixelSize.h ); + } + else + { + // Both eyes share the RT. + renderedViewport.x = 0; + renderedViewport.y = 0; + renderedViewport.w = Alg::Min ( actualRendertargetSurfaceSize.w/2, requestedRenderedPixelSize.w ); + renderedViewport.h = Alg::Min ( actualRendertargetSurfaceSize.h, requestedRenderedPixelSize.h ); + if ( eyeType == StereoEye_Right ) + { + renderedViewport.x = (actualRendertargetSurfaceSize.w+1)/2; // Round up, not down. + } + } + return renderedViewport; +} + +static Recti CalculateViewportDensityInternal ( StereoEye eyeType, + DistortionRenderDesc const &distortion, + FovPort const &fov, + Sizei const &actualRendertargetSurfaceSize, + bool bRendertargetSharedByBothEyes, + float desiredPixelDensity = 1.0f, + bool bMonoRenderingMode = false ) +{ + OVR_ASSERT ( actualRendertargetSurfaceSize.w > 0 ); + OVR_ASSERT ( actualRendertargetSurfaceSize.h > 0 ); + + // What size RT do we need to get 1:1 mapping? + Sizei idealPixelSize = CalculateIdealPixelSize ( eyeType, distortion, fov, desiredPixelDensity ); + // ...but we might not actually get that size. + return CalculateViewportInternal ( eyeType, + actualRendertargetSurfaceSize, + idealPixelSize, + bRendertargetSharedByBothEyes, bMonoRenderingMode ); +} + +static ViewportScaleAndOffset CalculateViewportScaleAndOffsetInternal ( + ScaleAndOffset2D const &eyeToSourceNDC, + Recti const &renderedViewport, + Sizei const &actualRendertargetSurfaceSize ) +{ + ViewportScaleAndOffset result; + result.RenderedViewport = renderedViewport; + result.EyeToSourceUV = CreateUVScaleAndOffsetfromNDCScaleandOffset( + eyeToSourceNDC, renderedViewport, actualRendertargetSurfaceSize ); + return result; +} + + +static StereoEyeParams CalculateStereoEyeParamsInternal ( StereoEye eyeType, HmdRenderInfo const &hmd, + DistortionRenderDesc const &distortion, + FovPort const &fov, + Sizei const &actualRendertargetSurfaceSize, + Recti const &renderedViewport, + bool bRightHanded = true, float zNear = 0.01f, float zFar = 10000.0f, + bool bMonoRenderingMode = false, + float zoomFactor = 1.0f ) +{ + // Generate the projection matrix for intermediate rendertarget. + // Z range can also be inserted later by the app (though not in this particular case) + float fovScale = 1.0f / zoomFactor; + FovPort zoomedFov = fov; + zoomedFov.LeftTan *= fovScale; + zoomedFov.RightTan *= fovScale; + zoomedFov.UpTan *= fovScale; + zoomedFov.DownTan *= fovScale; + Matrix4f projection = CreateProjection ( bRightHanded, zoomedFov, zNear, zFar ); + + // Find the mapping from TanAngle space to target NDC space. + // Note this does NOT take the zoom factor into account because + // this is the mapping of actual physical eye FOV (and our eyes do not zoom!) + // to screen space. + ScaleAndOffset2D eyeToSourceNDC = CreateNDCScaleAndOffsetFromFov ( fov ); + + // The size of the final FB, which is fixed and determined by the physical size of the device display. + Recti distortedViewport = GetFramebufferViewport ( eyeType, hmd ); + Vector3f virtualCameraOffset = CalculateEyeVirtualCameraOffset(hmd, eyeType, bMonoRenderingMode); + + StereoEyeParams result; + result.Eye = eyeType; + result.ViewAdjust = Matrix4f::Translation(virtualCameraOffset); + result.Distortion = distortion; + result.DistortionViewport = distortedViewport; + result.Fov = fov; + result.RenderedProjection = projection; + result.EyeToSourceNDC = eyeToSourceNDC; + ViewportScaleAndOffset vsao = CalculateViewportScaleAndOffsetInternal ( eyeToSourceNDC, renderedViewport, actualRendertargetSurfaceSize ); + result.RenderedViewport = vsao.RenderedViewport; + result.EyeToSourceUV = vsao.EyeToSourceUV; + + return result; +} + + +Vector3f CalculateEyeVirtualCameraOffset(HmdRenderInfo const &hmd, + StereoEye eyeType, bool bmonoRenderingMode) +{ + Vector3f virtualCameraOffset(0); + + if (!bmonoRenderingMode) + { + float eyeCenterRelief = hmd.GetEyeCenter().ReliefInMeters; + + if (eyeType == StereoEye_Left) + { + virtualCameraOffset.x = hmd.EyeLeft.NoseToPupilInMeters; + virtualCameraOffset.z = eyeCenterRelief - hmd.EyeLeft.ReliefInMeters; + } + else if (eyeType == StereoEye_Right) + { + virtualCameraOffset.x = -hmd.EyeRight.NoseToPupilInMeters; + virtualCameraOffset.z = eyeCenterRelief - hmd.EyeRight.ReliefInMeters; + } + } + + return virtualCameraOffset; +} + + +//----------------------------------------------------------------------------------- +// **** Higher-level utility functions. + +Sizei CalculateRecommendedTextureSize ( HmdRenderInfo const &hmd, + bool bRendertargetSharedByBothEyes, + float pixelDensityInCenter /*= 1.0f*/ ) +{ + Sizei idealPixelSize[2]; + for ( int eyeNum = 0; eyeNum < 2; eyeNum++ ) + { + StereoEye eyeType = ( eyeNum == 0 ) ? StereoEye_Left : StereoEye_Right; + + DistortionAndFov distortionAndFov = CalculateDistortionAndFovInternal ( eyeType, hmd, NULL, NULL, OVR_DEFAULT_EXTRA_EYE_ROTATION ); + + idealPixelSize[eyeNum] = CalculateIdealPixelSize ( eyeType, + distortionAndFov.Distortion, + distortionAndFov.Fov, + pixelDensityInCenter ); + } + + Sizei result; + result.w = Alg::Max ( idealPixelSize[0].w, idealPixelSize[1].w ); + result.h = Alg::Max ( idealPixelSize[0].h, idealPixelSize[1].h ); + if ( bRendertargetSharedByBothEyes ) + { + result.w *= 2; + } + return result; +} + +StereoEyeParams CalculateStereoEyeParams ( HmdRenderInfo const &hmd, + StereoEye eyeType, + Sizei const &actualRendertargetSurfaceSize, + bool bRendertargetSharedByBothEyes, + bool bRightHanded /*= true*/, + float zNear /*= 0.01f*/, float zFar /*= 10000.0f*/, + Sizei const *pOverrideRenderedPixelSize /* = NULL*/, + FovPort const *pOverrideFovport /*= NULL*/, + float zoomFactor /*= 1.0f*/ ) +{ + DistortionAndFov distortionAndFov = CalculateDistortionAndFovInternal ( eyeType, hmd, NULL, NULL, OVR_DEFAULT_EXTRA_EYE_ROTATION ); + if ( pOverrideFovport != NULL ) + { + distortionAndFov.Fov = *pOverrideFovport; + } + + Recti viewport; + if ( pOverrideRenderedPixelSize != NULL ) + { + viewport = CalculateViewportInternal ( eyeType, actualRendertargetSurfaceSize, *pOverrideRenderedPixelSize, bRendertargetSharedByBothEyes, false ); + } + else + { + viewport = CalculateViewportDensityInternal ( eyeType, + distortionAndFov.Distortion, + distortionAndFov.Fov, + actualRendertargetSurfaceSize, bRendertargetSharedByBothEyes, 1.0f, false ); + } + + return CalculateStereoEyeParamsInternal ( + eyeType, hmd, + distortionAndFov.Distortion, + distortionAndFov.Fov, + actualRendertargetSurfaceSize, viewport, + bRightHanded, zNear, zFar, false, zoomFactor ); +} + + +FovPort CalculateRecommendedFov ( HmdRenderInfo const &hmd, + StereoEye eyeType, + bool bMakeFovSymmetrical /* = false */ ) +{ + DistortionAndFov distortionAndFov = CalculateDistortionAndFovInternal ( eyeType, hmd, NULL, NULL, OVR_DEFAULT_EXTRA_EYE_ROTATION ); + FovPort fov = distortionAndFov.Fov; + if ( bMakeFovSymmetrical ) + { + // Deal with engines that cannot support an off-center projection. + // Unfortunately this means they will be rendering pixels that the user can't actually see. + float fovTanH = Alg::Max ( fov.LeftTan, fov.RightTan ); + float fovTanV = Alg::Max ( fov.UpTan, fov.DownTan ); + fov.LeftTan = fovTanH; + fov.RightTan = fovTanH; + fov.UpTan = fovTanV; + fov.DownTan = fovTanV; + } + return fov; +} + +ViewportScaleAndOffset ModifyRenderViewport ( StereoEyeParams const ¶ms, + Sizei const &actualRendertargetSurfaceSize, + Recti const &renderViewport ) +{ + return CalculateViewportScaleAndOffsetInternal ( params.EyeToSourceNDC, renderViewport, actualRendertargetSurfaceSize ); +} + +ViewportScaleAndOffset ModifyRenderSize ( StereoEyeParams const ¶ms, + Sizei const &actualRendertargetSurfaceSize, + Sizei const &requestedRenderSize, + bool bRendertargetSharedByBothEyes /*= false*/ ) +{ + Recti renderViewport = CalculateViewportInternal ( params.Eye, actualRendertargetSurfaceSize, requestedRenderSize, bRendertargetSharedByBothEyes, false ); + return CalculateViewportScaleAndOffsetInternal ( params.EyeToSourceNDC, renderViewport, actualRendertargetSurfaceSize ); +} + +ViewportScaleAndOffset ModifyRenderDensity ( StereoEyeParams const ¶ms, + Sizei const &actualRendertargetSurfaceSize, + float pixelDensity /*= 1.0f*/, + bool bRendertargetSharedByBothEyes /*= false*/ ) +{ + Recti renderViewport = CalculateViewportDensityInternal ( params.Eye, params.Distortion, params.Fov, actualRendertargetSurfaceSize, bRendertargetSharedByBothEyes, pixelDensity, false ); + return CalculateViewportScaleAndOffsetInternal ( params.EyeToSourceNDC, renderViewport, actualRendertargetSurfaceSize ); +} + + +//----------------------------------------------------------------------------------- +// **** StereoConfig Implementation + +StereoConfig::StereoConfig(StereoMode mode) + : Mode(mode), + DirtyFlag(true) +{ + // Initialize "fake" default HMD values for testing without HMD plugged in. + // These default values match those returned by DK1 + // (at least they did at time of writing - certainly good enough for debugging) + Hmd.HmdType = HmdType_None; + Hmd.ResolutionInPixels = Sizei(1280, 800); + Hmd.ScreenSizeInMeters = Sizef(0.1498f, 0.0936f); + Hmd.ScreenGapSizeInMeters = 0.0f; + Hmd.CenterFromTopInMeters = 0.0468f; + Hmd.LensSeparationInMeters = 0.0635f; + Hmd.LensDiameterInMeters = 0.035f; + Hmd.LensSurfaceToMidplateInMeters = 0.025f; + Hmd.EyeCups = EyeCup_DK1A; + Hmd.Shutter.Type = HmdShutter_RollingTopToBottom; + Hmd.Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + Hmd.Shutter.VsyncToFirstScanline = 0.000052f; + Hmd.Shutter.FirstScanlineToLastScanline = 0.016580f; + Hmd.Shutter.PixelSettleTime = 0.015f; + Hmd.Shutter.PixelPersistence = ( 1.0f / 60.0f ); + Hmd.EyeLeft.Distortion.SetToIdentity(); + Hmd.EyeLeft.Distortion.MetersPerTanAngleAtCenter = 0.043875f; + Hmd.EyeLeft.Distortion.Eqn = Distortion_RecipPoly4; + Hmd.EyeLeft.Distortion.K[0] = 1.0f; + Hmd.EyeLeft.Distortion.K[1] = -0.3999f; + Hmd.EyeLeft.Distortion.K[2] = 0.2408f; + Hmd.EyeLeft.Distortion.K[3] = -0.4589f; + Hmd.EyeLeft.Distortion.MaxR = 1.0f; + Hmd.EyeLeft.Distortion.ChromaticAberration[0] = 0.006f; + Hmd.EyeLeft.Distortion.ChromaticAberration[1] = 0.0f; + Hmd.EyeLeft.Distortion.ChromaticAberration[2] = -0.014f; + Hmd.EyeLeft.Distortion.ChromaticAberration[3] = 0.0f; + Hmd.EyeLeft.NoseToPupilInMeters = 0.62f; + Hmd.EyeLeft.ReliefInMeters = 0.013f; + Hmd.EyeRight = Hmd.EyeLeft; + + SetViewportMode = SVPM_Density; + SetViewportPixelsPerDisplayPixel = 1.0f; + // Not used in this mode, but init them anyway. + SetViewportSize[0] = Sizei(0,0); + SetViewportSize[1] = Sizei(0,0); + SetViewport[0] = Recti(0,0,0,0); + SetViewport[1] = Recti(0,0,0,0); + + OverrideLens = false; + OverrideTanHalfFov = false; + OverrideZeroIpd = false; + ExtraEyeRotationInRadians = OVR_DEFAULT_EXTRA_EYE_ROTATION; + IsRendertargetSharedByBothEyes = true; + RightHandedProjection = true; + + // This should cause an assert if the app does not call SetRendertargetSize() + RendertargetSize = Sizei ( 0, 0 ); + + ZNear = 0.01f; + ZFar = 10000.0f; + + Set2DAreaFov(DegreeToRad(85.0f)); +} + +void StereoConfig::SetHmdRenderInfo(const HmdRenderInfo& hmd) +{ + Hmd = hmd; + DirtyFlag = true; +} + +void StereoConfig::Set2DAreaFov(float fovRadians) +{ + Area2DFov = fovRadians; + DirtyFlag = true; +} + +const StereoEyeParamsWithOrtho& StereoConfig::GetEyeRenderParams(StereoEye eye) +{ + if ( DirtyFlag ) + { + UpdateComputedState(); + } + + static const UByte eyeParamIndices[3] = { 0, 0, 1 }; + + OVR_ASSERT(eye < sizeof(eyeParamIndices)); + return EyeRenderParams[eyeParamIndices[eye]]; +} + +void StereoConfig::SetLensOverride ( LensConfig const *pLensOverrideLeft /*= NULL*/, + LensConfig const *pLensOverrideRight /*= NULL*/ ) +{ + if ( pLensOverrideLeft == NULL ) + { + OverrideLens = false; + } + else + { + OverrideLens = true; + LensOverrideLeft = *pLensOverrideLeft; + LensOverrideRight = *pLensOverrideLeft; + if ( pLensOverrideRight != NULL ) + { + LensOverrideRight = *pLensOverrideRight; + } + } + DirtyFlag = true; +} + +void StereoConfig::SetRendertargetSize (Size<int> const rendertargetSize, + bool rendertargetIsSharedByBothEyes ) +{ + RendertargetSize = rendertargetSize; + IsRendertargetSharedByBothEyes = rendertargetIsSharedByBothEyes; + DirtyFlag = true; +} + +void StereoConfig::SetFov ( FovPort const *pfovLeft /*= NULL*/, + FovPort const *pfovRight /*= NULL*/ ) +{ + DirtyFlag = true; + if ( pfovLeft == NULL ) + { + OverrideTanHalfFov = false; + } + else + { + OverrideTanHalfFov = true; + FovOverrideLeft = *pfovLeft; + FovOverrideRight = *pfovLeft; + if ( pfovRight != NULL ) + { + FovOverrideRight = *pfovRight; + } + } +} + + +void StereoConfig::SetZeroVirtualIpdOverride ( bool enableOverride ) +{ + DirtyFlag = true; + OverrideZeroIpd = enableOverride; +} + + +void StereoConfig::SetZClipPlanesAndHandedness ( float zNear /*= 0.01f*/, float zFar /*= 10000.0f*/, bool rightHandedProjection /*= true*/ ) +{ + DirtyFlag = true; + ZNear = zNear; + ZFar = zFar; + RightHandedProjection = rightHandedProjection; +} + +void StereoConfig::SetExtraEyeRotation ( float extraEyeRotationInRadians ) +{ + DirtyFlag = true; + ExtraEyeRotationInRadians = extraEyeRotationInRadians; +} + +Sizei StereoConfig::CalculateRecommendedTextureSize ( bool rendertargetSharedByBothEyes, + float pixelDensityInCenter /*= 1.0f*/ ) +{ + return Render::CalculateRecommendedTextureSize ( Hmd, rendertargetSharedByBothEyes, pixelDensityInCenter ); +} + + + +void StereoConfig::UpdateComputedState() +{ + int numEyes = 2; + StereoEye eyeTypes[2]; + + switch ( Mode ) + { + case Stereo_None: + numEyes = 1; + eyeTypes[0] = StereoEye_Center; + break; + + case Stereo_LeftRight_Multipass: + numEyes = 2; + eyeTypes[0] = StereoEye_Left; + eyeTypes[1] = StereoEye_Right; + break; + + default: + OVR_ASSERT( false ); break; + } + + // If either of these fire, you've probably forgotten to call SetRendertargetSize() + OVR_ASSERT ( RendertargetSize.w > 0 ); + OVR_ASSERT ( RendertargetSize.h > 0 ); + + for ( int eyeNum = 0; eyeNum < numEyes; eyeNum++ ) + { + StereoEye eyeType = eyeTypes[eyeNum]; + LensConfig *pLensOverride = NULL; + if ( OverrideLens ) + { + if ( eyeType == StereoEye_Right ) + { + pLensOverride = &LensOverrideRight; + } + else + { + pLensOverride = &LensOverrideLeft; + } + } + + FovPort *pTanHalfFovOverride = NULL; + if ( OverrideTanHalfFov ) + { + if ( eyeType == StereoEye_Right ) + { + pTanHalfFovOverride = &FovOverrideRight; + } + else + { + pTanHalfFovOverride = &FovOverrideLeft; + } + } + + DistortionAndFov distortionAndFov = + CalculateDistortionAndFovInternal ( eyeType, Hmd, + pLensOverride, pTanHalfFovOverride, + ExtraEyeRotationInRadians ); + + EyeRenderParams[eyeNum].StereoEye.Distortion = distortionAndFov.Distortion; + EyeRenderParams[eyeNum].StereoEye.Fov = distortionAndFov.Fov; + } + + if ( OverrideZeroIpd ) + { + // Take the union of the calculated eye FOVs. + FovPort fov; + fov.UpTan = Alg::Max ( EyeRenderParams[0].StereoEye.Fov.UpTan , EyeRenderParams[1].StereoEye.Fov.UpTan ); + fov.DownTan = Alg::Max ( EyeRenderParams[0].StereoEye.Fov.DownTan , EyeRenderParams[1].StereoEye.Fov.DownTan ); + fov.LeftTan = Alg::Max ( EyeRenderParams[0].StereoEye.Fov.LeftTan , EyeRenderParams[1].StereoEye.Fov.LeftTan ); + fov.RightTan = Alg::Max ( EyeRenderParams[0].StereoEye.Fov.RightTan, EyeRenderParams[1].StereoEye.Fov.RightTan ); + EyeRenderParams[0].StereoEye.Fov = fov; + EyeRenderParams[1].StereoEye.Fov = fov; + } + + for ( int eyeNum = 0; eyeNum < numEyes; eyeNum++ ) + { + StereoEye eyeType = eyeTypes[eyeNum]; + + DistortionRenderDesc localDistortion = EyeRenderParams[eyeNum].StereoEye.Distortion; + FovPort fov = EyeRenderParams[eyeNum].StereoEye.Fov; + + // Use a placeholder - will be overridden later. + Recti tempViewport = Recti ( 0, 0, 1, 1 ); + + EyeRenderParams[eyeNum].StereoEye = CalculateStereoEyeParamsInternal ( + eyeType, Hmd, localDistortion, fov, + RendertargetSize, tempViewport, + RightHandedProjection, ZNear, ZFar, + OverrideZeroIpd ); + + // We want to create a virtual 2D surface we can draw debug text messages to. + // We'd like it to be a fixed distance (OrthoDistance) away, + // and to cover a specific FOV (Area2DFov). We need to find the projection matrix for this, + // and also to know how large it is in pixels to achieve a 1:1 mapping at the center of the screen. + float orthoDistance = 0.8f; + float orthoHalfFov = tanf ( Area2DFov * 0.5f ); + Vector2f unityOrthoPixelSize = localDistortion.PixelsPerTanAngleAtCenter * ( orthoHalfFov * 2.0f ); + float localInterpupillaryDistance = Hmd.EyeLeft.NoseToPupilInMeters + Hmd.EyeRight.NoseToPupilInMeters; + if ( OverrideZeroIpd ) + { + localInterpupillaryDistance = 0.0f; + } + Matrix4f ortho = CreateOrthoSubProjection ( true, eyeType, + orthoHalfFov, orthoHalfFov, + unityOrthoPixelSize.x, unityOrthoPixelSize.y, + orthoDistance, localInterpupillaryDistance, + EyeRenderParams[eyeNum].StereoEye.RenderedProjection ); + EyeRenderParams[eyeNum].OrthoProjection = ortho; + } + + // ...and now set up the viewport, scale & offset the way the app wanted. + setupViewportScaleAndOffsets(); + + if ( OverrideZeroIpd ) + { + // Monocular rendering has some fragile parts... don't break any by accident. + OVR_ASSERT ( EyeRenderParams[0].StereoEye.Fov.UpTan == EyeRenderParams[1].StereoEye.Fov.UpTan ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.Fov.DownTan == EyeRenderParams[1].StereoEye.Fov.DownTan ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.Fov.LeftTan == EyeRenderParams[1].StereoEye.Fov.LeftTan ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.Fov.RightTan == EyeRenderParams[1].StereoEye.Fov.RightTan ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.RenderedProjection.M[0][0] == EyeRenderParams[1].StereoEye.RenderedProjection.M[0][0] ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.RenderedProjection.M[1][1] == EyeRenderParams[1].StereoEye.RenderedProjection.M[1][1] ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.RenderedProjection.M[0][2] == EyeRenderParams[1].StereoEye.RenderedProjection.M[0][2] ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.RenderedProjection.M[1][2] == EyeRenderParams[1].StereoEye.RenderedProjection.M[1][2] ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.RenderedViewport == EyeRenderParams[1].StereoEye.RenderedViewport ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.EyeToSourceUV.Offset == EyeRenderParams[1].StereoEye.EyeToSourceUV.Offset ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.EyeToSourceUV.Scale == EyeRenderParams[1].StereoEye.EyeToSourceUV.Scale ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.EyeToSourceNDC.Offset == EyeRenderParams[1].StereoEye.EyeToSourceNDC.Offset ); + OVR_ASSERT ( EyeRenderParams[0].StereoEye.EyeToSourceNDC.Scale == EyeRenderParams[1].StereoEye.EyeToSourceNDC.Scale ); + OVR_ASSERT ( EyeRenderParams[0].OrthoProjection.M[0][0] == EyeRenderParams[1].OrthoProjection.M[0][0] ); + OVR_ASSERT ( EyeRenderParams[0].OrthoProjection.M[1][1] == EyeRenderParams[1].OrthoProjection.M[1][1] ); + OVR_ASSERT ( EyeRenderParams[0].OrthoProjection.M[0][2] == EyeRenderParams[1].OrthoProjection.M[0][2] ); + OVR_ASSERT ( EyeRenderParams[0].OrthoProjection.M[1][2] == EyeRenderParams[1].OrthoProjection.M[1][2] ); + } + + DirtyFlag = false; +} + + + +ViewportScaleAndOffsetBothEyes StereoConfig::setupViewportScaleAndOffsets() +{ + for ( int eyeNum = 0; eyeNum < 2; eyeNum++ ) + { + StereoEye eyeType = ( eyeNum == 0 ) ? StereoEye_Left : StereoEye_Right; + + DistortionRenderDesc localDistortion = EyeRenderParams[eyeNum].StereoEye.Distortion; + FovPort fov = EyeRenderParams[eyeNum].StereoEye.Fov; + + Recti renderedViewport; + switch ( SetViewportMode ) + { + case SVPM_Density: + renderedViewport = CalculateViewportDensityInternal ( + eyeType, localDistortion, fov, + RendertargetSize, IsRendertargetSharedByBothEyes, + SetViewportPixelsPerDisplayPixel, OverrideZeroIpd ); + break; + case SVPM_Size: + if ( ( eyeType == StereoEye_Right ) && !OverrideZeroIpd ) + { + renderedViewport = CalculateViewportInternal ( + eyeType, RendertargetSize, + SetViewportSize[1], + IsRendertargetSharedByBothEyes, OverrideZeroIpd ); + } + else + { + renderedViewport = CalculateViewportInternal ( + eyeType, RendertargetSize, + SetViewportSize[0], + IsRendertargetSharedByBothEyes, OverrideZeroIpd ); + } + break; + case SVPM_Viewport: + if ( ( eyeType == StereoEye_Right ) && !OverrideZeroIpd ) + { + renderedViewport = SetViewport[1]; + } + else + { + renderedViewport = SetViewport[0]; + } + break; + default: OVR_ASSERT ( false ); break; + } + + ViewportScaleAndOffset vpsao = CalculateViewportScaleAndOffsetInternal ( + EyeRenderParams[eyeNum].StereoEye.EyeToSourceNDC, + renderedViewport, + RendertargetSize ); + EyeRenderParams[eyeNum].StereoEye.RenderedViewport = vpsao.RenderedViewport; + EyeRenderParams[eyeNum].StereoEye.EyeToSourceUV = vpsao.EyeToSourceUV; + } + + ViewportScaleAndOffsetBothEyes result; + result.Left.EyeToSourceUV = EyeRenderParams[0].StereoEye.EyeToSourceUV; + result.Left.RenderedViewport = EyeRenderParams[0].StereoEye.RenderedViewport; + result.Right.EyeToSourceUV = EyeRenderParams[1].StereoEye.EyeToSourceUV; + result.Right.RenderedViewport = EyeRenderParams[1].StereoEye.RenderedViewport; + return result; +} + +// Specify a pixel density - how many rendered pixels per pixel in the physical display. +ViewportScaleAndOffsetBothEyes StereoConfig::SetRenderDensity ( float pixelsPerDisplayPixel ) +{ + SetViewportMode = SVPM_Density; + SetViewportPixelsPerDisplayPixel = pixelsPerDisplayPixel; + return setupViewportScaleAndOffsets(); +} + +// Supply the size directly. Will be clamped to the physical rendertarget size. +ViewportScaleAndOffsetBothEyes StereoConfig::SetRenderSize ( Sizei const &renderSizeLeft, Sizei const &renderSizeRight ) +{ + SetViewportMode = SVPM_Size; + SetViewportSize[0] = renderSizeLeft; + SetViewportSize[1] = renderSizeRight; + return setupViewportScaleAndOffsets(); +} + +// Supply the viewport directly. This is not clamped to the physical rendertarget - careful now! +ViewportScaleAndOffsetBothEyes StereoConfig::SetRenderViewport ( Recti const &renderViewportLeft, Recti const &renderViewportRight ) +{ + SetViewportMode = SVPM_Viewport; + SetViewport[0] = renderViewportLeft; + SetViewport[1] = renderViewportRight; + return setupViewportScaleAndOffsets(); +} + +Matrix4f StereoConfig::GetProjectionWithZoom ( StereoEye eye, float fovZoom ) const +{ + int eyeNum = ( eye == StereoEye_Right ) ? 1 : 0; + float fovScale = 1.0f / fovZoom; + FovPort fovPort = EyeRenderParams[eyeNum].StereoEye.Fov; + fovPort.LeftTan *= fovScale; + fovPort.RightTan *= fovScale; + fovPort.UpTan *= fovScale; + fovPort.DownTan *= fovScale; + return CreateProjection ( RightHandedProjection, fovPort, ZNear, ZFar ); +} + + + + +//----------------------------------------------------------------------------------- +// ***** Distortion Mesh Rendering + + +// Pow2 for the Morton order to work! +// 4 is too low - it is easy to see the "wobbles" in the HMD. +// 5 is realllly close but you can see pixel differences with even/odd frame checking. +// 6 is indistinguishable on a monitor on even/odd frames. +static const int DMA_GridSizeLog2 = 6; +static const int DMA_GridSize = 1<<DMA_GridSizeLog2; +static const int DMA_NumVertsPerEye = (DMA_GridSize+1)*(DMA_GridSize+1); +static const int DMA_NumTrisPerEye = (DMA_GridSize)*(DMA_GridSize)*2; + + + +void DistortionMeshDestroy ( DistortionMeshVertexData *pVertices, UInt16 *pTriangleMeshIndices ) +{ + OVR_FREE ( pVertices ); + OVR_FREE ( pTriangleMeshIndices ); +} + +void DistortionMeshCreate ( DistortionMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, + const StereoEyeParams &stereoParams, const HmdRenderInfo &hmdRenderInfo ) +{ + bool rightEye = ( stereoParams.Eye == StereoEye_Right ); + int vertexCount = 0; + int triangleCount = 0; + + // Generate mesh into allocated data and return result. + DistortionMeshCreate(ppVertices, ppTriangleListIndices, &vertexCount, &triangleCount, + rightEye, hmdRenderInfo, stereoParams.Distortion, stereoParams.EyeToSourceNDC); + + *pNumVertices = vertexCount; + *pNumTriangles = triangleCount; +} + + +// Generate distortion mesh for a eye. +void DistortionMeshCreate( DistortionMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, + bool rightEye, + const HmdRenderInfo &hmdRenderInfo, + const DistortionRenderDesc &distortion, const ScaleAndOffset2D &eyeToSourceNDC ) +{ + *pNumVertices = DMA_NumVertsPerEye; + *pNumTriangles = DMA_NumTrisPerEye; + + *ppVertices = (DistortionMeshVertexData*) + OVR_ALLOC( sizeof(DistortionMeshVertexData) * (*pNumVertices) ); + *ppTriangleListIndices = (UInt16*) OVR_ALLOC( sizeof(UInt16) * (*pNumTriangles) * 3 ); + + if (!*ppVertices || !*ppTriangleListIndices) + { + if (*ppVertices) + { + OVR_FREE(*ppVertices); + } + if (*ppTriangleListIndices) + { + OVR_FREE(*ppTriangleListIndices); + } + *ppVertices = NULL; + *ppTriangleListIndices = NULL; + *pNumTriangles = 0; + *pNumVertices = 0; + return; + } + + // When does the fade-to-black edge start? Chosen heuristically. + const float fadeOutBorderFraction = 0.075f; + + + // Populate vertex buffer info + float xOffset = 0.0f; + float uOffset = 0.0f; + OVR_UNUSED(uOffset); + + if (rightEye) + { + xOffset = 1.0f; + uOffset = 0.5f; + } + + // First pass - build up raw vertex data. + DistortionMeshVertexData* pcurVert = *ppVertices; + + for ( int y = 0; y <= DMA_GridSize; y++ ) + { + for ( int x = 0; x <= DMA_GridSize; x++ ) + { + + Vector2f sourceCoordNDC; + // NDC texture coords [-1,+1] + sourceCoordNDC.x = 2.0f * ( (float)x / (float)DMA_GridSize ) - 1.0f; + sourceCoordNDC.y = 2.0f * ( (float)y / (float)DMA_GridSize ) - 1.0f; + Vector2f tanEyeAngle = TransformRendertargetNDCToTanFovSpace ( eyeToSourceNDC, sourceCoordNDC ); + + // This is the function that does the really heavy lifting. + Vector2f screenNDC = TransformTanFovSpaceToScreenNDC ( distortion, tanEyeAngle, false ); + + // We then need RGB UVs. Since chromatic aberration is generated from screen coords, not + // directly from texture NDCs, we can't just use tanEyeAngle, we need to go the long way round. + Vector2f tanEyeAnglesR, tanEyeAnglesG, tanEyeAnglesB; + TransformScreenNDCToTanFovSpaceChroma ( &tanEyeAnglesR, &tanEyeAnglesG, &tanEyeAnglesB, + distortion, screenNDC ); + + pcurVert->TanEyeAnglesR = tanEyeAnglesR; + pcurVert->TanEyeAnglesG = tanEyeAnglesG; + pcurVert->TanEyeAnglesB = tanEyeAnglesB; + + + HmdShutterTypeEnum shutterType = hmdRenderInfo.Shutter.Type; + switch ( shutterType ) + { + case HmdShutter_Global: + pcurVert->TimewarpLerp = 0.0f; + break; + case HmdShutter_RollingLeftToRight: + // Retrace is left to right - left eye goes 0.0 -> 0.5, then right goes 0.5 -> 1.0 + pcurVert->TimewarpLerp = screenNDC.x * 0.25f + 0.25f; + if (rightEye) + { + pcurVert->TimewarpLerp += 0.5f; + } + break; + case HmdShutter_RollingRightToLeft: + // Retrace is right to left - right eye goes 0.0 -> 0.5, then left goes 0.5 -> 1.0 + pcurVert->TimewarpLerp = 0.75f - screenNDC.x * 0.25f; + if (rightEye) + { + pcurVert->TimewarpLerp -= 0.5f; + } + break; + case HmdShutter_RollingTopToBottom: + // Retrace is top to bottom on both eyes at the same time. + pcurVert->TimewarpLerp = screenNDC.y * 0.5f + 0.5f; + break; + default: OVR_ASSERT ( false ); break; + } + + // Fade out at texture edges. + float edgeFadeIn = ( 1.0f / fadeOutBorderFraction ) * + ( 1.0f - Alg::Max ( Alg::Abs ( sourceCoordNDC.x ), Alg::Abs ( sourceCoordNDC.y ) ) ); + // Also fade out at screen edges. + float edgeFadeInScreen = ( 2.0f / fadeOutBorderFraction ) * + ( 1.0f - Alg::Max ( Alg::Abs ( screenNDC.x ), Alg::Abs ( screenNDC.y ) ) ); + edgeFadeIn = Alg::Min ( edgeFadeInScreen, edgeFadeIn ); + + // Don't let verts overlap to the other eye. + screenNDC.x = Alg::Max ( -1.0f, Alg::Min ( screenNDC.x, 1.0f ) ); + screenNDC.y = Alg::Max ( -1.0f, Alg::Min ( screenNDC.y, 1.0f ) ); + + pcurVert->Shade = Alg::Max ( 0.0f, Alg::Min ( edgeFadeIn, 1.0f ) ); + pcurVert->ScreenPosNDC.x = 0.5f * screenNDC.x - 0.5f + xOffset; + pcurVert->ScreenPosNDC.y = -screenNDC.y; + + pcurVert++; + } + } + + + // Populate index buffer info + UInt16 *pcurIndex = *ppTriangleListIndices; + + for ( int triNum = 0; triNum < DMA_GridSize * DMA_GridSize; triNum++ ) + { + // Use a Morton order to help locality of FB, texture and vertex cache. + // (0.325ms raster order -> 0.257ms Morton order) + OVR_ASSERT ( DMA_GridSize <= 256 ); + int x = ( ( triNum & 0x0001 ) >> 0 ) | + ( ( triNum & 0x0004 ) >> 1 ) | + ( ( triNum & 0x0010 ) >> 2 ) | + ( ( triNum & 0x0040 ) >> 3 ) | + ( ( triNum & 0x0100 ) >> 4 ) | + ( ( triNum & 0x0400 ) >> 5 ) | + ( ( triNum & 0x1000 ) >> 6 ) | + ( ( triNum & 0x4000 ) >> 7 ); + int y = ( ( triNum & 0x0002 ) >> 1 ) | + ( ( triNum & 0x0008 ) >> 2 ) | + ( ( triNum & 0x0020 ) >> 3 ) | + ( ( triNum & 0x0080 ) >> 4 ) | + ( ( triNum & 0x0200 ) >> 5 ) | + ( ( triNum & 0x0800 ) >> 6 ) | + ( ( triNum & 0x2000 ) >> 7 ) | + ( ( triNum & 0x8000 ) >> 8 ); + int FirstVertex = x * (DMA_GridSize+1) + y; + // Another twist - we want the top-left and bottom-right quadrants to + // have the triangles split one way, the other two split the other. + // +---+---+---+---+ + // | /| /|\ |\ | + // | / | / | \ | \ | + // |/ |/ | \| \| + // +---+---+---+---+ + // | /| /|\ |\ | + // | / | / | \ | \ | + // |/ |/ | \| \| + // +---+---+---+---+ + // |\ |\ | /| /| + // | \ | \ | / | / | + // | \| \|/ |/ | + // +---+---+---+---+ + // |\ |\ | /| /| + // | \ | \ | / | / | + // | \| \|/ |/ | + // +---+---+---+---+ + // This way triangle edges don't span long distances over the distortion function, + // so linear interpolation works better & we can use fewer tris. + if ( ( x < DMA_GridSize/2 ) != ( y < DMA_GridSize/2 ) ) // != is logical XOR + { + *pcurIndex++ = (UInt16)FirstVertex; + *pcurIndex++ = (UInt16)FirstVertex+1; + *pcurIndex++ = (UInt16)FirstVertex+(DMA_GridSize+1)+1; + + *pcurIndex++ = (UInt16)FirstVertex+(DMA_GridSize+1)+1; + *pcurIndex++ = (UInt16)FirstVertex+(DMA_GridSize+1); + *pcurIndex++ = (UInt16)FirstVertex; + } + else + { + *pcurIndex++ = (UInt16)FirstVertex; + *pcurIndex++ = (UInt16)FirstVertex+1; + *pcurIndex++ = (UInt16)FirstVertex+(DMA_GridSize+1); + + *pcurIndex++ = (UInt16)FirstVertex+1; + *pcurIndex++ = (UInt16)FirstVertex+(DMA_GridSize+1)+1; + *pcurIndex++ = (UInt16)FirstVertex+(DMA_GridSize+1); + } + } +} + +//----------------------------------------------------------------------------------- +// ***** Heightmap Mesh Rendering + + +static const int HMA_GridSizeLog2 = 7; +static const int HMA_GridSize = 1<<HMA_GridSizeLog2; +static const int HMA_NumVertsPerEye = (HMA_GridSize+1)*(HMA_GridSize+1); +static const int HMA_NumTrisPerEye = (HMA_GridSize)*(HMA_GridSize)*2; + + +void HeightmapMeshDestroy ( HeightmapMeshVertexData *pVertices, UInt16 *pTriangleMeshIndices ) +{ + OVR_FREE ( pVertices ); + OVR_FREE ( pTriangleMeshIndices ); +} + +void HeightmapMeshCreate ( HeightmapMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, + const StereoEyeParams &stereoParams, const HmdRenderInfo &hmdRenderInfo ) +{ + bool rightEye = ( stereoParams.Eye == StereoEye_Right ); + int vertexCount = 0; + int triangleCount = 0; + + // Generate mesh into allocated data and return result. + HeightmapMeshCreate(ppVertices, ppTriangleListIndices, &vertexCount, &triangleCount, + rightEye, hmdRenderInfo, stereoParams.EyeToSourceNDC); + + *pNumVertices = vertexCount; + *pNumTriangles = triangleCount; +} + + +// Generate heightmap mesh for one eye. +void HeightmapMeshCreate( HeightmapMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, bool rightEye, + const HmdRenderInfo &hmdRenderInfo, + const ScaleAndOffset2D &eyeToSourceNDC ) +{ + *pNumVertices = HMA_NumVertsPerEye; + *pNumTriangles = HMA_NumTrisPerEye; + + *ppVertices = (HeightmapMeshVertexData*) OVR_ALLOC( sizeof(HeightmapMeshVertexData) * (*pNumVertices) ); + *ppTriangleListIndices = (UInt16*) OVR_ALLOC( sizeof(UInt16) * (*pNumTriangles) * 3 ); + + if (!*ppVertices || !*ppTriangleListIndices) + { + if (*ppVertices) + { + OVR_FREE(*ppVertices); + } + if (*ppTriangleListIndices) + { + OVR_FREE(*ppTriangleListIndices); + } + *ppVertices = NULL; + *ppTriangleListIndices = NULL; + *pNumTriangles = 0; + *pNumVertices = 0; + return; + } + + // Populate vertex buffer info + float xOffset = 0.0f; + float uOffset = 0.0f; + + if (rightEye) + { + xOffset = 1.0f; + uOffset = 0.5f; + } + + // First pass - build up raw vertex data. + HeightmapMeshVertexData* pcurVert = *ppVertices; + + for ( int y = 0; y <= HMA_GridSize; y++ ) + { + for ( int x = 0; x <= HMA_GridSize; x++ ) + { + Vector2f sourceCoordNDC; + // NDC texture coords [-1,+1] + sourceCoordNDC.x = 2.0f * ( (float)x / (float)HMA_GridSize ) - 1.0f; + sourceCoordNDC.y = 2.0f * ( (float)y / (float)HMA_GridSize ) - 1.0f; + Vector2f tanEyeAngle = TransformRendertargetNDCToTanFovSpace ( eyeToSourceNDC, sourceCoordNDC ); + + pcurVert->TanEyeAngles = tanEyeAngle; + + HmdShutterTypeEnum shutterType = hmdRenderInfo.Shutter.Type; + switch ( shutterType ) + { + case HmdShutter_Global: + pcurVert->TimewarpLerp = 0.0f; + break; + case HmdShutter_RollingLeftToRight: + // Retrace is left to right - left eye goes 0.0 -> 0.5, then right goes 0.5 -> 1.0 + pcurVert->TimewarpLerp = sourceCoordNDC.x * 0.25f + 0.25f; + if (rightEye) + { + pcurVert->TimewarpLerp += 0.5f; + } + break; + case HmdShutter_RollingRightToLeft: + // Retrace is right to left - right eye goes 0.0 -> 0.5, then left goes 0.5 -> 1.0 + pcurVert->TimewarpLerp = 0.75f - sourceCoordNDC.x * 0.25f; + if (rightEye) + { + pcurVert->TimewarpLerp -= 0.5f; + } + break; + case HmdShutter_RollingTopToBottom: + // Retrace is top to bottom on both eyes at the same time. + pcurVert->TimewarpLerp = sourceCoordNDC.y * 0.5f + 0.5f; + break; + default: OVR_ASSERT ( false ); break; + } + + // Don't let verts overlap to the other eye. + //sourceCoordNDC.x = Alg::Max ( -1.0f, Alg::Min ( sourceCoordNDC.x, 1.0f ) ); + //sourceCoordNDC.y = Alg::Max ( -1.0f, Alg::Min ( sourceCoordNDC.y, 1.0f ) ); + + //pcurVert->ScreenPosNDC.x = 0.5f * sourceCoordNDC.x - 0.5f + xOffset; + pcurVert->ScreenPosNDC.x = sourceCoordNDC.x; + pcurVert->ScreenPosNDC.y = -sourceCoordNDC.y; + + pcurVert++; + } + } + + + // Populate index buffer info + UInt16 *pcurIndex = *ppTriangleListIndices; + + for ( int triNum = 0; triNum < HMA_GridSize * HMA_GridSize; triNum++ ) + { + // Use a Morton order to help locality of FB, texture and vertex cache. + // (0.325ms raster order -> 0.257ms Morton order) + OVR_ASSERT ( HMA_GridSize < 256 ); + int x = ( ( triNum & 0x0001 ) >> 0 ) | + ( ( triNum & 0x0004 ) >> 1 ) | + ( ( triNum & 0x0010 ) >> 2 ) | + ( ( triNum & 0x0040 ) >> 3 ) | + ( ( triNum & 0x0100 ) >> 4 ) | + ( ( triNum & 0x0400 ) >> 5 ) | + ( ( triNum & 0x1000 ) >> 6 ) | + ( ( triNum & 0x4000 ) >> 7 ); + int y = ( ( triNum & 0x0002 ) >> 1 ) | + ( ( triNum & 0x0008 ) >> 2 ) | + ( ( triNum & 0x0020 ) >> 3 ) | + ( ( triNum & 0x0080 ) >> 4 ) | + ( ( triNum & 0x0200 ) >> 5 ) | + ( ( triNum & 0x0800 ) >> 6 ) | + ( ( triNum & 0x2000 ) >> 7 ) | + ( ( triNum & 0x8000 ) >> 8 ); + int FirstVertex = x * (HMA_GridSize+1) + y; + // Another twist - we want the top-left and bottom-right quadrants to + // have the triangles split one way, the other two split the other. + // +---+---+---+---+ + // | /| /|\ |\ | + // | / | / | \ | \ | + // |/ |/ | \| \| + // +---+---+---+---+ + // | /| /|\ |\ | + // | / | / | \ | \ | + // |/ |/ | \| \| + // +---+---+---+---+ + // |\ |\ | /| /| + // | \ | \ | / | / | + // | \| \|/ |/ | + // +---+---+---+---+ + // |\ |\ | /| /| + // | \ | \ | / | / | + // | \| \|/ |/ | + // +---+---+---+---+ + // This way triangle edges don't span long distances over the distortion function, + // so linear interpolation works better & we can use fewer tris. + if ( ( x < HMA_GridSize/2 ) != ( y < HMA_GridSize/2 ) ) // != is logical XOR + { + *pcurIndex++ = (UInt16)FirstVertex; + *pcurIndex++ = (UInt16)FirstVertex+1; + *pcurIndex++ = (UInt16)FirstVertex+(HMA_GridSize+1)+1; + + *pcurIndex++ = (UInt16)FirstVertex+(HMA_GridSize+1)+1; + *pcurIndex++ = (UInt16)FirstVertex+(HMA_GridSize+1); + *pcurIndex++ = (UInt16)FirstVertex; + } + else + { + *pcurIndex++ = (UInt16)FirstVertex; + *pcurIndex++ = (UInt16)FirstVertex+1; + *pcurIndex++ = (UInt16)FirstVertex+(HMA_GridSize+1); + + *pcurIndex++ = (UInt16)FirstVertex+1; + *pcurIndex++ = (UInt16)FirstVertex+(HMA_GridSize+1)+1; + *pcurIndex++ = (UInt16)FirstVertex+(HMA_GridSize+1); + } + } +} + +//----------------------------------------------------------------------------------- +// ***** Prediction and timewarp. +// + +// Calculates the values from the HMD info. +PredictionValues PredictionGetDeviceValues ( const HmdRenderInfo &hmdRenderInfo, + bool withTimewarp /*= true*/, + bool withVsync /*= true*/ ) +{ + PredictionValues result; + + result.WithTimewarp = withTimewarp; + result.WithVsync = withVsync; + + // For unclear reasons, most graphics systems add an extra frame of latency + // somewhere along the way. In time we'll debug this and figure it out, but + // for now this gets prediction a little bit better. + const float extraFramesOfBufferingKludge = 1.0f; + + if ( withVsync ) + { + // These are the times from the Present+Flush to when the middle of the scene is "averagely visible" (without timewarp) + // So if you had no timewarp, this, plus the time until the next vsync, is how much to predict by. + result.PresentFlushToRenderedScene = extraFramesOfBufferingKludge * hmdRenderInfo.Shutter.FirstScanlineToLastScanline; + // Predict to the middle of the screen being scanned out. + result.PresentFlushToRenderedScene += hmdRenderInfo.Shutter.VsyncToFirstScanline + 0.5f * hmdRenderInfo.Shutter.FirstScanlineToLastScanline; + // Time for pixels to get half-way to settling. + result.PresentFlushToRenderedScene += hmdRenderInfo.Shutter.PixelSettleTime * 0.5f; + // Predict to half-way through persistence + result.PresentFlushToRenderedScene += hmdRenderInfo.Shutter.PixelPersistence * 0.5f; + + // The time from the Present+Flush to when the first scanline is "averagely visible". + result.PresentFlushToTimewarpStart = extraFramesOfBufferingKludge * hmdRenderInfo.Shutter.FirstScanlineToLastScanline; + // Predict to the first line being scanned out. + result.PresentFlushToTimewarpStart += hmdRenderInfo.Shutter.VsyncToFirstScanline; + // Time for pixels to get half-way to settling. + result.PresentFlushToTimewarpStart += hmdRenderInfo.Shutter.PixelSettleTime * 0.5f; + // Predict to half-way through persistence + result.PresentFlushToTimewarpStart += hmdRenderInfo.Shutter.PixelPersistence * 0.5f; + + // Time to the the last scanline. + result.PresentFlushToTimewarpEnd = result.PresentFlushToTimewarpStart + hmdRenderInfo.Shutter.FirstScanlineToLastScanline; + + // Ideal framerate. + result.PresentFlushToPresentFlush = hmdRenderInfo.Shutter.VsyncToNextVsync; + } + else + { + // Timewarp without vsync is a little odd. + // Currently, we assume that without vsync, we have no idea which scanline + // is currently being sent to the display. So we can't do lerping timewarp, + // we can just do a full-screen late-stage fixup. + + // "PresentFlushToRenderedScene" means the time from the Present+Flush to when the middle of the scene is "averagely visible" (without timewarp) + // So if you had no timewarp, this, plus the time until the next flush (which is usually the time to render the frame), is how much to predict by. + // Time for pixels to get half-way to settling. + result.PresentFlushToRenderedScene = hmdRenderInfo.Shutter.PixelSettleTime * 0.5f; + // Predict to half-way through persistence + result.PresentFlushToRenderedScene += hmdRenderInfo.Shutter.PixelPersistence * 0.5f; + + // Without vsync, you don't know timings, and so can't do anything useful with lerped warping. + result.PresentFlushToTimewarpStart = result.PresentFlushToRenderedScene; + result.PresentFlushToTimewarpEnd = result.PresentFlushToRenderedScene; + + // There's no concept of "ideal" when vsync is off. + result.PresentFlushToPresentFlush = 0.0f; + } + + return result; +} + +Matrix4f TimewarpComputePoseDelta ( Matrix4f const &renderedViewFromWorld, Matrix4f const &predictedViewFromWorld, Matrix4f const&eyeViewAdjust ) +{ + Matrix4f worldFromPredictedView = (eyeViewAdjust * predictedViewFromWorld).InvertedHomogeneousTransform(); + Matrix4f matRenderFromNowStart = (eyeViewAdjust * renderedViewFromWorld) * worldFromPredictedView; + + // The sensor-predicted orientations have: X=right, Y=up, Z=backwards. + // The vectors inside the mesh are in NDC to keep the shader simple: X=right, Y=down, Z=forwards. + // So we need to perform a similarity transform on this delta matrix. + // The verbose code would look like this: + /* + Matrix4f matBasisChange; + matBasisChange.SetIdentity(); + matBasisChange.M[0][0] = 1.0f; + matBasisChange.M[1][1] = -1.0f; + matBasisChange.M[2][2] = -1.0f; + Matrix4f matBasisChangeInv = matBasisChange.Inverted(); + matRenderFromNow = matBasisChangeInv * matRenderFromNow * matBasisChange; + */ + // ...but of course all the above is a constant transform and much more easily done. + // We flip the signs of the Y&Z row, then flip the signs of the Y&Z column, + // and of course most of the flips cancel: + // +++ +-- +-- + // +++ -> flip Y&Z columns -> +-- -> flip Y&Z rows -> -++ + // +++ +-- -++ + matRenderFromNowStart.M[0][1] = -matRenderFromNowStart.M[0][1]; + matRenderFromNowStart.M[0][2] = -matRenderFromNowStart.M[0][2]; + matRenderFromNowStart.M[1][0] = -matRenderFromNowStart.M[1][0]; + matRenderFromNowStart.M[2][0] = -matRenderFromNowStart.M[2][0]; + matRenderFromNowStart.M[1][3] = -matRenderFromNowStart.M[1][3]; + matRenderFromNowStart.M[2][3] = -matRenderFromNowStart.M[2][3]; + + return matRenderFromNowStart; +} + +Matrix4f TimewarpComputePoseDeltaPosition ( Matrix4f const &renderedViewFromWorld, Matrix4f const &predictedViewFromWorld, Matrix4f const&eyeViewAdjust ) +{ + Matrix4f worldFromPredictedView = (eyeViewAdjust * predictedViewFromWorld).InvertedHomogeneousTransform(); + Matrix4f matRenderXform = (eyeViewAdjust * renderedViewFromWorld) * worldFromPredictedView; + + return matRenderXform.Inverted(); +} + +TimewarpMachine::TimewarpMachine() +{ + for ( int i = 0; i < 2; i++ ) + { + EyeRenderPoses[i] = Transformf(); + } + DistortionTimeCount = 0; + VsyncEnabled = false; +} + +void TimewarpMachine::Reset(HmdRenderInfo& renderInfo, bool vsyncEnabled, double timeNow) +{ + RenderInfo = renderInfo; + VsyncEnabled = vsyncEnabled; + CurrentPredictionValues = PredictionGetDeviceValues ( renderInfo, true, VsyncEnabled ); + PresentFlushToPresentFlushSeconds = 0.0f; + DistortionTimeCount = 0; + DistortionTimeAverage = 0.0f; + LastFramePresentFlushTime = timeNow; + AfterPresentAndFlush(timeNow); +} + +void TimewarpMachine::AfterPresentAndFlush(double timeNow) +{ + PresentFlushToPresentFlushSeconds = (float)(timeNow - LastFramePresentFlushTime); + LastFramePresentFlushTime = timeNow; + NextFramePresentFlushTime = timeNow + (double)PresentFlushToPresentFlushSeconds; +} + +double TimewarpMachine::GetViewRenderPredictionTime() +{ + // Note that PredictionGetDeviceValues() did all the vsync-dependent thinking for us. + return NextFramePresentFlushTime + CurrentPredictionValues.PresentFlushToRenderedScene; +} + +Transformf TimewarpMachine::GetViewRenderPredictionPose(SensorFusion &sfusion) +{ + double predictionTime = GetViewRenderPredictionTime(); + return sfusion.GetPoseAtTime(predictionTime); +} + +double TimewarpMachine::GetVisiblePixelTimeStart() +{ + // Note that PredictionGetDeviceValues() did all the vsync-dependent thinking for us. + return NextFramePresentFlushTime + CurrentPredictionValues.PresentFlushToTimewarpStart; +} +double TimewarpMachine::GetVisiblePixelTimeEnd() +{ + // Note that PredictionGetDeviceValues() did all the vsync-dependent thinking for us. + return NextFramePresentFlushTime + CurrentPredictionValues.PresentFlushToTimewarpEnd; +} +Transformf TimewarpMachine::GetPredictedVisiblePixelPoseStart(SensorFusion &sfusion) +{ + double predictionTime = GetVisiblePixelTimeStart(); + return sfusion.GetPoseAtTime(predictionTime); +} +Transformf TimewarpMachine::GetPredictedVisiblePixelPoseEnd (SensorFusion &sfusion) +{ + double predictionTime = GetVisiblePixelTimeEnd(); + return sfusion.GetPoseAtTime(predictionTime); +} +Matrix4f TimewarpMachine::GetTimewarpDeltaStart(SensorFusion &sfusion, Transformf const &renderedPose) +{ + Transformf visiblePose = GetPredictedVisiblePixelPoseStart ( sfusion ); + Matrix4f visibleMatrix(visiblePose); + Matrix4f renderedMatrix(renderedPose); + Matrix4f identity; // doesn't matter for orientation-only timewarp + return TimewarpComputePoseDelta ( renderedMatrix, visibleMatrix, identity ); +} +Matrix4f TimewarpMachine::GetTimewarpDeltaEnd (SensorFusion &sfusion, Transformf const &renderedPose) +{ + Transformf visiblePose = GetPredictedVisiblePixelPoseEnd ( sfusion ); + Matrix4f visibleMatrix(visiblePose); + Matrix4f renderedMatrix(renderedPose); + Matrix4f identity; // doesn't matter for orientation-only timewarp + return TimewarpComputePoseDelta ( renderedMatrix, visibleMatrix, identity ); +} + + +// What time should the app wait until before starting distortion? +double TimewarpMachine::JustInTime_GetDistortionWaitUntilTime() +{ + if ( !VsyncEnabled || ( DistortionTimeCount < NumDistortionTimes ) ) + { + // Don't wait. + return LastFramePresentFlushTime; + } + + const float fudgeFactor = 0.002f; // Found heuristically - 1ms is too short because of timing granularity - may need further tweaking! + float howLongBeforePresent = DistortionTimeAverage + fudgeFactor; + // Subtlety here. Technically, the correct time is NextFramePresentFlushTime - howLongBeforePresent. + // However, if the app drops a frame, this then perpetuates it, + // i.e. if the display is running at 60fps, but the last frame was slow, + // (e.g. because of swapping or whatever), then NextFramePresentFlushTime is + // 33ms in the future, not 16ms. Since this function supplies the + // time to wait until, the app will indeed wait until 32ms, so the framerate + // drops to 30fps and never comes back up! + // So we return the *ideal* framerate, not the *actual* framerate. + return LastFramePresentFlushTime + (float)( CurrentPredictionValues.PresentFlushToPresentFlush - howLongBeforePresent ); +} + + +bool TimewarpMachine::JustInTime_NeedDistortionTimeMeasurement() const +{ + if (!VsyncEnabled) + { + return false; + } + return ( DistortionTimeCount < NumDistortionTimes ); +} + +void TimewarpMachine::JustInTime_BeforeDistortionTimeMeasurement(double timeNow) +{ + DistortionTimeCurrentStart = timeNow; +} + +void TimewarpMachine::JustInTime_AfterDistortionTimeMeasurement(double timeNow) +{ + float timeDelta = (float)( timeNow - DistortionTimeCurrentStart ); + if ( DistortionTimeCount < NumDistortionTimes ) + { + DistortionTimes[DistortionTimeCount] = timeDelta; + DistortionTimeCount++; + if ( DistortionTimeCount == NumDistortionTimes ) + { + // Median. + float distortionTimeMedian = 0.0f; + for ( int i = 0; i < NumDistortionTimes/2; i++ ) + { + // Find the maximum time of those remaining. + float maxTime = DistortionTimes[0]; + int maxIndex = 0; + for ( int j = 1; j < NumDistortionTimes; j++ ) + { + if ( maxTime < DistortionTimes[j] ) + { + maxTime = DistortionTimes[j]; + maxIndex = j; + } + } + // Zero that max time, so we'll find the next-highest time. + DistortionTimes[maxIndex] = 0.0f; + distortionTimeMedian = maxTime; + } + DistortionTimeAverage = distortionTimeMedian; + } + } + else + { + OVR_ASSERT ( !"Really didn't need more measurements, thanks" ); + } +} + + +}}} // OVR::Util::Render + diff --git a/LibOVR/Src/Util/Util_Render_Stereo.h b/LibOVR/Src/Util/Util_Render_Stereo.h new file mode 100644 index 0000000..326059e --- /dev/null +++ b/LibOVR/Src/Util/Util_Render_Stereo.h @@ -0,0 +1,498 @@ +/************************************************************************************ + +PublicHeader: OVR.h +Filename : Util_Render_Stereo.h +Content : Sample stereo rendering configuration classes. +Created : October 22, 2012 +Authors : Michael Antonov, Tom Forsyth + +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. + +*************************************************************************************/ + +#ifndef OVR_Util_Render_Stereo_h +#define OVR_Util_Render_Stereo_h + +#include "../OVR_Stereo.h" + + +namespace OVR { + +class SensorFusion; + +namespace Util { namespace Render { + + + +//----------------------------------------------------------------------------------- +// **** Useful debug functions. +// +// Purely for debugging - the results are not very end-user-friendly. +char const* GetDebugNameEyeCupType ( EyeCupType eyeCupType ); +char const* GetDebugNameHmdType ( HmdTypeEnum hmdType ); + + + +//----------------------------------------------------------------------------------- +// **** Higher-level utility functions. + +Sizei CalculateRecommendedTextureSize ( HmdRenderInfo const &hmd, + bool bRendertargetSharedByBothEyes, + float pixelDensityInCenter = 1.0f ); + +FovPort CalculateRecommendedFov ( HmdRenderInfo const &hmd, + StereoEye eyeType, + bool bMakeFovSymmetrical = false); + +StereoEyeParams CalculateStereoEyeParams ( HmdRenderInfo const &hmd, + StereoEye eyeType, + Sizei const &actualRendertargetSurfaceSize, + bool bRendertargetSharedByBothEyes, + bool bRightHanded = true, + float zNear = 0.01f, float zFar = 10000.0f, + Sizei const *pOverrideRenderedPixelSize = NULL, + FovPort const *pOverrideFovport = NULL, + float zoomFactor = 1.0f ); + +Vector3f CalculateEyeVirtualCameraOffset(HmdRenderInfo const &hmd, + StereoEye eyeType, bool bMonoRenderingMode ); + + +// These are two components from StereoEyeParams that can be changed +// very easily without full recomputation of everything. +struct ViewportScaleAndOffset +{ + Recti RenderedViewport; + ScaleAndOffset2D EyeToSourceUV; +}; + +// Three ways to override the size of the render view dynamically. +// None of these require changing the distortion parameters or the regenerating the distortion mesh, +// and can be called every frame if desired. +ViewportScaleAndOffset ModifyRenderViewport ( StereoEyeParams const ¶ms, + Sizei const &actualRendertargetSurfaceSize, + Recti const &renderViewport ); + +ViewportScaleAndOffset ModifyRenderSize ( StereoEyeParams const ¶ms, + Sizei const &actualRendertargetSurfaceSize, + Sizei const &requestedRenderSize, + bool bRendertargetSharedByBothEyes = false ); + +ViewportScaleAndOffset ModifyRenderDensity ( StereoEyeParams const ¶ms, + Sizei const &actualRendertargetSurfaceSize, + float pixelDensity = 1.0f, + bool bRendertargetSharedByBothEyes = false ); + + +//----------------------------------------------------------------------------------- +// ***** StereoConfig + +// StereoConfig maintains a scene stereo state and allow switching between different +// stereo rendering modes. To support rendering, StereoConfig keeps track of HMD +// variables such as screen size, eye-to-screen distance and distortion, and computes +// extra data such as FOV and distortion center offsets based on it. Rendering +// parameters are returned though StereoEyeParams for each eye. +// +// Beyond regular 3D projection, this class supports rendering a 2D orthographic +// surface for UI and text. The 2D surface will be defined by CreateOrthoSubProjection(). +// The (0,0) coordinate corresponds to eye center location. +// +// Applications are not required to use this class, but they should be doing very +// similar sequences of operations, and it may be useful to start with this class +// and modify it. + +struct StereoEyeParamsWithOrtho +{ + StereoEyeParams StereoEye; + Matrix4f OrthoProjection; +}; + +struct ViewportScaleAndOffsetBothEyes +{ + ViewportScaleAndOffset Left; + ViewportScaleAndOffset Right; +}; + +class StereoConfig +{ +public: + + // StereoMode describes rendering modes that can be used by StereoConfig. + // These modes control whether stereo rendering is used or not (Stereo_None), + // and how it is implemented. + enum StereoMode + { + Stereo_None = 0, // Single eye + Stereo_LeftRight_Multipass = 1, // One frustum per eye + }; + + + StereoConfig(StereoMode mode = Stereo_LeftRight_Multipass); + + //--------------------------------------------------------------------------------------------- + // *** Core functions - every app MUST call these functions at least once. + + // Sets HMD parameters; also initializes distortion coefficients. + void SetHmdRenderInfo(const HmdRenderInfo& hmd); + + // Set the physical size of the rendertarget surface the app created, + // and whether one RT is shared by both eyes, or each eye has its own RT: + // true: both eyes are rendered to the same RT. Left eye starts at top-left, right eye starts at top-middle. + // false: each eye is rendered to its own RT. Some GPU architectures prefer this arrangement. + // Typically, the app would call CalculateRecommendedTextureSize() to suggest the choice of RT size. + // This setting must be exactly the size of the actual RT created, or the UVs produced will be incorrect. + // If the app wants to render to a subsection of the RT, it should use SetRenderSize() + void SetRendertargetSize (Size<int> const rendertargetSize, + bool rendertargetIsSharedByBothEyes ); + + // Returns full set of Stereo rendering parameters for the specified eye. + const StereoEyeParamsWithOrtho& GetEyeRenderParams(StereoEye eye); + + + + //--------------------------------------------------------------------------------------------- + // *** Optional functions - an app may call these to override default behaviours. + + const HmdRenderInfo& GetHmdRenderInfo() const { return Hmd; } + + // Returns the recommended size of rendertargets. + // If rendertargetIsSharedByBothEyes is true, this is the size of the combined buffer. + // If rendertargetIsSharedByBothEyes is false, this is the size of each individual buffer. + // pixelDensityInCenter may be set to any number - by default it will match the HMD resolution in the center of the image. + // After creating the rendertargets, the application MUST call SetRendertargetSize() with the actual size created + // (which can be larger or smaller as the app wishes, but StereoConfig needs to know either way) + Sizei CalculateRecommendedTextureSize ( bool rendertargetSharedByBothEyes, + float pixelDensityInCenter = 1.0f ); + + // Sets a stereo rendering mode and updates internal cached + // state (matrices, per-eye view) based on it. + void SetStereoMode(StereoMode mode) { Mode = mode; DirtyFlag = true; } + StereoMode GetStereoMode() const { return Mode; } + + // Sets the fieldOfView that the 2D coordinate area stretches to. + void Set2DAreaFov(float fovRadians); + + // Really only for science experiments - no normal app should ever need to override + // the HMD's lens descriptors. Passing NULL removes the override. + // Supply both = set left and right. + // Supply just left = set both to the same. + // Supply neither = remove override. + void SetLensOverride ( LensConfig const *pLensOverrideLeft = NULL, + LensConfig const *pLensOverrideRight = NULL ); + + // Override the rendered FOV in various ways. All angles in tangent units. + // This is not clamped to the physical FOV of the display - you'll need to do that yourself! + // Supply both = set left and right. + // Supply just left = set both to the same. + // Supply neither = remove override. + void SetFov ( FovPort const *pfovLeft = NULL, + FovPort const *pfovRight = NULL ); + + void SetFovPortRadians ( float horizontal, float vertical ) + { + FovPort fov = FovPort::CreateFromRadians(horizontal, vertical); + SetFov( &fov, &fov ); + } + + + // This forces a "zero IPD" mode where there is just a single render with an FOV that + // is the union of the two calculated FOVs. + // The calculated render is for the left eye. Any size & FOV overrides for the right + // eye will be ignored. + // If you query the right eye's size, you will get the same render + // size & position as the left eye - you should not actually do the render of course! + // The distortion values will be different, because it goes to a different place on the framebuffer. + // Note that if you do this, the rendertarget does not need to be twice the width of + // the render size any more. + void SetZeroVirtualIpdOverride ( bool enableOverride ); + + // Allows the app to specify near and far clip planes and the right/left-handedness of the projection matrix. + void SetZClipPlanesAndHandedness ( float zNear = 0.01f, float zFar = 10000.0f, + bool rightHandedProjection = true ); + + // Allows the app to specify how much extra eye rotation to allow when determining the visible FOV. + void SetExtraEyeRotation ( float extraEyeRotationInRadians = 0.0f ); + + // The dirty flag is set by any of the above calls. Just handy for the app to know + // if e.g. the distortion mesh needs regeneration. + void SetDirty() { DirtyFlag = true; } + bool IsDirty() { return DirtyFlag; } + + // An app never needs to call this - GetEyeRenderParams will call it internally if + // the state is dirty. However apps can call this explicitly to control when and where + // computation is performed (e.g. not inside critical loops) + void UpdateComputedState(); + + // This returns the projection matrix with a "zoom". Does not modify any internal state. + Matrix4f GetProjectionWithZoom ( StereoEye eye, float fovZoom ) const; + + + //--------------------------------------------------------------------------------------------- + // The SetRender* functions are special. + // + // They do not require a full recalculation of state, and they do not change anything but the + // ViewportScaleAndOffset data for the eyes (which they return), and do not set the dirty flag! + // This means they can be called without regenerating the distortion mesh, and thus + // can happily be called every frame without causing performance problems. Dynamic rescaling + // of the rendertarget can help keep framerate up in demanding VR applications. + // See the documentation for more details on their use. + + // Specify a pixel density - how many rendered pixels per pixel in the physical display. + ViewportScaleAndOffsetBothEyes SetRenderDensity ( float pixelsPerDisplayPixel ); + + // Supply the size directly. Will be clamped to the physical rendertarget size. + ViewportScaleAndOffsetBothEyes SetRenderSize ( Sizei const &renderSizeLeft, Sizei const &renderSizeRight ); + + // Supply the viewport directly. This is not clamped to the physical rendertarget - careful now! + ViewportScaleAndOffsetBothEyes SetRenderViewport ( Recti const &renderViewportLeft, Recti const &renderViewportRight ); + +private: + + // *** Modifiable State + + StereoMode Mode; + HmdRenderInfo Hmd; + + float Area2DFov; // FOV range mapping to the 2D area. + + // Only one of these three overrides can be true! + enum SetViewportModeEnum + { + SVPM_Density, + SVPM_Size, + SVPM_Viewport, + } SetViewportMode; + // ...and depending which it is, one of the following are used. + float SetViewportPixelsPerDisplayPixel; + Sizei SetViewportSize[2]; + Recti SetViewport[2]; + + // Other overrides. + bool OverrideLens; + LensConfig LensOverrideLeft; + LensConfig LensOverrideRight; + Sizei RendertargetSize; + bool OverrideTanHalfFov; + FovPort FovOverrideLeft; + FovPort FovOverrideRight; + bool OverrideZeroIpd; + float ZNear; + float ZFar; + float ExtraEyeRotationInRadians; + bool IsRendertargetSharedByBothEyes; + bool RightHandedProjection; + + bool DirtyFlag; // Set when any if the modifiable state changed. Does NOT get set by SetRender*() + + // Utility function. + ViewportScaleAndOffsetBothEyes setupViewportScaleAndOffsets(); + + // *** Computed State + +public: // Small hack for the config tool. Normal code should never read EyeRenderParams directly - use GetEyeRenderParams() instead. + // 0/1 = left/right main views. + StereoEyeParamsWithOrtho EyeRenderParams[2]; +}; + + +//----------------------------------------------------------------------------------- +// ***** Distortion Mesh Rendering +// + +// Stores both texture UV coords, or tan(angle) values. +// Use whichever set of data the specific distortion algorithm requires. +// This struct *must* be binary compatible with CAPI ovrDistortionVertex. +struct DistortionMeshVertexData +{ + // [-1,+1],[-1,+1] over the entire framebuffer. + Vector2f ScreenPosNDC; + // [0.0-1.0] interpolation value for timewarping - see documentation for details. + float TimewarpLerp; + // [0.0-1.0] fade-to-black at the edges to reduce peripheral vision noise. + float Shade; + // The red, green, and blue vectors in tan(angle) space. + // Scale and offset by the values in StereoEyeParams.EyeToSourceUV.Scale + // and StereoParams.EyeToSourceUV.Offset to get to real texture UV coords. + Vector2f TanEyeAnglesR; + Vector2f TanEyeAnglesG; + Vector2f TanEyeAnglesB; +}; + + +void DistortionMeshCreate ( DistortionMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, + const StereoEyeParams &stereoParams, const HmdRenderInfo &hmdRenderInfo ); + +// Generate distortion mesh for a eye. This version requires less data then stereoParms, supporting +// dynamic change in render target viewport. +void DistortionMeshCreate( DistortionMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, + bool rightEye, + const HmdRenderInfo &hmdRenderInfo, + const DistortionRenderDesc &distortion, const ScaleAndOffset2D &eyeToSourceNDC ); + +void DistortionMeshDestroy ( DistortionMeshVertexData *pVertices, UInt16 *pTriangleMeshIndices ); + + +//----------------------------------------------------------------------------------- +// ***** Heightmap Mesh Rendering +// + +// Stores both texture UV coords, or tan(angle) values. +// This struct *must* be binary compatible with CAPI ovrHeightmapVertex. +struct HeightmapMeshVertexData +{ + // [-1,+1],[-1,+1] over the entire framebuffer. + Vector2f ScreenPosNDC; + // [0.0-1.0] interpolation value for timewarping - see documentation for details. + float TimewarpLerp; + // The vectors in tan(angle) space. + // Scale and offset by the values in StereoEyeParams.EyeToSourceUV.Scale + // and StereoParams.EyeToSourceUV.Offset to get to real texture UV coords. + Vector2f TanEyeAngles; +}; + + +void HeightmapMeshCreate ( HeightmapMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, + const StereoEyeParams &stereoParams, const HmdRenderInfo &hmdRenderInfo ); + +// Generate heightmap mesh for a eye. This version requires less data then stereoParms, supporting +// dynamic change in render target viewport. +void HeightmapMeshCreate( HeightmapMeshVertexData **ppVertices, UInt16 **ppTriangleListIndices, + int *pNumVertices, int *pNumTriangles, bool rightEye, + const HmdRenderInfo &hmdRenderInfo, const ScaleAndOffset2D &eyeToSourceNDC ); + +void HeightmapMeshDestroy ( HeightmapMeshVertexData *pVertices, UInt16 *pTriangleMeshIndices ); + + + +//----------------------------------------------------------------------------------- +// ***** Prediction and timewarp. +// + +struct PredictionValues +{ + // All values in seconds. + // These are the times in seconds from a present+flush to the relevant display element. + // The time is measured to the middle of that element's visibility window, + // e.g. if the device is a full-persistence display, the element will be visible for + // an entire frame, so the time measures to the middle of that period, i.e. half the frame time. + float PresentFlushToRenderedScene; // To the overall rendered 3D scene being visible. + float PresentFlushToTimewarpStart; // To when the first timewarped scanline will be visible. + float PresentFlushToTimewarpEnd; // To when the last timewarped scanline will be visible. + float PresentFlushToPresentFlush; // To the next present+flush, i.e. the ideal framerate. + + bool WithTimewarp; + bool WithVsync; +}; + +// Calculates the values from the HMD info. +PredictionValues PredictionGetDeviceValues ( const HmdRenderInfo &hmdRenderInfo, + bool withTimewarp = true, + bool withVsync = true ); + +// Pass in an orientation used to render the scene, and then the predicted orientation +// (which may have been computed later on, and thus is more accurate), and this +// will return the matrix to pass to the timewarp distortion shader. +// TODO: deal with different handedness? +Matrix4f TimewarpComputePoseDelta ( Matrix4f const &renderedViewFromWorld, Matrix4f const &predictedViewFromWorld, Matrix4f const&eyeViewAdjust ); +Matrix4f TimewarpComputePoseDeltaPosition ( Matrix4f const &renderedViewFromWorld, Matrix4f const &predictedViewFromWorld, Matrix4f const&eyeViewAdjust ); + + + +// TimewarpMachine helps keep track of rendered frame timing and +// handles predictions for time-warp rendering. +class TimewarpMachine +{ +public: + TimewarpMachine(); + + // Call this on and every time something about the setup changes. + void Reset ( HmdRenderInfo& renderInfo, bool vsyncEnabled, double timeNow ); + + // The only reliable time in most engines is directly after the frame-present and GPU flush-and-wait. + // This call should be done right after that to give this system the timing info it needs. + void AfterPresentAndFlush(double timeNow); + + // The "average" time the rendered frame will show up, + // and the predicted pose of the HMD at that time. + // You usually only need to call one of these functions. + double GetViewRenderPredictionTime(); + Transformf GetViewRenderPredictionPose(SensorFusion &sfusion); + + + // Timewarp prediction functions. You usually only need to call one of these three sets of functions. + + // The predicted times that the first and last pixel will be visible on-screen. + double GetVisiblePixelTimeStart(); + double GetVisiblePixelTimeEnd(); + // Predicted poses of the HMD at those first and last pixels. + Transformf GetPredictedVisiblePixelPoseStart(SensorFusion &sfusion); + Transformf GetPredictedVisiblePixelPoseEnd (SensorFusion &sfusion); + // The delta matrices to feed to the timewarp distortion code, + // given the pose that was used for rendering. + // (usually the one returned by GetViewRenderPredictionPose() earlier) + Matrix4f GetTimewarpDeltaStart(SensorFusion &sfusion, Transformf const &renderedPose); + Matrix4f GetTimewarpDeltaEnd (SensorFusion &sfusion, Transformf const &renderedPose); + + + // Just-In-Time distortion aims to delay the second sensor reading & distortion + // until the very last moment to improve prediction. However, it is a little scary, + // since the delay might wait too long and miss the vsync completely! + // Use of the JustInTime_* functions is entirely optional, and we advise allowing + // users to turn it off in their video options to cope with odd machine configurations. + + // What time should the app wait until before starting distortion? + double JustInTime_GetDistortionWaitUntilTime(); + + // Used to time the distortion rendering + bool JustInTime_NeedDistortionTimeMeasurement() const; + void JustInTime_BeforeDistortionTimeMeasurement(double timeNow); + void JustInTime_AfterDistortionTimeMeasurement(double timeNow); + + +private: + + bool VsyncEnabled; + HmdRenderInfo RenderInfo; + PredictionValues CurrentPredictionValues; + + enum { NumDistortionTimes = 10 }; + int DistortionTimeCount; + double DistortionTimeCurrentStart; + float DistortionTimes[NumDistortionTimes]; + float DistortionTimeAverage; + + // Pose at which last time the eye was rendered. + Transformf EyeRenderPoses[2]; + + // Absolute time of the last present+flush + double LastFramePresentFlushTime; + // Seconds between presentflushes + float PresentFlushToPresentFlushSeconds; + // Predicted absolute time of the next present+flush + double NextFramePresentFlushTime; + +}; + + + +}}} // OVR::Util::Render + +#endif |