IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
SteamVR_Render.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: Handles rendering of all SteamVR_Cameras
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 using System.Collections;
9 using Valve.VR;
10 
11 public class SteamVR_Render : MonoBehaviour
12 {
13  public bool pauseGameWhenDashboardIsVisible = true;
14  public bool lockPhysicsUpdateRateToRenderFrequency = true;
15 
16  public SteamVR_ExternalCamera externalCamera;
17  public string externalCameraConfigPath = "externalcamera.cfg";
18 
19  public ETrackingUniverseOrigin trackingSpace = ETrackingUniverseOrigin.TrackingUniverseStanding;
20 
21  static public EVREye eye { get; private set; }
22 
23  static private SteamVR_Render _instance;
24  static public SteamVR_Render instance
25  {
26  get
27  {
28  if (_instance == null)
29  {
30  _instance = GameObject.FindObjectOfType<SteamVR_Render>();
31 
32  if (_instance == null)
33  _instance = new GameObject("[SteamVR]").AddComponent<SteamVR_Render>();
34  }
35  return _instance;
36  }
37  }
38 
39  void OnDestroy()
40  {
41  _instance = null;
42  }
43 
44  static private bool isQuitting;
45  void OnApplicationQuit()
46  {
47  isQuitting = true;
48  SteamVR.SafeDispose();
49  }
50 
51  static public void Add(SteamVR_Camera vrcam)
52  {
53  if (!isQuitting)
54  instance.AddInternal(vrcam);
55  }
56 
57  static public void Remove(SteamVR_Camera vrcam)
58  {
59  if (!isQuitting && _instance != null)
60  instance.RemoveInternal(vrcam);
61  }
62 
63  static public SteamVR_Camera Top()
64  {
65  if (!isQuitting)
66  return instance.TopInternal();
67 
68  return null;
69  }
70 
71  private SteamVR_Camera[] cameras = new SteamVR_Camera[0];
72 
73  void AddInternal(SteamVR_Camera vrcam)
74  {
75  var camera = vrcam.GetComponent<Camera>();
76  var length = cameras.Length;
77  var sorted = new SteamVR_Camera[length + 1];
78  int insert = 0;
79  for (int i = 0; i < length; i++)
80  {
81  var c = cameras[i].GetComponent<Camera>();
82  if (i == insert && c.depth > camera.depth)
83  sorted[insert++] = vrcam;
84 
85  sorted[insert++] = cameras[i];
86  }
87  if (insert == length)
88  sorted[insert] = vrcam;
89 
90  cameras = sorted;
91  }
92 
93  void RemoveInternal(SteamVR_Camera vrcam)
94  {
95  var length = cameras.Length;
96  int count = 0;
97  for (int i = 0; i < length; i++)
98  {
99  var c = cameras[i];
100  if (c == vrcam)
101  ++count;
102  }
103  if (count == 0)
104  return;
105 
106  var sorted = new SteamVR_Camera[length - count];
107  int insert = 0;
108  for (int i = 0; i < length; i++)
109  {
110  var c = cameras[i];
111  if (c != vrcam)
112  sorted[insert++] = c;
113  }
114 
115  cameras = sorted;
116  }
117 
118  SteamVR_Camera TopInternal()
119  {
120  if (cameras.Length > 0)
121  return cameras[cameras.Length - 1];
122 
123  return null;
124  }
125 
126  public TrackedDevicePose_t[] poses = new TrackedDevicePose_t[OpenVR.k_unMaxTrackedDeviceCount];
127  public TrackedDevicePose_t[] gamePoses = new TrackedDevicePose_t[0];
128 
129  static private bool _pauseRendering;
130  static public bool pauseRendering
131  {
132  get { return _pauseRendering; }
133  set
134  {
135  _pauseRendering = value;
136 
137  var compositor = OpenVR.Compositor;
138  if (compositor != null)
139  compositor.SuspendRendering(value);
140  }
141  }
142 
143  private WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
144 
145  private IEnumerator RenderLoop()
146  {
147  while (Application.isPlaying)
148  {
149  yield return waitForEndOfFrame;
150 
151  if (pauseRendering)
152  continue;
153 
154  var compositor = OpenVR.Compositor;
155  if (compositor != null)
156  {
157  if (!compositor.CanRenderScene())
158  continue;
159 
160  compositor.SetTrackingSpace(trackingSpace);
161  }
162 
163  var overlay = SteamVR_Overlay.instance;
164  if (overlay != null)
165  overlay.UpdateOverlay();
166 
167  RenderExternalCamera();
168  }
169  }
170 
171  void RenderExternalCamera()
172  {
173  if (externalCamera == null)
174  return;
175 
176  if (!externalCamera.gameObject.activeInHierarchy)
177  return;
178 
179  var frameSkip = (int)Mathf.Max(externalCamera.config.frameSkip, 0.0f);
180  if (Time.frameCount % (frameSkip + 1) != 0)
181  return;
182 
183  // Keep external camera relative to the most relevant vr camera.
184  externalCamera.AttachToCamera(TopInternal());
185 
186  externalCamera.RenderNear();
187  externalCamera.RenderFar();
188  }
189 
190  float sceneResolutionScale = 1.0f, timeScale = 1.0f;
191 
192  private void OnInputFocus(bool hasFocus)
193  {
194  if (hasFocus)
195  {
196  if (pauseGameWhenDashboardIsVisible)
197  {
198  Time.timeScale = timeScale;
199  }
200 
201  SteamVR_Camera.sceneResolutionScale = sceneResolutionScale;
202  }
203  else
204  {
205  if (pauseGameWhenDashboardIsVisible)
206  {
207  timeScale = Time.timeScale;
208  Time.timeScale = 0.0f;
209  }
210 
211  sceneResolutionScale = SteamVR_Camera.sceneResolutionScale;
212  SteamVR_Camera.sceneResolutionScale = 0.5f;
213  }
214  }
215 
216  void OnQuit(VREvent_t vrEvent)
217  {
218 #if UNITY_EDITOR
219  foreach (System.Reflection.Assembly a in System.AppDomain.CurrentDomain.GetAssemblies())
220  {
221  var t = a.GetType("UnityEditor.EditorApplication");
222  if (t != null)
223  {
224  t.GetProperty("isPlaying").SetValue(null, false, null);
225  break;
226  }
227  }
228 #else
229  Application.Quit();
230 #endif
231  }
232 
233  private string GetScreenshotFilename(uint screenshotHandle, EVRScreenshotPropertyFilenames screenshotPropertyFilename)
234  {
235  var error = EVRScreenshotError.None;
236  var capacity = OpenVR.Screenshots.GetScreenshotPropertyFilename(screenshotHandle, screenshotPropertyFilename, null, 0, ref error);
237  if (error != EVRScreenshotError.None && error != EVRScreenshotError.BufferTooSmall)
238  return null;
239  if (capacity > 1)
240  {
241  var result = new System.Text.StringBuilder((int)capacity);
242  OpenVR.Screenshots.GetScreenshotPropertyFilename(screenshotHandle, screenshotPropertyFilename, result, capacity, ref error);
243  if (error != EVRScreenshotError.None)
244  return null;
245  return result.ToString();
246  }
247  return null;
248  }
249 
250  private void OnRequestScreenshot(VREvent_t vrEvent)
251  {
252  var screenshotHandle = vrEvent.data.screenshot.handle;
253  var screenshotType = (EVRScreenshotType)vrEvent.data.screenshot.type;
254 
255  if (screenshotType == EVRScreenshotType.StereoPanorama)
256  {
257  string previewFilename = GetScreenshotFilename(screenshotHandle, EVRScreenshotPropertyFilenames.Preview);
258  string VRFilename = GetScreenshotFilename(screenshotHandle, EVRScreenshotPropertyFilenames.VR);
259 
260  if (previewFilename == null || VRFilename == null)
261  return;
262 
263  // Do the stereo panorama screenshot
264  // Figure out where the view is
265  GameObject screenshotPosition = new GameObject("screenshotPosition");
266  screenshotPosition.transform.position = SteamVR_Render.Top().transform.position;
267  screenshotPosition.transform.rotation = SteamVR_Render.Top().transform.rotation;
268  screenshotPosition.transform.localScale = SteamVR_Render.Top().transform.lossyScale;
269  SteamVR_Utils.TakeStereoScreenshot(screenshotHandle, screenshotPosition, 32, 0.064f, ref previewFilename, ref VRFilename);
270 
271  // and submit it
272  OpenVR.Screenshots.SubmitScreenshot(screenshotHandle, screenshotType, previewFilename, VRFilename);
273  }
274  }
275 
276  void OnEnable()
277  {
278  StartCoroutine(RenderLoop());
279  SteamVR_Events.InputFocus.Listen(OnInputFocus);
280  SteamVR_Events.System(EVREventType.VREvent_Quit).Listen(OnQuit);
281  SteamVR_Events.System(EVREventType.VREvent_RequestScreenshot).Listen(OnRequestScreenshot);
282 #if UNITY_2017_1_OR_NEWER
283  Application.onBeforeRender += OnBeforeRender;
284 #else
285  Camera.onPreCull += OnCameraPreCull;
286 #endif
287  var vr = SteamVR.instance;
288  if (vr == null)
289  {
290  enabled = false;
291  return;
292  }
293  var types = new EVRScreenshotType[] { EVRScreenshotType.StereoPanorama };
294  OpenVR.Screenshots.HookScreenshot(types);
295  }
296 
297  void OnDisable()
298  {
299  StopAllCoroutines();
300  SteamVR_Events.InputFocus.Remove(OnInputFocus);
301  SteamVR_Events.System(EVREventType.VREvent_Quit).Remove(OnQuit);
302  SteamVR_Events.System(EVREventType.VREvent_RequestScreenshot).Remove(OnRequestScreenshot);
303 #if UNITY_2017_1_OR_NEWER
304  Application.onBeforeRender -= OnBeforeRender;
305 #else
306  Camera.onPreCull -= OnCameraPreCull;
307 #endif
308  }
309 
310  void Awake()
311  {
312  if (externalCamera == null && System.IO.File.Exists(externalCameraConfigPath))
313  {
314  var prefab = Resources.Load<GameObject>("SteamVR_ExternalCamera");
315  var instance = Instantiate(prefab);
316  instance.gameObject.name = "External Camera";
317 
318  externalCamera = instance.transform.GetChild(0).GetComponent<SteamVR_ExternalCamera>();
319  externalCamera.configPath = externalCameraConfigPath;
320  externalCamera.ReadConfig();
321  }
322  }
323 
324  public void UpdatePoses()
325  {
326  var compositor = OpenVR.Compositor;
327  if (compositor != null)
328  {
329  compositor.GetLastPoses(poses, gamePoses);
330  SteamVR_Events.NewPoses.Send(poses);
331  SteamVR_Events.NewPosesApplied.Send();
332  }
333  }
334 
335 #if UNITY_2017_1_OR_NEWER
336  void OnBeforeRender() { UpdatePoses(); }
337 #else
338  void OnCameraPreCull(Camera cam)
339  {
340 #if !( UNITY_5_4 )
341  if (cam.cameraType != CameraType.VR)
342  return;
343 #endif
344  // Only update poses on the first camera per frame.
345  if (Time.frameCount != lastFrameCount)
346  {
347  lastFrameCount = Time.frameCount;
348  UpdatePoses();
349  }
350  }
351  static int lastFrameCount = -1;
352 #endif
353 
354  void Update()
355  {
356  // Force controller update in case no one else called this frame to ensure prevState gets updated.
357  SteamVR_Controller.Update();
358 
359  // Dispatch any OpenVR events.
360  var system = OpenVR.System;
361  if (system != null)
362  {
363  var vrEvent = new VREvent_t();
364  var size = (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VREvent_t));
365  for (int i = 0; i < 64; i++)
366  {
367  if (!system.PollNextEvent(ref vrEvent, size))
368  break;
369 
370  switch ((EVREventType)vrEvent.eventType)
371  {
372  case EVREventType.VREvent_InputFocusCaptured: // another app has taken focus (likely dashboard)
373  if (vrEvent.data.process.oldPid == 0)
374  {
375  SteamVR_Events.InputFocus.Send(false);
376  }
377  break;
378  case EVREventType.VREvent_InputFocusReleased: // that app has released input focus
379  if (vrEvent.data.process.pid == 0)
380  {
381  SteamVR_Events.InputFocus.Send(true);
382  }
383  break;
384  case EVREventType.VREvent_ShowRenderModels:
385  SteamVR_Events.HideRenderModels.Send(false);
386  break;
387  case EVREventType.VREvent_HideRenderModels:
388  SteamVR_Events.HideRenderModels.Send(true);
389  break;
390  default:
391  SteamVR_Events.System((EVREventType)vrEvent.eventType).Send(vrEvent);
392  break;
393  }
394  }
395  }
396 
397  // Ensure various settings to minimize latency.
398  Application.targetFrameRate = -1;
399  Application.runInBackground = true; // don't require companion window focus
400  QualitySettings.maxQueuedFrames = -1;
401  QualitySettings.vSyncCount = 0; // this applies to the companion window
402 
403  if (lockPhysicsUpdateRateToRenderFrequency && Time.timeScale > 0.0f)
404  {
405  var vr = SteamVR.instance;
406  if (vr != null)
407  {
408  var timing = new Compositor_FrameTiming();
409  timing.m_nSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(Compositor_FrameTiming));
410  vr.compositor.GetFrameTiming(ref timing, 0);
411 
412  Time.fixedDeltaTime = Time.timeScale / vr.hmd_DisplayFrequency;
413  }
414  }
415  }
416 }
417