WebXRLayers.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import { WebXRFeatureName, WebXRFeaturesManager } from "../webXRFeaturesManager.js";
  2. import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js";
  3. import { WebXRWebGLLayerWrapper } from "../webXRWebGLLayer.js";
  4. import { WebXRProjectionLayerWrapper, defaultXRProjectionLayerInit } from "./Layers/WebXRProjectionLayer.js";
  5. import { WebXRCompositionLayerRenderTargetTextureProvider, WebXRCompositionLayerWrapper } from "./Layers/WebXRCompositionLayer.js";
  6. import { Color4 } from "../../Maths/math.color.js";
  7. const defaultXRWebGLLayerInit = {};
  8. /**
  9. * Exposes the WebXR Layers API.
  10. */
  11. export class WebXRLayers extends WebXRAbstractFeature {
  12. constructor(_xrSessionManager, _options = {}) {
  13. super(_xrSessionManager);
  14. this._options = _options;
  15. /**
  16. * Already-created layers
  17. */
  18. this._existingLayers = [];
  19. this._isMultiviewEnabled = false;
  20. this._projectionLayerInitialized = false;
  21. this._compositionLayerTextureMapping = new WeakMap();
  22. this._layerToRTTProviderMapping = new WeakMap();
  23. this.xrNativeFeatureName = "layers";
  24. }
  25. /**
  26. * Attach this feature.
  27. * Will usually be called by the features manager.
  28. *
  29. * @returns true if successful.
  30. */
  31. attach() {
  32. if (!super.attach()) {
  33. return false;
  34. }
  35. const engine = this._xrSessionManager.scene.getEngine();
  36. this._glContext = engine._gl;
  37. this._xrWebGLBinding = new XRWebGLBinding(this._xrSessionManager.session, this._glContext);
  38. this._existingLayers.length = 0;
  39. const projectionLayerInit = { ...defaultXRProjectionLayerInit, ...this._options.projectionLayerInit };
  40. this._isMultiviewEnabled = this._options.preferMultiviewOnInit && engine.getCaps().multiview;
  41. this.createProjectionLayer(projectionLayerInit /*, projectionLayerMultiview*/);
  42. this._projectionLayerInitialized = true;
  43. return true;
  44. }
  45. detach() {
  46. if (!super.detach()) {
  47. return false;
  48. }
  49. this._existingLayers.forEach((layer) => {
  50. layer.dispose();
  51. });
  52. this._existingLayers.length = 0;
  53. this._projectionLayerInitialized = false;
  54. return true;
  55. }
  56. /**
  57. * Creates a new XRWebGLLayer.
  58. * @param params an object providing configuration options for the new XRWebGLLayer
  59. * @returns the XRWebGLLayer
  60. */
  61. createXRWebGLLayer(params = defaultXRWebGLLayerInit) {
  62. const layer = new XRWebGLLayer(this._xrSessionManager.session, this._glContext, params);
  63. return new WebXRWebGLLayerWrapper(layer);
  64. }
  65. _validateLayerInit(params, multiview = this._isMultiviewEnabled) {
  66. // check if we are in session
  67. if (!this._xrSessionManager.inXRSession) {
  68. throw new Error("Cannot create a layer outside of a WebXR session. Make sure the session has started before creating layers.");
  69. }
  70. if (multiview && params.textureType !== "texture-array") {
  71. throw new Error("Projection layers can only be made multiview if they use texture arrays. Set the textureType parameter to 'texture-array'.");
  72. }
  73. // TODO (rgerd): Support RTT's that are bound to sub-images in the texture array.
  74. if (!multiview && params.textureType === "texture-array") {
  75. throw new Error("We currently only support multiview rendering when the textureType parameter is set to 'texture-array'.");
  76. }
  77. }
  78. _extendXRLayerInit(params, multiview = this._isMultiviewEnabled) {
  79. if (multiview) {
  80. params.textureType = "texture-array";
  81. }
  82. return params;
  83. }
  84. /**
  85. * Creates a new XRProjectionLayer.
  86. * @param params an object providing configuration options for the new XRProjectionLayer.
  87. * @param multiview whether the projection layer should render with multiview. Will be tru automatically if the extension initialized with multiview.
  88. * @returns the projection layer
  89. */
  90. createProjectionLayer(params = defaultXRProjectionLayerInit, multiview = this._isMultiviewEnabled) {
  91. this._extendXRLayerInit(params, multiview);
  92. this._validateLayerInit(params, multiview);
  93. const projLayer = this._xrWebGLBinding.createProjectionLayer(params);
  94. const layer = new WebXRProjectionLayerWrapper(projLayer, multiview, this._xrWebGLBinding);
  95. this.addXRSessionLayer(layer);
  96. return layer;
  97. }
  98. /**
  99. * Note about making it private - this function will be exposed once I decide on a proper API to support all of the XR layers' options
  100. * @param options an object providing configuration options for the new XRQuadLayer.
  101. * @param babylonTexture the texture to display in the layer
  102. * @returns the quad layer
  103. */
  104. _createQuadLayer(options = { params: {} }, babylonTexture) {
  105. this._extendXRLayerInit(options.params, false);
  106. const width = this._existingLayers[0].layer.textureWidth;
  107. const height = this._existingLayers[0].layer.textureHeight;
  108. const populatedParams = {
  109. space: this._xrSessionManager.referenceSpace,
  110. viewPixelWidth: width,
  111. viewPixelHeight: height,
  112. clearOnAccess: true,
  113. ...options.params,
  114. };
  115. this._validateLayerInit(populatedParams, false);
  116. const quadLayer = this._xrWebGLBinding.createQuadLayer(populatedParams);
  117. quadLayer.width = this._isMultiviewEnabled ? 1 : 2;
  118. quadLayer.height = 1;
  119. // this wrapper is not really needed, but it's here for consistency
  120. const wrapper = new WebXRCompositionLayerWrapper(() => quadLayer.width, () => quadLayer.height, quadLayer, "XRQuadLayer", false, (sessionManager) => new WebXRCompositionLayerRenderTargetTextureProvider(sessionManager, this._xrWebGLBinding, wrapper));
  121. if (babylonTexture) {
  122. this._compositionLayerTextureMapping.set(quadLayer, babylonTexture);
  123. }
  124. const rtt = wrapper.createRenderTargetTextureProvider(this._xrSessionManager);
  125. this._layerToRTTProviderMapping.set(quadLayer, rtt);
  126. this.addXRSessionLayer(wrapper);
  127. return wrapper;
  128. }
  129. /**
  130. * @experimental
  131. * This will support full screen ADT when used with WebXR Layers. This API might change in the future.
  132. * Note that no interaction will be available with the ADT when using this method
  133. * @param texture the texture to display in the layer
  134. * @param options optional parameters for the layer
  135. * @returns a composition layer containing the texture
  136. */
  137. addFullscreenAdvancedDynamicTexture(texture, options = { distanceFromHeadset: 1.5 }) {
  138. const wrapper = this._createQuadLayer({
  139. params: {
  140. space: this._xrSessionManager.viewerReferenceSpace,
  141. textureType: "texture",
  142. layout: "mono",
  143. },
  144. }, texture);
  145. const layer = wrapper.layer;
  146. const distance = Math.max(0.1, options.distanceFromHeadset);
  147. const pos = { x: 0, y: 0, z: -distance };
  148. const orient = { x: 0, y: 0, z: 0, w: 1 };
  149. layer.transform = new XRRigidTransform(pos, orient);
  150. const rttProvider = this._layerToRTTProviderMapping.get(layer);
  151. if (!rttProvider) {
  152. throw new Error("Could not find the RTT provider for the layer");
  153. }
  154. const babylonLayer = this._xrSessionManager.scene.layers.find((babylonLayer) => {
  155. return babylonLayer.texture === texture;
  156. });
  157. if (!babylonLayer) {
  158. throw new Error("Could not find the babylon layer for the texture");
  159. }
  160. rttProvider.onRenderTargetTextureCreatedObservable.add((data) => {
  161. if (data.eye && data.eye === "right") {
  162. return;
  163. }
  164. data.texture.clearColor = new Color4(0, 0, 0, 0);
  165. babylonLayer.renderTargetTextures.push(data.texture);
  166. babylonLayer.renderOnlyInRenderTargetTextures = true;
  167. // for stereo (not for gui) it should be onBeforeCameraRenderObservable
  168. this._xrSessionManager.scene.onBeforeRenderObservable.add(() => {
  169. data.texture.render();
  170. });
  171. babylonLayer.renderTargetTextures.push(data.texture);
  172. babylonLayer.renderOnlyInRenderTargetTextures = true;
  173. // add it back when the session ends
  174. this._xrSessionManager.onXRSessionEnded.addOnce(() => {
  175. babylonLayer.renderTargetTextures.splice(babylonLayer.renderTargetTextures.indexOf(data.texture), 1);
  176. babylonLayer.renderOnlyInRenderTargetTextures = false;
  177. });
  178. });
  179. return wrapper;
  180. }
  181. /**
  182. * @experimental
  183. * This functions allows you to add a lens flare system to the XR scene.
  184. * Note - this will remove the lens flare system from the scene and add it to the XR scene.
  185. * This feature is experimental and might change in the future.
  186. * @param flareSystem the flare system to add
  187. * @returns a composition layer containing the flare system
  188. */
  189. _addLensFlareSystem(flareSystem) {
  190. const wrapper = this._createQuadLayer({
  191. params: {
  192. space: this._xrSessionManager.viewerReferenceSpace,
  193. textureType: "texture",
  194. layout: "mono",
  195. },
  196. });
  197. const layer = wrapper.layer;
  198. layer.width = 2;
  199. layer.height = 1;
  200. const distance = 10;
  201. const pos = { x: 0, y: 0, z: -distance };
  202. const orient = { x: 0, y: 0, z: 0, w: 1 };
  203. layer.transform = new XRRigidTransform(pos, orient);
  204. // get the rtt wrapper
  205. const rttProvider = this._layerToRTTProviderMapping.get(layer);
  206. if (!rttProvider) {
  207. throw new Error("Could not find the RTT provider for the layer");
  208. }
  209. // render the flare system to the rtt
  210. rttProvider.onRenderTargetTextureCreatedObservable.add((data) => {
  211. data.texture.clearColor = new Color4(0, 0, 0, 0);
  212. data.texture.customRenderFunction = () => {
  213. flareSystem.render();
  214. };
  215. // add to the scene's render targets
  216. // this._xrSessionManager.scene.onBeforeCameraRenderObservable.add(() => {
  217. // data.texture.render();
  218. // });
  219. });
  220. // remove the lens flare system from the scene
  221. this._xrSessionManager.onXRSessionInit.add(() => {
  222. this._xrSessionManager.scene.lensFlareSystems.splice(this._xrSessionManager.scene.lensFlareSystems.indexOf(flareSystem), 1);
  223. });
  224. // add it back when the session ends
  225. this._xrSessionManager.onXRSessionEnded.add(() => {
  226. this._xrSessionManager.scene.lensFlareSystems.push(flareSystem);
  227. });
  228. return wrapper;
  229. }
  230. /**
  231. * Add a new layer to the already-existing list of layers
  232. * @param wrappedLayer the new layer to add to the existing ones
  233. */
  234. addXRSessionLayer(wrappedLayer) {
  235. this._existingLayers.push(wrappedLayer);
  236. this.setXRSessionLayers(this._existingLayers);
  237. }
  238. /**
  239. * Sets the layers to be used by the XR session.
  240. * Note that you must call this function with any layers you wish to render to
  241. * since it adds them to the XR session's render state
  242. * (replacing any layers that were added in a previous call to setXRSessionLayers or updateRenderState).
  243. * This method also sets up the session manager's render target texture provider
  244. * as the first layer in the array, which feeds the WebXR camera(s) attached to the session.
  245. * @param wrappedLayers An array of WebXRLayerWrapper, usually returned from the WebXRLayers createLayer functions.
  246. */
  247. setXRSessionLayers(wrappedLayers = this._existingLayers) {
  248. // this._existingLayers = wrappedLayers;
  249. const renderStateInit = { ...this._xrSessionManager.session.renderState };
  250. // Clear out the layer-related fields.
  251. renderStateInit.baseLayer = undefined;
  252. renderStateInit.layers = wrappedLayers.map((wrappedLayer) => wrappedLayer.layer);
  253. this._xrSessionManager.updateRenderState(renderStateInit);
  254. if (!this._projectionLayerInitialized) {
  255. this._xrSessionManager._setBaseLayerWrapper(wrappedLayers.length > 0 ? wrappedLayers.at(0) : null);
  256. }
  257. }
  258. isCompatible() {
  259. // TODO (rgerd): Add native support.
  260. return !this._xrSessionManager.isNative && typeof XRWebGLBinding !== "undefined" && !!XRWebGLBinding.prototype.createProjectionLayer;
  261. }
  262. /**
  263. * Dispose this feature and all of the resources attached.
  264. */
  265. dispose() {
  266. super.dispose();
  267. }
  268. _onXRFrame(_xrFrame) {
  269. // Replace once the mapped internal texture of each available composition layer, apart from the last one, which is the projection layer that needs an RTT
  270. const layers = this._existingLayers;
  271. for (let i = 0; i < layers.length; ++i) {
  272. const layer = layers[i];
  273. if (layer.layerType !== "XRProjectionLayer") {
  274. // get the rtt provider
  275. const rttProvider = this._layerToRTTProviderMapping.get(layer.layer);
  276. if (!rttProvider) {
  277. continue;
  278. }
  279. if (rttProvider.layerWrapper.isMultiview) {
  280. // get the views, if we are in multiview
  281. const pose = _xrFrame.getViewerPose(this._xrSessionManager.referenceSpace);
  282. if (pose) {
  283. const views = pose.views;
  284. for (let j = 0; j < views.length; ++j) {
  285. const view = views[j];
  286. rttProvider.getRenderTargetTextureForView(view);
  287. }
  288. }
  289. }
  290. else {
  291. rttProvider.getRenderTargetTextureForView();
  292. }
  293. }
  294. }
  295. }
  296. }
  297. /**
  298. * The module's name
  299. */
  300. WebXRLayers.Name = WebXRFeatureName.LAYERS;
  301. /**
  302. * The (Babylon) version of this module.
  303. * This is an integer representing the implementation version.
  304. * This number does not correspond to the WebXR specs version
  305. */
  306. WebXRLayers.Version = 1;
  307. //register the plugin
  308. WebXRFeaturesManager.AddWebXRFeature(WebXRLayers.Name, (xrSessionManager, options) => {
  309. return () => new WebXRLayers(xrSessionManager, options);
  310. }, WebXRLayers.Version, false);
  311. //# sourceMappingURL=WebXRLayers.js.map