SteamVR_LoadLevel.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. //======= Copyright (c) Valve Corporation, All rights reserved. ===============
  2. //
  3. // Purpose: Helper for smoothing over transitions between levels.
  4. //
  5. //=============================================================================
  6. using UnityEngine;
  7. using System.Collections;
  8. using Valve.VR;
  9. using System.IO;
  10. public class SteamVR_LoadLevel : MonoBehaviour
  11. {
  12. private static SteamVR_LoadLevel _active = null;
  13. public static bool loading { get { return _active != null; } }
  14. public static float progress
  15. {
  16. get { return (_active != null && _active.async != null) ? _active.async.progress : 0.0f; }
  17. }
  18. public static Texture progressTexture
  19. {
  20. get { return (_active != null) ? _active.renderTexture : null; }
  21. }
  22. // Name of level to load.
  23. public string levelName;
  24. // Name of internal process to launch (instead of levelName).
  25. public string internalProcessPath;
  26. // The command-line args for the internal process to launch.
  27. public string internalProcessArgs;
  28. // If true, call LoadLevelAdditiveAsync instead of LoadLevelAsync.
  29. public bool loadAdditive;
  30. // Async load causes crashes in some apps.
  31. public bool loadAsync = true;
  32. // Optional logo texture.
  33. public Texture loadingScreen;
  34. // Optional progress bar textures.
  35. public Texture progressBarEmpty, progressBarFull;
  36. // Sizes of overlays.
  37. public float loadingScreenWidthInMeters = 6.0f;
  38. public float progressBarWidthInMeters = 3.0f;
  39. // If specified, the loading screen will be positioned in the player's view this far away.
  40. public float loadingScreenDistance = 0.0f;
  41. // Optional overrides for where to display loading screen and progress bar overlays.
  42. // Otherwise defaults to using this object's transform.
  43. public Transform loadingScreenTransform, progressBarTransform;
  44. // Optional skybox override textures.
  45. public Texture front, back, left, right, top, bottom;
  46. // Colors to use when dropping to the compositor between levels if no skybox is set.
  47. public Color backgroundColor = Color.black;
  48. // If false, the background color above gets applied as the foreground color in the compositor.
  49. // This does not have any effect when using a skybox instead.
  50. public bool showGrid = false;
  51. // Time to fade from current scene to the compositor and back.
  52. public float fadeOutTime = 0.5f;
  53. public float fadeInTime = 0.5f;
  54. // Additional time to wait after finished loading before we start fading the new scene back in.
  55. // This is to cover up any initial hitching that takes place right at the start of levels.
  56. // Most scenes should hopefully not require this.
  57. public float postLoadSettleTime = 0.0f;
  58. // Time to fade loading screen in and out (also used for progress bar).
  59. public float loadingScreenFadeInTime = 1.0f;
  60. public float loadingScreenFadeOutTime = 0.25f;
  61. float fadeRate = 1.0f;
  62. float alpha = 0.0f;
  63. AsyncOperation async; // used to track level load progress
  64. RenderTexture renderTexture; // used to render progress bar
  65. ulong loadingScreenOverlayHandle = OpenVR.k_ulOverlayHandleInvalid;
  66. ulong progressBarOverlayHandle = OpenVR.k_ulOverlayHandleInvalid;
  67. public bool autoTriggerOnEnable = false;
  68. void OnEnable()
  69. {
  70. if (autoTriggerOnEnable)
  71. Trigger();
  72. }
  73. public void Trigger()
  74. {
  75. if (!loading && !string.IsNullOrEmpty(levelName))
  76. StartCoroutine(LoadLevel());
  77. }
  78. // Helper function to quickly and simply load a level from script.
  79. public static void Begin(string levelName,
  80. bool showGrid = false, float fadeOutTime = 0.5f,
  81. float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f)
  82. {
  83. var loader = new GameObject("loader").AddComponent<SteamVR_LoadLevel>();
  84. loader.levelName = levelName;
  85. loader.showGrid = showGrid;
  86. loader.fadeOutTime = fadeOutTime;
  87. loader.backgroundColor = new Color(r, g, b, a);
  88. loader.Trigger();
  89. }
  90. // Updates progress bar.
  91. void OnGUI()
  92. {
  93. if (_active != this)
  94. return;
  95. // Optionally create an overlay for our progress bar to use, separate from the loading screen.
  96. if (progressBarEmpty != null && progressBarFull != null)
  97. {
  98. if (progressBarOverlayHandle == OpenVR.k_ulOverlayHandleInvalid)
  99. progressBarOverlayHandle = GetOverlayHandle("progressBar", progressBarTransform != null ? progressBarTransform : transform, progressBarWidthInMeters);
  100. if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  101. {
  102. var progress = (async != null) ? async.progress : 0.0f;
  103. // Use the full bar size for everything.
  104. var w = progressBarFull.width;
  105. var h = progressBarFull.height;
  106. // Create a separate render texture so we can composite the full image on top of the empty one.
  107. if (renderTexture == null)
  108. {
  109. renderTexture = new RenderTexture(w, h, 0);
  110. renderTexture.Create();
  111. }
  112. var prevActive = RenderTexture.active;
  113. RenderTexture.active = renderTexture;
  114. if (Event.current.type == EventType.Repaint)
  115. GL.Clear(false, true, Color.clear);
  116. GUILayout.BeginArea(new Rect(0, 0, w, h));
  117. GUI.DrawTexture(new Rect(0, 0, w, h), progressBarEmpty);
  118. // Reveal the full bar texture based on progress.
  119. GUI.DrawTextureWithTexCoords(new Rect(0, 0, progress * w, h), progressBarFull, new Rect(0.0f, 0.0f, progress, 1.0f));
  120. GUILayout.EndArea();
  121. RenderTexture.active = prevActive;
  122. // Texture needs to be set every frame after it is updated since SteamVR makes a copy internally to a shared texture.
  123. var overlay = OpenVR.Overlay;
  124. if (overlay != null)
  125. {
  126. var texture = new Texture_t();
  127. texture.handle = renderTexture.GetNativeTexturePtr();
  128. texture.eType = SteamVR.instance.textureType;
  129. texture.eColorSpace = EColorSpace.Auto;
  130. overlay.SetOverlayTexture(progressBarOverlayHandle, ref texture);
  131. }
  132. }
  133. }
  134. #if false
  135. // Draw loading screen and progress bar to 2d companion window as well.
  136. if (loadingScreen != null)
  137. {
  138. var screenAspect = (float)Screen.width / Screen.height;
  139. var textureAspect = (float)loadingScreen.width / loadingScreen.height;
  140. float w, h;
  141. if (screenAspect < textureAspect)
  142. {
  143. // Clamp horizontally
  144. w = Screen.width * 0.9f;
  145. h = w / textureAspect;
  146. }
  147. else
  148. {
  149. // Clamp vertically
  150. h = Screen.height * 0.9f;
  151. w = h * textureAspect;
  152. }
  153. GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
  154. var x = Screen.width / 2 - w / 2;
  155. var y = Screen.height / 2 - h / 2;
  156. GUI.DrawTexture(new Rect(x, y, w, h), loadingScreen);
  157. GUILayout.EndArea();
  158. }
  159. if (renderTexture != null)
  160. {
  161. var x = Screen.width / 2 - renderTexture.width / 2;
  162. var y = Screen.height * 0.9f - renderTexture.height;
  163. GUI.DrawTexture(new Rect(x, y, renderTexture.width, renderTexture.height), renderTexture);
  164. }
  165. #endif
  166. }
  167. // Fade our overlays in/out over time.
  168. void Update()
  169. {
  170. if (_active != this)
  171. return;
  172. alpha = Mathf.Clamp01(alpha + fadeRate * Time.deltaTime);
  173. var overlay = OpenVR.Overlay;
  174. if (overlay != null)
  175. {
  176. if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  177. overlay.SetOverlayAlpha(loadingScreenOverlayHandle, alpha);
  178. if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  179. overlay.SetOverlayAlpha(progressBarOverlayHandle, alpha);
  180. }
  181. }
  182. // Corourtine to handle all the steps across loading boundaries.
  183. IEnumerator LoadLevel()
  184. {
  185. // Optionally rotate loading screen transform around the camera into view.
  186. // We assume here that the loading screen is already facing toward the origin,
  187. // and that the progress bar transform (if any) is a child and will follow along.
  188. if (loadingScreen != null && loadingScreenDistance > 0.0f)
  189. {
  190. // Wait until we have tracking.
  191. var hmd = SteamVR_Controller.Input((int)OpenVR.k_unTrackedDeviceIndex_Hmd);
  192. while (!hmd.hasTracking)
  193. yield return null;
  194. var tloading = hmd.transform;
  195. tloading.rot = Quaternion.Euler(0.0f, tloading.rot.eulerAngles.y, 0.0f);
  196. tloading.pos += tloading.rot * new Vector3(0.0f, 0.0f, loadingScreenDistance);
  197. var t = loadingScreenTransform != null ? loadingScreenTransform : transform;
  198. t.position = tloading.pos;
  199. t.rotation = tloading.rot;
  200. }
  201. _active = this;
  202. SteamVR_Events.Loading.Send(true);
  203. // Calculate rate for fading in loading screen and progress bar.
  204. if (loadingScreenFadeInTime > 0.0f)
  205. {
  206. fadeRate = 1.0f / loadingScreenFadeInTime;
  207. }
  208. else
  209. {
  210. alpha = 1.0f;
  211. }
  212. var overlay = OpenVR.Overlay;
  213. // Optionally create our loading screen overlay.
  214. if (loadingScreen != null && overlay != null)
  215. {
  216. loadingScreenOverlayHandle = GetOverlayHandle("loadingScreen", loadingScreenTransform != null ? loadingScreenTransform : transform, loadingScreenWidthInMeters);
  217. if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  218. {
  219. var texture = new Texture_t();
  220. texture.handle = loadingScreen.GetNativeTexturePtr();
  221. texture.eType = SteamVR.instance.textureType;
  222. texture.eColorSpace = EColorSpace.Auto;
  223. overlay.SetOverlayTexture(loadingScreenOverlayHandle, ref texture);
  224. }
  225. }
  226. bool fadedForeground = false;
  227. // Fade out to compositor
  228. SteamVR_Events.LoadingFadeOut.Send(fadeOutTime);
  229. // Optionally set a skybox to use as a backdrop in the compositor.
  230. var compositor = OpenVR.Compositor;
  231. if (compositor != null)
  232. {
  233. if (front != null)
  234. {
  235. SteamVR_Skybox.SetOverride(front, back, left, right, top, bottom);
  236. // Explicitly fade to the compositor since loading will cause us to stop rendering.
  237. compositor.FadeGrid(fadeOutTime, true);
  238. yield return new WaitForSeconds(fadeOutTime);
  239. }
  240. else if (backgroundColor != Color.clear)
  241. {
  242. // Otherwise, use the specified background color.
  243. if (showGrid)
  244. {
  245. // Set compositor background color immediately, and start fading to it.
  246. compositor.FadeToColor(0.0f, backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a, true);
  247. compositor.FadeGrid(fadeOutTime, true);
  248. yield return new WaitForSeconds(fadeOutTime);
  249. }
  250. else
  251. {
  252. // Fade the foreground color in (which will blend on top of the scene), and then cut to the compositor.
  253. compositor.FadeToColor(fadeOutTime, backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a, false);
  254. yield return new WaitForSeconds(fadeOutTime + 0.1f);
  255. compositor.FadeGrid(0.0f, true);
  256. fadedForeground = true;
  257. }
  258. }
  259. }
  260. // Now that we're fully faded out, we can stop submitting frames to the compositor.
  261. SteamVR_Render.pauseRendering = true;
  262. // Continue waiting for the overlays to fully fade in before continuing.
  263. while (alpha < 1.0f)
  264. yield return null;
  265. // Keep us from getting destroyed when loading the new level, otherwise this coroutine will get stopped prematurely.
  266. transform.parent = null;
  267. DontDestroyOnLoad(gameObject);
  268. if (!string.IsNullOrEmpty(internalProcessPath))
  269. {
  270. Debug.Log("Launching external application...");
  271. var applications = OpenVR.Applications;
  272. if (applications == null)
  273. {
  274. Debug.Log("Failed to get OpenVR.Applications interface!");
  275. }
  276. else
  277. {
  278. var workingDirectory = Directory.GetCurrentDirectory();
  279. var fullPath = Path.Combine(workingDirectory, internalProcessPath);
  280. Debug.Log("LaunchingInternalProcess");
  281. Debug.Log("ExternalAppPath = " + internalProcessPath);
  282. Debug.Log("FullPath = " + fullPath);
  283. Debug.Log("ExternalAppArgs = " + internalProcessArgs);
  284. Debug.Log("WorkingDirectory = " + workingDirectory);
  285. var error = applications.LaunchInternalProcess(fullPath, internalProcessArgs, workingDirectory);
  286. Debug.Log("LaunchInternalProcessError: " + error);
  287. #if UNITY_EDITOR
  288. UnityEditor.EditorApplication.isPlaying = false;
  289. #elif !UNITY_METRO
  290. System.Diagnostics.Process.GetCurrentProcess().Kill();
  291. #endif
  292. }
  293. }
  294. else
  295. {
  296. var mode = loadAdditive ? UnityEngine.SceneManagement.LoadSceneMode.Additive : UnityEngine.SceneManagement.LoadSceneMode.Single;
  297. if (loadAsync)
  298. {
  299. Application.backgroundLoadingPriority = ThreadPriority.Low;
  300. async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(levelName, mode);
  301. // Performing this in a while loop instead seems to help smooth things out.
  302. //yield return async;
  303. while (!async.isDone)
  304. {
  305. yield return null;
  306. }
  307. }
  308. else
  309. {
  310. UnityEngine.SceneManagement.SceneManager.LoadScene(levelName, mode);
  311. }
  312. }
  313. yield return null;
  314. System.GC.Collect();
  315. yield return null;
  316. Shader.WarmupAllShaders();
  317. // Optionally wait a short period of time after loading everything back in, but before we start rendering again
  318. // in order to give everything a change to settle down to avoid any hitching at the start of the new level.
  319. yield return new WaitForSeconds(postLoadSettleTime);
  320. SteamVR_Render.pauseRendering = false;
  321. // Fade out loading screen.
  322. if (loadingScreenFadeOutTime > 0.0f)
  323. {
  324. fadeRate = -1.0f / loadingScreenFadeOutTime;
  325. }
  326. else
  327. {
  328. alpha = 0.0f;
  329. }
  330. // Fade out to compositor
  331. SteamVR_Events.LoadingFadeIn.Send(fadeInTime);
  332. // Refresh compositor reference since loading scenes might have invalidated it.
  333. compositor = OpenVR.Compositor;
  334. if (compositor != null)
  335. {
  336. // Fade out foreground color if necessary.
  337. if (fadedForeground)
  338. {
  339. compositor.FadeGrid(0.0f, false);
  340. compositor.FadeToColor(fadeInTime, 0.0f, 0.0f, 0.0f, 0.0f, false);
  341. yield return new WaitForSeconds(fadeInTime);
  342. }
  343. else
  344. {
  345. // Fade scene back in, and reset skybox once no longer visible.
  346. compositor.FadeGrid(fadeInTime, false);
  347. yield return new WaitForSeconds(fadeInTime);
  348. if (front != null)
  349. {
  350. SteamVR_Skybox.ClearOverride();
  351. }
  352. }
  353. }
  354. // Finally, stick around long enough for our overlays to fully fade out.
  355. while (alpha > 0.0f)
  356. yield return null;
  357. if (overlay != null)
  358. {
  359. if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  360. overlay.HideOverlay(progressBarOverlayHandle);
  361. if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  362. overlay.HideOverlay(loadingScreenOverlayHandle);
  363. }
  364. Destroy(gameObject);
  365. _active = null;
  366. SteamVR_Events.Loading.Send(false);
  367. }
  368. // Helper to create (or reuse if possible) each of our different overlay types.
  369. ulong GetOverlayHandle(string overlayName, Transform transform, float widthInMeters = 1.0f)
  370. {
  371. ulong handle = OpenVR.k_ulOverlayHandleInvalid;
  372. var overlay = OpenVR.Overlay;
  373. if (overlay == null)
  374. return handle;
  375. var key = SteamVR_Overlay.key + "." + overlayName;
  376. var error = overlay.FindOverlay(key, ref handle);
  377. if (error != EVROverlayError.None)
  378. error = overlay.CreateOverlay(key, overlayName, ref handle);
  379. if (error == EVROverlayError.None)
  380. {
  381. overlay.ShowOverlay(handle);
  382. overlay.SetOverlayAlpha(handle, alpha);
  383. overlay.SetOverlayWidthInMeters(handle, widthInMeters);
  384. // D3D textures are upside-down in Unity to match OpenGL.
  385. if (SteamVR.instance.textureType == ETextureType.DirectX)
  386. {
  387. var textureBounds = new VRTextureBounds_t();
  388. textureBounds.uMin = 0;
  389. textureBounds.vMin = 1;
  390. textureBounds.uMax = 1;
  391. textureBounds.vMax = 0;
  392. overlay.SetOverlayTextureBounds(handle, ref textureBounds);
  393. }
  394. // Convert from world space to tracking space using the top-most camera.
  395. var vrcam = (loadingScreenDistance == 0.0f) ? SteamVR_Render.Top() : null;
  396. if (vrcam != null && vrcam.origin != null)
  397. {
  398. var offset = new SteamVR_Utils.RigidTransform(vrcam.origin, transform);
  399. offset.pos.x /= vrcam.origin.localScale.x;
  400. offset.pos.y /= vrcam.origin.localScale.y;
  401. offset.pos.z /= vrcam.origin.localScale.z;
  402. var t = offset.ToHmdMatrix34();
  403. overlay.SetOverlayTransformAbsolute(handle, SteamVR_Render.instance.trackingSpace, ref t);
  404. }
  405. else
  406. {
  407. var t = new SteamVR_Utils.RigidTransform(transform).ToHmdMatrix34();
  408. overlay.SetOverlayTransformAbsolute(handle, SteamVR_Render.instance.trackingSpace, ref t);
  409. }
  410. }
  411. return handle;
  412. }
  413. }