webXRManagedOutputCanvas.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import { Nullable } from "../types";
  2. import { ThinEngine } from "../Engines/thinEngine";
  3. import { WebXRRenderTarget } from "./webXRTypes";
  4. import { WebXRSessionManager } from "./webXRSessionManager";
  5. import { Observable } from "../Misc/observable";
  6. import { Tools } from "../Misc/tools";
  7. /**
  8. * COnfiguration object for WebXR output canvas
  9. */
  10. export class WebXRManagedOutputCanvasOptions {
  11. /**
  12. * An optional canvas in case you wish to create it yourself and provide it here.
  13. * If not provided, a new canvas will be created
  14. */
  15. public canvasElement?: HTMLCanvasElement;
  16. /**
  17. * Options for this XR Layer output
  18. */
  19. public canvasOptions?: XRWebGLLayerInit;
  20. /**
  21. * CSS styling for a newly created canvas (if not provided)
  22. */
  23. public newCanvasCssStyle?: string;
  24. /**
  25. * Get the default values of the configuration object
  26. * @param engine defines the engine to use (can be null)
  27. * @returns default values of this configuration object
  28. */
  29. public static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions {
  30. const defaults = new WebXRManagedOutputCanvasOptions();
  31. defaults.canvasOptions = {
  32. antialias: true,
  33. depth: true,
  34. stencil: engine ? engine.isStencilEnable : true,
  35. alpha: true,
  36. multiview: false,
  37. framebufferScaleFactor: 1,
  38. };
  39. defaults.newCanvasCssStyle = "position:absolute; bottom:0px;right:0px;z-index:10;width:90%;height:100%;background-color: #000000;";
  40. return defaults;
  41. }
  42. }
  43. /**
  44. * Creates a canvas that is added/removed from the webpage when entering/exiting XR
  45. */
  46. export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
  47. private _canvas: Nullable<HTMLCanvasElement> = null;
  48. private _engine: ThinEngine;
  49. private _originalCanvasSize: {
  50. width: number;
  51. height: number;
  52. };
  53. /**
  54. * Rendering context of the canvas which can be used to display/mirror xr content
  55. */
  56. public canvasContext: WebGLRenderingContext;
  57. /**
  58. * xr layer for the canvas
  59. */
  60. public xrLayer: Nullable<XRWebGLLayer> = null;
  61. /**
  62. * Obseervers registered here will be triggered when the xr layer was initialized
  63. */
  64. public onXRLayerInitObservable: Observable<XRWebGLLayer> = new Observable();
  65. /**
  66. * Initializes the canvas to be added/removed upon entering/exiting xr
  67. * @param _xrSessionManager The XR Session manager
  68. * @param _options optional configuration for this canvas output. defaults will be used if not provided
  69. */
  70. constructor(_xrSessionManager: WebXRSessionManager, private _options: WebXRManagedOutputCanvasOptions = WebXRManagedOutputCanvasOptions.GetDefaults()) {
  71. this._engine = _xrSessionManager.scene.getEngine();
  72. if (!_options.canvasElement) {
  73. const canvas = document.createElement("canvas");
  74. canvas.style.cssText = this._options.newCanvasCssStyle || "position:absolute; bottom:0px;right:0px;";
  75. this._setManagedOutputCanvas(canvas);
  76. } else {
  77. this._setManagedOutputCanvas(_options.canvasElement);
  78. }
  79. _xrSessionManager.onXRSessionInit.add(() => {
  80. this._addCanvas();
  81. });
  82. _xrSessionManager.onXRSessionEnded.add(() => {
  83. this._removeCanvas();
  84. });
  85. }
  86. /**
  87. * Disposes of the object
  88. */
  89. public dispose() {
  90. this._removeCanvas();
  91. this._setManagedOutputCanvas(null);
  92. }
  93. /**
  94. * Initializes the xr layer for the session
  95. * @param xrSession xr session
  96. * @returns a promise that will resolve once the XR Layer has been created
  97. */
  98. public initializeXRLayerAsync(xrSession: XRSession): Promise<XRWebGLLayer> {
  99. const createLayer = () => {
  100. const layer = new XRWebGLLayer(xrSession, this.canvasContext, this._options.canvasOptions);
  101. this.onXRLayerInitObservable.notifyObservers(layer);
  102. return layer;
  103. };
  104. // support canvases without makeXRCompatible
  105. if (!(this.canvasContext as any).makeXRCompatible) {
  106. this.xrLayer = createLayer();
  107. return Promise.resolve(this.xrLayer);
  108. }
  109. return (this.canvasContext as any)
  110. .makeXRCompatible()
  111. .then(
  112. // catch any error and continue. When using the emulator is throws this error for no apparent reason.
  113. () => {},
  114. () => {
  115. // log the error, continue nonetheless!
  116. Tools.Warn("Error executing makeXRCompatible. This does not mean that the session will work incorrectly.");
  117. }
  118. )
  119. .then(() => {
  120. this.xrLayer = createLayer();
  121. return this.xrLayer;
  122. });
  123. }
  124. private _addCanvas() {
  125. if (this._canvas && this._canvas !== this._engine.getRenderingCanvas()) {
  126. document.body.appendChild(this._canvas);
  127. }
  128. if (this.xrLayer) {
  129. this._setCanvasSize(true);
  130. } else {
  131. this.onXRLayerInitObservable.addOnce((layer) => {
  132. this._setCanvasSize(true, layer);
  133. });
  134. }
  135. }
  136. private _removeCanvas() {
  137. if (this._canvas && document.body.contains(this._canvas) && this._canvas !== this._engine.getRenderingCanvas()) {
  138. document.body.removeChild(this._canvas);
  139. }
  140. this._setCanvasSize(false);
  141. }
  142. private _setCanvasSize(init: boolean = true, xrLayer = this.xrLayer) {
  143. if (!this._canvas) {
  144. return;
  145. }
  146. if (init) {
  147. if (xrLayer) {
  148. if (this._canvas !== this._engine.getRenderingCanvas()) {
  149. this._canvas.style.width = xrLayer.framebufferWidth + "px";
  150. this._canvas.style.height = xrLayer.framebufferHeight + "px";
  151. } else {
  152. this._engine.setSize(xrLayer.framebufferWidth, xrLayer.framebufferHeight);
  153. }
  154. }
  155. } else {
  156. if (this._originalCanvasSize) {
  157. if (this._canvas !== this._engine.getRenderingCanvas()) {
  158. this._canvas.style.width = this._originalCanvasSize.width + "px";
  159. this._canvas.style.height = this._originalCanvasSize.height + "px";
  160. } else {
  161. this._engine.setSize(this._originalCanvasSize.width, this._originalCanvasSize.height);
  162. }
  163. }
  164. }
  165. }
  166. private _setManagedOutputCanvas(canvas: Nullable<HTMLCanvasElement>) {
  167. this._removeCanvas();
  168. if (!canvas) {
  169. this._canvas = null;
  170. (this.canvasContext as any) = null;
  171. } else {
  172. this._originalCanvasSize = {
  173. width: canvas.offsetWidth,
  174. height: canvas.offsetHeight,
  175. };
  176. this._canvas = canvas;
  177. this.canvasContext = <any>this._canvas.getContext("webgl2");
  178. if (!this.canvasContext) {
  179. this.canvasContext = <any>this._canvas.getContext("webgl");
  180. }
  181. }
  182. }
  183. }