webXRSessionManager.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { Logger } from "../../Misc/logger";
  2. import { Observable } from "../../Misc/observable";
  3. import { Nullable } from "../../types";
  4. import { IDisposable, Scene } from "../../scene";
  5. import { InternalTexture } from "../../Materials/Textures/internalTexture";
  6. import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
  7. /**
  8. * Manages an XRSession to work with Babylon's engine
  9. * @see https://doc.babylonjs.com/how_to/webxr
  10. */
  11. export class WebXRSessionManager implements IDisposable {
  12. /**
  13. * Fires every time a new xrFrame arrives which can be used to update the camera
  14. */
  15. public onXRFrameObservable: Observable<any> = new Observable<any>();
  16. /**
  17. * Fires when the xr session is ended either by the device or manually done
  18. */
  19. public onXRSessionEnded: Observable<any> = new Observable<any>();
  20. /**
  21. * Underlying xr session
  22. */
  23. public session: XRSession;
  24. /**
  25. * Type of reference space used when creating the session
  26. */
  27. public referenceSpace: XRReferenceSpace;
  28. /** @hidden */
  29. public _sessionRenderTargetTexture: Nullable<RenderTargetTexture> = null;
  30. /**
  31. * Current XR frame
  32. */
  33. public currentFrame: Nullable<XRFrame>;
  34. private _xrNavigator: any;
  35. private baseLayer: Nullable<XRWebGLLayer> = null;
  36. /**
  37. * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
  38. * @param scene The scene which the session should be created for
  39. */
  40. constructor(private scene: Scene) {
  41. }
  42. /**
  43. * Initializes the manager
  44. * After initialization enterXR can be called to start an XR session
  45. * @returns Promise which resolves after it is initialized
  46. */
  47. public initializeAsync(): Promise<void> {
  48. Logger.Warn("The WebXR APIs are still under development and are subject to change in the future.");
  49. // Check if the browser supports webXR
  50. this._xrNavigator = navigator;
  51. if (!this._xrNavigator.xr) {
  52. return Promise.reject("webXR not supported by this browser");
  53. }
  54. return Promise.resolve();
  55. }
  56. /**
  57. * Initializes an xr session
  58. * @param xrSessionMode mode to initialize
  59. * @returns a promise which will resolve once the session has been initialized
  60. */
  61. public initializeSessionAsync(xrSessionMode: XRSessionMode) {
  62. return this._xrNavigator.xr.requestSession(xrSessionMode).then((session: XRSession) => {
  63. this.session = session;
  64. // handle when the session is ended (By calling session.end or device ends its own session eg. pressing home button on phone)
  65. this.session.addEventListener("end", () => {
  66. // Remove render target texture and notify frame obervers
  67. this._sessionRenderTargetTexture = null;
  68. // Restore frame buffer to avoid clear on xr framebuffer after session end
  69. this.scene.getEngine().restoreDefaultFramebuffer();
  70. // Need to restart render loop as after the session is ended the last request for new frame will never call callback
  71. this.scene.getEngine().customAnimationFrameRequester = null;
  72. this.onXRSessionEnded.notifyObservers(null);
  73. this.scene.getEngine()._renderLoop();
  74. }, { once: true });
  75. });
  76. }
  77. /**
  78. * Sets the reference space on the xr session
  79. * @param referenceSpace space to set
  80. * @returns a promise that will resolve once the reference space has been set
  81. */
  82. public setReferenceSpaceAsync(referenceSpace: XRReferenceSpaceType) {
  83. return this.session.requestReferenceSpace(referenceSpace).then((referenceSpace: XRReferenceSpace) => {
  84. this.referenceSpace = referenceSpace;
  85. });
  86. }
  87. /**
  88. * Updates the render state of the session
  89. * @param state state to set
  90. * @returns a promise that resolves once the render state has been updated
  91. */
  92. public updateRenderStateAsync(state: any) {
  93. if (state.baseLayer) {
  94. this.baseLayer = state.baseLayer;
  95. }
  96. return this.session.updateRenderState(state);
  97. }
  98. /**
  99. * Starts rendering to the xr layer
  100. * @returns a promise that will resolve once rendering has started
  101. */
  102. public startRenderingToXRAsync() {
  103. // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
  104. this.scene.getEngine().customAnimationFrameRequester = {
  105. requestAnimationFrame: this.session.requestAnimationFrame.bind(this.session),
  106. renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
  107. // Store the XR frame in the manager to be consumed by the XR camera to update pose
  108. this.currentFrame = xrFrame;
  109. this.onXRFrameObservable.notifyObservers(null);
  110. this.scene.getEngine()._renderLoop();
  111. }
  112. };
  113. // Create render target texture from xr's webgl render target
  114. this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this.session, this.scene, this.baseLayer!);
  115. // Stop window's animation frame and trigger sessions animation frame
  116. window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
  117. this.scene.getEngine()._renderLoop();
  118. return Promise.resolve();
  119. }
  120. /**
  121. * Stops the xrSession and restores the renderloop
  122. * @returns Promise which resolves after it exits XR
  123. */
  124. public exitXRAsync() {
  125. if (this.session) {
  126. this.session.end();
  127. }
  128. return new Promise(() => {});
  129. }
  130. /**
  131. * Checks if a session would be supported for the creation options specified
  132. * @param sessionMode session mode to check if supported eg. immersive-vr
  133. * @returns true if supported
  134. */
  135. public supportsSessionAsync(sessionMode: XRSessionMode) {
  136. if (!(navigator as any).xr || !(navigator as any).xr.supportsSession) {
  137. return Promise.resolve(false);
  138. }else {
  139. return (navigator as any).xr.supportsSession(sessionMode).then(() => {
  140. return Promise.resolve(true);
  141. }).catch((e: any) => {
  142. Logger.Warn(e);
  143. return Promise.resolve(false);
  144. });
  145. }
  146. }
  147. /**
  148. * @hidden
  149. * Converts the render layer of xrSession to a render target
  150. * @param session session to create render target for
  151. * @param scene scene the new render target should be created for
  152. */
  153. public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
  154. if (!baseLayer) {
  155. throw "no layer";
  156. }
  157. // Create internal texture
  158. var internalTexture = new InternalTexture(scene.getEngine(), InternalTexture.DATASOURCE_UNKNOWN, true);
  159. internalTexture.width = baseLayer.framebufferWidth;
  160. internalTexture.height = baseLayer.framebufferHeight;
  161. internalTexture._framebuffer = baseLayer.framebuffer;
  162. // Create render target texture from the internal texture
  163. var renderTargetTexture = new RenderTargetTexture("XR renderTargetTexture", { width: internalTexture.width, height: internalTexture.height }, scene, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
  164. renderTargetTexture._texture = internalTexture;
  165. return renderTargetTexture;
  166. }
  167. /**
  168. * Disposes of the session manager
  169. */
  170. public dispose() {
  171. this.onXRFrameObservable.clear();
  172. this.onXRSessionEnded.clear();
  173. }
  174. }