SteamVR_LoadLevel.cs 20 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. namespace Valve.VR
  11. {
  12. public class SteamVR_LoadLevel : MonoBehaviour
  13. {
  14. private static SteamVR_LoadLevel _active = null;
  15. public static bool loading { get { return _active != null; } }
  16. public static float progress
  17. {
  18. get { return (_active != null && _active.async != null) ? _active.async.progress : 0.0f; }
  19. }
  20. public static Texture progressTexture
  21. {
  22. get { return (_active != null) ? _active.renderTexture : null; }
  23. }
  24. // Name of level to load.
  25. public string levelName;
  26. // Name of internal process to launch (instead of levelName).
  27. public string internalProcessPath;
  28. // The command-line args for the internal process to launch.
  29. public string internalProcessArgs;
  30. // If true, call LoadLevelAdditiveAsync instead of LoadLevelAsync.
  31. public bool loadAdditive;
  32. // Async load causes crashes in some apps.
  33. public bool loadAsync = true;
  34. // Optional logo texture.
  35. public Texture loadingScreen;
  36. // Optional progress bar textures.
  37. public Texture progressBarEmpty, progressBarFull;
  38. // Sizes of overlays.
  39. public float loadingScreenWidthInMeters = 6.0f;
  40. public float progressBarWidthInMeters = 3.0f;
  41. // If specified, the loading screen will be positioned in the player's view this far away.
  42. public float loadingScreenDistance = 0.0f;
  43. // Optional overrides for where to display loading screen and progress bar overlays.
  44. // Otherwise defaults to using this object's transform.
  45. public Transform loadingScreenTransform, progressBarTransform;
  46. // Optional skybox override textures.
  47. public Texture front, back, left, right, top, bottom;
  48. // Colors to use when dropping to the compositor between levels if no skybox is set.
  49. public Color backgroundColor = Color.black;
  50. // If false, the background color above gets applied as the foreground color in the compositor.
  51. // This does not have any effect when using a skybox instead.
  52. public bool showGrid = false;
  53. // Time to fade from current scene to the compositor and back.
  54. public float fadeOutTime = 0.5f;
  55. public float fadeInTime = 0.5f;
  56. // Additional time to wait after finished loading before we start fading the new scene back in.
  57. // This is to cover up any initial hitching that takes place right at the start of levels.
  58. // Most scenes should hopefully not require this.
  59. public float postLoadSettleTime = 0.0f;
  60. // Time to fade loading screen in and out (also used for progress bar).
  61. public float loadingScreenFadeInTime = 1.0f;
  62. public float loadingScreenFadeOutTime = 0.25f;
  63. float fadeRate = 1.0f;
  64. float alpha = 0.0f;
  65. AsyncOperation async; // used to track level load progress
  66. RenderTexture renderTexture; // used to render progress bar
  67. ulong loadingScreenOverlayHandle = OpenVR.k_ulOverlayHandleInvalid;
  68. ulong progressBarOverlayHandle = OpenVR.k_ulOverlayHandleInvalid;
  69. public bool autoTriggerOnEnable = false;
  70. void OnEnable()
  71. {
  72. if (autoTriggerOnEnable)
  73. Trigger();
  74. }
  75. public void Trigger()
  76. {
  77. if (!loading && !string.IsNullOrEmpty(levelName))
  78. StartCoroutine(LoadLevel());
  79. }
  80. // Helper function to quickly and simply load a level from script.
  81. public static void Begin(string levelName,
  82. bool showGrid = false, float fadeOutTime = 0.5f,
  83. float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f)
  84. {
  85. var loader = new GameObject("loader").AddComponent<SteamVR_LoadLevel>();
  86. loader.levelName = levelName;
  87. loader.showGrid = showGrid;
  88. loader.fadeOutTime = fadeOutTime;
  89. loader.backgroundColor = new Color(r, g, b, a);
  90. loader.Trigger();
  91. }
  92. // Updates progress bar.
  93. void OnGUI()
  94. {
  95. if (_active != this)
  96. return;
  97. // Optionally create an overlay for our progress bar to use, separate from the loading screen.
  98. if (progressBarEmpty != null && progressBarFull != null)
  99. {
  100. if (progressBarOverlayHandle == OpenVR.k_ulOverlayHandleInvalid)
  101. progressBarOverlayHandle = GetOverlayHandle("progressBar", progressBarTransform != null ? progressBarTransform : transform, progressBarWidthInMeters);
  102. if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  103. {
  104. var progress = (async != null) ? async.progress : 0.0f;
  105. // Use the full bar size for everything.
  106. var w = progressBarFull.width;
  107. var h = progressBarFull.height;
  108. // Create a separate render texture so we can composite the full image on top of the empty one.
  109. if (renderTexture == null)
  110. {
  111. renderTexture = new RenderTexture(w, h, 0);
  112. renderTexture.Create();
  113. }
  114. var prevActive = RenderTexture.active;
  115. RenderTexture.active = renderTexture;
  116. if (Event.current.type == EventType.Repaint)
  117. GL.Clear(false, true, Color.clear);
  118. GUILayout.BeginArea(new Rect(0, 0, w, h));
  119. GUI.DrawTexture(new Rect(0, 0, w, h), progressBarEmpty);
  120. // Reveal the full bar texture based on progress.
  121. GUI.DrawTextureWithTexCoords(new Rect(0, 0, progress * w, h), progressBarFull, new Rect(0.0f, 0.0f, progress, 1.0f));
  122. GUILayout.EndArea();
  123. RenderTexture.active = prevActive;
  124. // Texture needs to be set every frame after it is updated since SteamVR makes a copy internally to a shared texture.
  125. var overlay = OpenVR.Overlay;
  126. if (overlay != null)
  127. {
  128. var texture = new Texture_t();
  129. texture.handle = renderTexture.GetNativeTexturePtr();
  130. texture.eType = SteamVR.instance.textureType;
  131. texture.eColorSpace = EColorSpace.Auto;
  132. overlay.SetOverlayTexture(progressBarOverlayHandle, ref texture);
  133. }
  134. }
  135. }
  136. #if false
  137. // Draw loading screen and progress bar to 2d companion window as well.
  138. if (loadingScreen != null)
  139. {
  140. var screenAspect = (float)Screen.width / Screen.height;
  141. var textureAspect = (float)loadingScreen.width / loadingScreen.height;
  142. float w, h;
  143. if (screenAspect < textureAspect)
  144. {
  145. // Clamp horizontally
  146. w = Screen.width * 0.9f;
  147. h = w / textureAspect;
  148. }
  149. else
  150. {
  151. // Clamp vertically
  152. h = Screen.height * 0.9f;
  153. w = h * textureAspect;
  154. }
  155. GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
  156. var x = Screen.width / 2 - w / 2;
  157. var y = Screen.height / 2 - h / 2;
  158. GUI.DrawTexture(new Rect(x, y, w, h), loadingScreen);
  159. GUILayout.EndArea();
  160. }
  161. if (renderTexture != null)
  162. {
  163. var x = Screen.width / 2 - renderTexture.width / 2;
  164. var y = Screen.height * 0.9f - renderTexture.height;
  165. GUI.DrawTexture(new Rect(x, y, renderTexture.width, renderTexture.height), renderTexture);
  166. }
  167. #endif
  168. }
  169. // Fade our overlays in/out over time.
  170. void Update()
  171. {
  172. if (_active != this)
  173. return;
  174. alpha = Mathf.Clamp01(alpha + fadeRate * Time.deltaTime);
  175. var overlay = OpenVR.Overlay;
  176. if (overlay != null)
  177. {
  178. if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  179. overlay.SetOverlayAlpha(loadingScreenOverlayHandle, alpha);
  180. if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
  181. overlay.SetOverlayAlpha(progressBarOverlayHandle, alpha);
  182. }
  183. }
  184. // Corourtine to handle all the steps across loading boundaries.
  185. IEnumerator LoadLevel()
  186. {
  187. // Optionally rotate loading screen transform around the camera into view.
  188. // We assume here that the loading screen is already facing toward the origin,
  189. // and that the progress bar transform (if any) is a child and will follow along.
  190. if (loadingScreen != null && loadingScreenDistance > 0.0f)
  191. {
  192. Transform hmd = this.transform;
  193. if (Camera.main != null)
  194. hmd = Camera.main.transform;
  195. Quaternion rot = Quaternion.Euler(0.0f, hmd.eulerAngles.y, 0.0f);
  196. Vector3 pos = hmd.position + (rot * new Vector3(0.0f, 0.0f, loadingScreenDistance));
  197. var t = loadingScreenTransform != null ? loadingScreenTransform : transform;
  198. t.position = pos;
  199. t.rotation = 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("<b>[SteamVR]</b> Launching external application...");
  271. var applications = OpenVR.Applications;
  272. if (applications == null)
  273. {
  274. Debug.Log("<b>[SteamVR]</b> 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("<b>[SteamVR]</b> LaunchingInternalProcess");
  281. Debug.Log("<b>[SteamVR]</b> ExternalAppPath = " + internalProcessPath);
  282. Debug.Log("<b>[SteamVR]</b> FullPath = " + fullPath);
  283. Debug.Log("<b>[SteamVR]</b> ExternalAppArgs = " + internalProcessArgs);
  284. Debug.Log("<b>[SteamVR]</b> WorkingDirectory = " + workingDirectory);
  285. var error = applications.LaunchInternalProcess(fullPath, internalProcessArgs, workingDirectory);
  286. Debug.Log("<b>[SteamVR]</b> 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.settings.trackingSpace, ref t);
  404. }
  405. else
  406. {
  407. var t = new SteamVR_Utils.RigidTransform(transform).ToHmdMatrix34();
  408. overlay.SetOverlayTransformAbsolute(handle, SteamVR.settings.trackingSpace, ref t);
  409. }
  410. }
  411. return handle;
  412. }
  413. }
  414. }