/************************************************************************************ Copyright : Copyright 2014 Oculus VR, LLC. All Rights reserved. Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); you may not use the Oculus VR Rift SDK except in compliance with the License, which is provided at the time of installation or download, or which otherwise accompanies this software in either electronic or hard copy form. You may obtain a copy of the License at http://www.oculusvr.com/licenses/LICENSE-3.2 Unless required by applicable law or agreed to in writing, the Oculus VR SDK distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ************************************************************************************/ using System; using System.Runtime.InteropServices; using UnityEngine; using Ovr; /// /// Manages an Oculus Rift head-mounted display (HMD). /// public class OVRDisplay { /// /// Specifies the size and field-of-view for one eye texture. /// public struct EyeRenderDesc { /// /// The horizontal and vertical size of the texture. /// public Vector2 resolution; /// /// The angle of the horizontal and vertical field of view in degrees. /// public Vector2 fov; } /// /// Contains latency measurements for a single frame of rendering. /// public struct LatencyData { /// /// The time it took to render both eyes in seconds. /// public float render; /// /// The time it took to perform TimeWarp in seconds. /// public float timeWarp; /// /// The time between the end of TimeWarp and scan-out in seconds. /// public float postPresent; } /// /// If true, a physical HMD is attached to the system. /// /// true if is present; otherwise, false. public bool isPresent { get { #if !UNITY_ANDROID || UNITY_EDITOR return (OVRManager.capiHmd.GetTrackingState().StatusFlags & (uint)StatusBits.HmdConnected) != 0; #else return OVR_IsHMDPresent(); #endif } } private int prevScreenWidth; private int prevScreenHeight; private bool needsSetTexture; private float prevVirtualTextureScale; private bool prevFullScreen; private OVRPose[] eyePoses = new OVRPose[(int)OVREye.Count]; private EyeRenderDesc[] eyeDescs = new EyeRenderDesc[(int)OVREye.Count]; private RenderTexture[] eyeTextures = new RenderTexture[eyeTextureCount]; private int[] eyeTextureIds = new int[eyeTextureCount]; private int currEyeTextureIdx = 0; private static int frameCount = 0; #if !UNITY_ANDROID && !UNITY_EDITOR private bool needsSetViewport; #endif #if UNITY_ANDROID && !UNITY_EDITOR private const int eyeTextureCount = 3 * (int)OVREye.Count; // triple buffer #else private const int eyeTextureCount = 1 * (int)OVREye.Count; #endif #if UNITY_ANDROID && !UNITY_EDITOR private int nextEyeTextureIdx = 0; #endif /// /// Creates an instance of OVRDisplay. Called by OVRManager. /// public OVRDisplay() { #if !UNITY_ANDROID || UNITY_EDITOR needsSetTexture = true; prevFullScreen = Screen.fullScreen; prevVirtualTextureScale = OVRManager.instance.virtualTextureScale; #elif !UNITY_ANDROID && !UNITY_EDITOR needsSetViewport = true; #endif ConfigureEyeDesc(OVREye.Left); ConfigureEyeDesc(OVREye.Right); for (int i = 0; i < eyeTextureCount; i += 2) { ConfigureEyeTexture(i, OVREye.Left, OVRManager.instance.nativeTextureScale); ConfigureEyeTexture(i, OVREye.Right, OVRManager.instance.nativeTextureScale); } } /// /// Updates the internal state of the OVRDisplay. Called by OVRManager. /// public void Update() { // HACK - needed to force DX11 into low persistence mode, remove after Unity patch release if (frameCount < 2) { uint caps = OVRManager.capiHmd.GetEnabledCaps(); caps ^= (uint)HmdCaps.LowPersistence; OVRManager.capiHmd.SetEnabledCaps(caps); } UpdateViewport(); UpdateTextures(); } /// /// Marks the beginning of all rendering. /// public void BeginFrame() { bool updateFrameCount = !(OVRManager.instance.timeWarp && OVRManager.instance.freezeTimeWarp); if (updateFrameCount) { frameCount++; } OVRPluginEvent.IssueWithData(RenderEventType.BeginFrame, frameCount); } /// /// Marks the end of all rendering. /// public void EndFrame() { OVRPluginEvent.Issue(RenderEventType.EndFrame); } /// /// Gets the head pose at the current time or predicted at the given time. /// public OVRPose GetHeadPose(double predictionTime = 0d) { #if !UNITY_ANDROID || UNITY_EDITOR double abs_time_plus_pred = Hmd.GetTimeInSeconds() + predictionTime; TrackingState state = OVRManager.capiHmd.GetTrackingState(abs_time_plus_pred); return state.HeadPose.ThePose.ToPose(); #else float px = 0, py = 0, pz = 0, ow = 0, ox = 0, oy = 0, oz = 0; double atTime = Time.time + predictionTime; OVR_GetCameraPositionOrientation(ref px, ref py, ref pz, ref ox, ref oy, ref oz, ref ow, atTime); return new OVRPose { position = new Vector3(px, py, -pz), orientation = new Quaternion(-ox, -oy, oz, ow), }; #endif } #if UNITY_ANDROID && !UNITY_EDITOR private float w = 0, x = 0, y = 0, z = 0, fov = 90f; #endif /// /// Gets the pose of the given eye, predicted for the time when the current frame will scan out. /// public OVRPose GetEyePose(OVREye eye) { #if !UNITY_ANDROID || UNITY_EDITOR bool updateEyePose = !(OVRManager.instance.timeWarp && OVRManager.instance.freezeTimeWarp); if (updateEyePose) { eyePoses[(int)eye] = OVR_GetRenderPose(frameCount, (int)eye).ToPose(); } return eyePoses[(int)eye]; #else if (eye == OVREye.Left) OVR_GetSensorState( false, ref w, ref x, ref y, ref z, ref fov, ref OVRManager.timeWarpViewNumber); Quaternion rot = new Quaternion(-x, -y, z, w); float eyeOffsetX = 0.5f * OVRManager.profile.ipd; eyeOffsetX = (eye == OVREye.Left) ? -eyeOffsetX : eyeOffsetX; Vector3 pos = rot * new Vector3(eyeOffsetX, 0.0f, 0.0f); return new OVRPose { position = pos, orientation = rot, }; #endif } /// /// Gets the given eye's projection matrix. /// /// Specifies the eye. /// The distance to the near clipping plane. /// The distance to the far clipping plane. public Matrix4x4 GetProjection(int eyeId, float nearClip, float farClip) { #if !UNITY_ANDROID || UNITY_EDITOR FovPort fov = OVRManager.capiHmd.GetDesc().DefaultEyeFov[eyeId]; return Hmd.GetProjection(fov, nearClip, farClip, true).ToMatrix4x4(); #else return new Matrix4x4(); #endif } /// /// Occurs when the head pose is reset. /// public event System.Action RecenteredPose; /// /// Recenters the head pose. /// public void RecenterPose() { #if !UNITY_ANDROID || UNITY_EDITOR OVRManager.capiHmd.RecenterPose(); #else OVR_ResetSensorOrientation(); #endif if (RecenteredPose != null) { RecenteredPose(); } } /// /// Gets the current acceleration of the head. /// public Vector3 acceleration { get { #if !UNITY_ANDROID || UNITY_EDITOR return OVRManager.capiHmd.GetTrackingState().HeadPose.LinearAcceleration.ToVector3(); #else float x = 0.0f, y = 0.0f, z = 0.0f; OVR_GetAcceleration(ref x, ref y, ref z); return new Vector3(x, y, z); #endif } } /// /// Gets the current angular velocity of the head. /// public Vector3 angularVelocity { get { #if !UNITY_ANDROID || UNITY_EDITOR return OVRManager.capiHmd.GetTrackingState().HeadPose.AngularVelocity.ToVector3(); #else float x = 0.0f, y = 0.0f, z = 0.0f; OVR_GetAngularVelocity(ref x, ref y, ref z); return new Vector3(x, y, z); #endif } } /// /// Gets the resolution and field of view for the given eye. /// public EyeRenderDesc GetEyeRenderDesc(OVREye eye) { return eyeDescs[(int)eye]; } /// /// Gets the currently active render texture for the given eye. /// public RenderTexture GetEyeTexture(OVREye eye) { return eyeTextures[currEyeTextureIdx + (int)eye]; } /// /// Gets the currently active render texture's native ID for the given eye. /// public int GetEyeTextureId(OVREye eye) { return eyeTextureIds[currEyeTextureIdx + (int)eye]; } /// /// True if the direct mode display driver is active. /// public bool isDirectMode { get { #if !UNITY_ANDROID || UNITY_EDITOR uint caps = OVRManager.capiHmd.GetDesc().HmdCaps; uint mask = caps & (uint)HmdCaps.ExtendDesktop; return mask == 0; #else return false; #endif } } /// /// If true, direct mode rendering will also show output in the main window. /// public bool mirrorMode { get { #if !UNITY_ANDROID || UNITY_EDITOR uint caps = OVRManager.capiHmd.GetEnabledCaps(); return (caps & (uint)HmdCaps.NoMirrorToWindow) == 0; #else return false; #endif } set { #if !UNITY_ANDROID || UNITY_EDITOR uint caps = OVRManager.capiHmd.GetEnabledCaps(); if (((caps & (uint)HmdCaps.NoMirrorToWindow) == 0) == value) return; if (value) caps &= ~(uint)HmdCaps.NoMirrorToWindow; else caps |= (uint)HmdCaps.NoMirrorToWindow; OVRManager.capiHmd.SetEnabledCaps(caps); #endif } } /// /// If true, TimeWarp will be used to correct the output of each OVRCameraRig for rotational latency. /// internal bool timeWarp { get { return (distortionCaps & (int)DistortionCaps.TimeWarp) != 0; } set { if (value != timeWarp) distortionCaps ^= (int)DistortionCaps.TimeWarp; } } /// /// If true, VR output will be rendered upside-down. /// internal bool flipInput { get { return (distortionCaps & (int)DistortionCaps.FlipInput) != 0; } set { if (value != flipInput) distortionCaps ^= (int)DistortionCaps.FlipInput; } } /// /// Enables and disables distortion rendering capabilities from the Ovr.DistortionCaps enum. /// public uint distortionCaps { get { return _distortionCaps; } set { if (value == _distortionCaps) return; _distortionCaps = value; #if !UNITY_ANDROID || UNITY_EDITOR OVR_SetDistortionCaps(value); #endif } } private uint _distortionCaps = #if (UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) (uint)DistortionCaps.ProfileNoTimewarpSpinWaits | #endif (uint)DistortionCaps.Chromatic | (uint)DistortionCaps.Vignette | (uint)DistortionCaps.SRGB | (uint)DistortionCaps.Overdrive; /// /// Gets the current measured latency values. /// public LatencyData latency { get { #if !UNITY_ANDROID || UNITY_EDITOR float[] values = { 0.0f, 0.0f, 0.0f }; float[] latencies = OVRManager.capiHmd.GetFloatArray("DK2Latency", values); return new LatencyData { render = latencies[0], timeWarp = latencies[1], postPresent = latencies[2] }; #else return new LatencyData { render = 0.0f, timeWarp = 0.0f, postPresent = 0.0f }; #endif } } private void UpdateViewport() { #if !UNITY_ANDROID && !UNITY_EDITOR needsSetViewport = needsSetViewport || Screen.width != prevScreenWidth || Screen.height != prevScreenHeight; if (needsSetViewport) { SetViewport(0, 0, Screen.width, Screen.height); prevScreenWidth = Screen.width; prevScreenHeight = Screen.height; needsSetViewport = false; } #endif } private void UpdateTextures() { for (int i = 0; i < eyeTextureCount; i++) { if (!eyeTextures[i].IsCreated()) { eyeTextures[i].Create(); eyeTextureIds[i] = eyeTextures[i].GetNativeTextureID(); #if !UNITY_ANDROID || UNITY_EDITOR needsSetTexture = true; #endif } } #if !UNITY_ANDROID || UNITY_EDITOR needsSetTexture = needsSetTexture || OVRManager.instance.virtualTextureScale != prevVirtualTextureScale || Screen.fullScreen != prevFullScreen || OVR_UnityGetModeChange(); if (needsSetTexture) { for (int i = 0; i < eyeTextureCount; i++) { if (eyeTextures[i].GetNativeTexturePtr() == System.IntPtr.Zero) return; OVR_SetTexture(i, eyeTextures[i].GetNativeTexturePtr(), OVRManager.instance.virtualTextureScale); } prevVirtualTextureScale = OVRManager.instance.virtualTextureScale; prevFullScreen = Screen.fullScreen; OVR_UnitySetModeChange(false); needsSetTexture = false; } #else currEyeTextureIdx = nextEyeTextureIdx; nextEyeTextureIdx = (nextEyeTextureIdx + 2) % eyeTextureCount; #endif } private void ConfigureEyeDesc(OVREye eye) { #if !UNITY_ANDROID || UNITY_EDITOR HmdDesc desc = OVRManager.capiHmd.GetDesc(); FovPort fov = desc.DefaultEyeFov[(int)eye]; fov.LeftTan = fov.RightTan = Mathf.Max(fov.LeftTan, fov.RightTan); fov.UpTan = fov.DownTan = Mathf.Max(fov.UpTan, fov.DownTan); // Configure Stereo settings. Default pixel density is one texel per pixel. float desiredPixelDensity = 1f; Sizei texSize = OVRManager.capiHmd.GetFovTextureSize((Ovr.Eye)eye, fov, desiredPixelDensity); float fovH = 2f * Mathf.Rad2Deg * Mathf.Atan(fov.LeftTan); float fovV = 2f * Mathf.Rad2Deg * Mathf.Atan(fov.UpTan); eyeDescs[(int)eye] = new EyeRenderDesc() { resolution = texSize.ToVector2(), fov = new Vector2(fovH, fovV) }; #else eyeDescs[(int)eye] = new EyeRenderDesc() { resolution = new Vector2(1024, 1024), fov = new Vector2(90, 90) }; #endif } private void ConfigureEyeTexture(int eyeBufferIndex, OVREye eye, float scale) { int eyeIndex = eyeBufferIndex + (int)eye; EyeRenderDesc eyeDesc = eyeDescs[(int)eye]; int w = (int)(eyeDesc.resolution.x * scale); int h = (int)(eyeDesc.resolution.y * scale); eyeTextures[eyeIndex] = new RenderTexture(w, h, OVRManager.instance.eyeTextureDepth, OVRManager.instance.eyeTextureFormat); eyeTextures[eyeIndex].antiAliasing = (QualitySettings.antiAliasing == 0) ? 1 : QualitySettings.antiAliasing; eyeTextures[eyeIndex].Create(); eyeTextureIds[eyeIndex] = eyeTextures[eyeIndex].GetNativeTextureID(); } public void ForceSymmetricProj(bool enabled) { #if !UNITY_ANDROID || UNITY_EDITOR OVR_ForceSymmetricProj(enabled); #endif } public void SetViewport(int x, int y, int w, int h) { #if !UNITY_ANDROID || UNITY_EDITOR OVR_SetViewport(x, y, w, h); #endif } private const string LibOVR = "OculusPlugin"; #if UNITY_ANDROID && !UNITY_EDITOR //TODO: Get rid of these functions and implement OVR.CAPI.Hmd on Android. [DllImport(LibOVR)] private static extern bool OVR_ResetSensorOrientation(); [DllImport(LibOVR)] private static extern bool OVR_GetAcceleration(ref float x, ref float y, ref float z); [DllImport(LibOVR)] private static extern bool OVR_GetAngularVelocity(ref float x, ref float y, ref float z); [DllImport(LibOVR)] private static extern bool OVR_IsHMDPresent(); [DllImport(LibOVR)] private static extern bool OVR_GetCameraPositionOrientation( ref float px, ref float py, ref float pz, ref float ox, ref float oy, ref float oz, ref float ow, double atTime); [DllImport(LibOVR)] private static extern void OVR_GetDistortionMeshInfo( ref int resH, ref int resV, ref float fovH, ref float fovV); [DllImport(LibOVR)] private static extern void OVR_SetLowPersistenceMode(bool on); [DllImport(LibOVR)] private static extern bool OVR_GetSensorState( bool monoscopic, ref float w, ref float x, ref float y, ref float z, ref float fov, ref int viewNumber); #else [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern void OVR_SetDistortionCaps(uint distortionCaps); [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern bool OVR_SetViewport(int x, int y, int w, int h); [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern Posef OVR_GetRenderPose(int frameIndex, int eyeId); [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern bool OVR_SetTexture(int id, System.IntPtr texture, float scale = 1); [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern bool OVR_UnityGetModeChange(); [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern bool OVR_UnitySetModeChange(bool isChanged); [DllImport(LibOVR, CallingConvention = CallingConvention.Cdecl)] private static extern void OVR_ForceSymmetricProj(bool isEnabled); #endif }