//========= Copyright 2014, Valve Corporation, All rights reserved. =========== // // Purpose: Handles rendering of all SteamVR_Cameras // //============================================================================= using UnityEngine; using System.Collections; using Valve.VR; public class SteamVR_Render : MonoBehaviour { public bool pauseGameWhenDashboardIsVisible = true; public bool lockPhysicsUpdateRateToRenderFrequency = true; public SteamVR_ExternalCamera externalCamera; public string externalCameraConfigPath = "externalcamera.cfg"; #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) public LayerMask leftMask, rightMask; SteamVR_CameraMask cameraMask; #endif public ETrackingUniverseOrigin trackingSpace = ETrackingUniverseOrigin.TrackingUniverseStanding; static public EVREye eye { get; private set; } static private SteamVR_Render _instance; static public SteamVR_Render instance { get { if (_instance == null) { _instance = GameObject.FindObjectOfType(); if (_instance == null) _instance = new GameObject("[SteamVR]").AddComponent(); } return _instance; } } void OnDestroy() { _instance = null; } static private bool isQuitting; void OnApplicationQuit() { isQuitting = true; SteamVR.SafeDispose(); } static public void Add(SteamVR_Camera vrcam) { if (!isQuitting) instance.AddInternal(vrcam); } static public void Remove(SteamVR_Camera vrcam) { if (!isQuitting && _instance != null) instance.RemoveInternal(vrcam); } static public SteamVR_Camera Top() { if (!isQuitting) return instance.TopInternal(); return null; } private SteamVR_Camera[] cameras = new SteamVR_Camera[0]; void AddInternal(SteamVR_Camera vrcam) { var camera = vrcam.GetComponent(); var length = cameras.Length; var sorted = new SteamVR_Camera[length + 1]; int insert = 0; for (int i = 0; i < length; i++) { var c = cameras[i].GetComponent(); if (i == insert && c.depth > camera.depth) sorted[insert++] = vrcam; sorted[insert++] = cameras[i]; } if (insert == length) sorted[insert] = vrcam; cameras = sorted; #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) enabled = true; #endif } void RemoveInternal(SteamVR_Camera vrcam) { var length = cameras.Length; int count = 0; for (int i = 0; i < length; i++) { var c = cameras[i]; if (c == vrcam) ++count; } if (count == 0) return; var sorted = new SteamVR_Camera[length - count]; int insert = 0; for (int i = 0; i < length; i++) { var c = cameras[i]; if (c != vrcam) sorted[insert++] = c; } cameras = sorted; } SteamVR_Camera TopInternal() { if (cameras.Length > 0) return cameras[cameras.Length - 1]; return null; } public TrackedDevicePose_t[] poses = new TrackedDevicePose_t[OpenVR.k_unMaxTrackedDeviceCount]; public TrackedDevicePose_t[] gamePoses = new TrackedDevicePose_t[0]; static private bool _pauseRendering; static public bool pauseRendering { get { return _pauseRendering; } set { _pauseRendering = value; #if !(UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) var compositor = OpenVR.Compositor; if (compositor != null) compositor.SuspendRendering(value); #endif } } private IEnumerator RenderLoop() { while (true) { yield return new WaitForEndOfFrame(); if (pauseRendering) continue; var compositor = OpenVR.Compositor; if (compositor != null) { if (!compositor.CanRenderScene()) continue; compositor.SetTrackingSpace(trackingSpace); #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) SteamVR_Utils.QueueEventOnRenderThread(SteamVR.Unity.k_nRenderEventID_WaitGetPoses); // Hack to flush render event that was queued in Update (this ensures WaitGetPoses has returned before we grab the new values). SteamVR.Unity.EventWriteString("[UnityMain] GetNativeTexturePtr - Begin"); SteamVR_Camera.GetSceneTexture(cameras[0].GetComponent().hdr).GetNativeTexturePtr(); SteamVR.Unity.EventWriteString("[UnityMain] GetNativeTexturePtr - End"); compositor.GetLastPoses(poses, gamePoses); SteamVR_Utils.Event.Send("new_poses", poses); SteamVR_Utils.Event.Send("new_poses_applied"); #endif } var overlay = SteamVR_Overlay.instance; if (overlay != null) overlay.UpdateOverlay(); RenderExternalCamera(); #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) var vr = SteamVR.instance; RenderEye(vr, EVREye.Eye_Left); RenderEye(vr, EVREye.Eye_Right); // Move cameras back to head position so they can be tracked reliably foreach (var c in cameras) { c.transform.localPosition = Vector3.zero; c.transform.localRotation = Quaternion.identity; } if (cameraMask != null) cameraMask.Clear(); #endif } } #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) void RenderEye(SteamVR vr, EVREye eye) { int i = (int)eye; SteamVR_Render.eye = eye; if (cameraMask != null) cameraMask.Set(vr, eye); foreach (var c in cameras) { c.transform.localPosition = vr.eyes[i].pos; c.transform.localRotation = vr.eyes[i].rot; // Update position to keep from getting culled cameraMask.transform.position = c.transform.position; var camera = c.GetComponent(); camera.targetTexture = SteamVR_Camera.GetSceneTexture(camera.hdr); int cullingMask = camera.cullingMask; if (eye == EVREye.Eye_Left) { camera.cullingMask &= ~rightMask; camera.cullingMask |= leftMask; } else { camera.cullingMask &= ~leftMask; camera.cullingMask |= rightMask; } camera.Render(); camera.cullingMask = cullingMask; } } #endif void RenderExternalCamera() { if (externalCamera == null) return; if (!externalCamera.gameObject.activeInHierarchy) return; var frameSkip = (int)Mathf.Max(externalCamera.config.frameSkip, 0.0f); if (Time.frameCount % (frameSkip + 1) != 0) return; // Keep external camera relative to the most relevant vr camera. externalCamera.AttachToCamera(TopInternal()); externalCamera.RenderNear(); externalCamera.RenderFar(); } float sceneResolutionScale = 1.0f, timeScale = 1.0f; private void OnInputFocus(params object[] args) { bool hasFocus = (bool)args[0]; if (hasFocus) { if (pauseGameWhenDashboardIsVisible) { Time.timeScale = timeScale; } SteamVR_Camera.sceneResolutionScale = sceneResolutionScale; } else { if (pauseGameWhenDashboardIsVisible) { timeScale = Time.timeScale; Time.timeScale = 0.0f; } sceneResolutionScale = SteamVR_Camera.sceneResolutionScale; SteamVR_Camera.sceneResolutionScale = 0.5f; } } void OnQuit(params object[] args) { #if UNITY_EDITOR foreach (System.Reflection.Assembly a in System.AppDomain.CurrentDomain.GetAssemblies()) { var t = a.GetType("UnityEditor.EditorApplication"); if (t != null) { t.GetProperty("isPlaying").SetValue(null, false, null); break; } } #else Application.Quit(); #endif } void OnEnable() { StartCoroutine("RenderLoop"); SteamVR_Utils.Event.Listen("input_focus", OnInputFocus); SteamVR_Utils.Event.Listen("Quit", OnQuit); } void OnDisable() { StopAllCoroutines(); SteamVR_Utils.Event.Remove("input_focus", OnInputFocus); SteamVR_Utils.Event.Remove("Quit", OnQuit); } void Awake() { #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) var go = new GameObject("cameraMask"); go.transform.parent = transform; cameraMask = go.AddComponent(); #endif if (externalCamera == null && System.IO.File.Exists(externalCameraConfigPath)) { var prefab = Resources.Load("SteamVR_ExternalCamera"); var instance = Instantiate(prefab); instance.gameObject.name = "External Camera"; externalCamera = instance.transform.GetChild(0).GetComponent(); externalCamera.configPath = externalCameraConfigPath; externalCamera.ReadConfig(); } } void FixedUpdate() { #if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) // We want to call this as soon after Present as possible. SteamVR_Utils.QueueEventOnRenderThread(SteamVR.Unity.k_nRenderEventID_PostPresentHandoff); #endif } #if !(UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) private SteamVR_UpdatePoses poseUpdater; #endif void Update() { #if !(UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0) if (poseUpdater == null) { var go = new GameObject("poseUpdater"); go.transform.parent = transform; poseUpdater = go.AddComponent(); } #else if (cameras.Length == 0) { enabled = false; return; } // If our FixedUpdate rate doesn't match our render framerate, then catch the handoff here. SteamVR_Utils.QueueEventOnRenderThread(SteamVR.Unity.k_nRenderEventID_PostPresentHandoff); #endif // Force controller update in case no one else called this frame to ensure prevState gets updated. SteamVR_Controller.Update(); // Dispatch any OpenVR events. var system = OpenVR.System; if (system != null) { var vrEvent = new VREvent_t(); var size = (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VREvent_t)); for (int i = 0; i < 64; i++) { if (!system.PollNextEvent(ref vrEvent, size)) break; switch ((EVREventType)vrEvent.eventType) { case EVREventType.VREvent_InputFocusCaptured: // another app has taken focus (likely dashboard) if (vrEvent.data.process.oldPid == 0) { SteamVR_Utils.Event.Send("input_focus", false); } break; case EVREventType.VREvent_InputFocusReleased: // that app has released input focus if (vrEvent.data.process.pid == 0) { SteamVR_Utils.Event.Send("input_focus", true); } break; case EVREventType.VREvent_ShowRenderModels: SteamVR_Utils.Event.Send("hide_render_models", false); break; case EVREventType.VREvent_HideRenderModels: SteamVR_Utils.Event.Send("hide_render_models", true); break; default: var name = System.Enum.GetName(typeof(EVREventType), vrEvent.eventType); if (name != null) SteamVR_Utils.Event.Send(name.Substring(8) /*strip VREvent_*/, vrEvent); break; } } } // Ensure various settings to minimize latency. Application.targetFrameRate = -1; Application.runInBackground = true; // don't require companion window focus QualitySettings.maxQueuedFrames = -1; QualitySettings.vSyncCount = 0; // this applies to the companion window if (lockPhysicsUpdateRateToRenderFrequency && Time.timeScale > 0.0f) { var vr = SteamVR.instance; if (vr != null) { var timing = new Compositor_FrameTiming(); timing.m_nSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(Compositor_FrameTiming)); vr.compositor.GetFrameTiming(ref timing, 0); Time.fixedDeltaTime = Time.timeScale / vr.hmd_DisplayFrequency; Time.maximumDeltaTime = Time.fixedDeltaTime * timing.m_nNumFramePresents; } } } }