library_godot_webxr.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /*************************************************************************/
  2. /* library_godot_webxr.js */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. const GodotWebXR = {
  31. $GodotWebXR__deps: ['$Browser', '$GL', '$GodotRuntime'],
  32. $GodotWebXR: {
  33. gl: null,
  34. texture_ids: [null, null],
  35. textures: [null, null],
  36. session: null,
  37. space: null,
  38. frame: null,
  39. pose: null,
  40. // Monkey-patch the requestAnimationFrame() used by Emscripten for the main
  41. // loop, so that we can swap it out for XRSession.requestAnimationFrame()
  42. // when an XR session is started.
  43. orig_requestAnimationFrame: null,
  44. requestAnimationFrame: (callback) => {
  45. if (GodotWebXR.session && GodotWebXR.space) {
  46. const onFrame = function (time, frame) {
  47. GodotWebXR.frame = frame;
  48. GodotWebXR.pose = frame.getViewerPose(GodotWebXR.space);
  49. callback(time);
  50. GodotWebXR.frame = null;
  51. GodotWebXR.pose = null;
  52. };
  53. GodotWebXR.session.requestAnimationFrame(onFrame);
  54. } else {
  55. GodotWebXR.orig_requestAnimationFrame(callback);
  56. }
  57. },
  58. monkeyPatchRequestAnimationFrame: (enable) => {
  59. if (GodotWebXR.orig_requestAnimationFrame === null) {
  60. GodotWebXR.orig_requestAnimationFrame = Browser.requestAnimationFrame;
  61. }
  62. Browser.requestAnimationFrame = enable
  63. ? GodotWebXR.requestAnimationFrame : GodotWebXR.orig_requestAnimationFrame;
  64. },
  65. pauseResumeMainLoop: () => {
  66. // Once both GodotWebXR.session and GodotWebXR.space are set or
  67. // unset, our monkey-patched requestAnimationFrame() should be
  68. // enabled or disabled. When using the WebXR API Emulator, this
  69. // gets picked up automatically, however, in the Oculus Browser
  70. // on the Quest, we need to pause and resume the main loop.
  71. Browser.pauseAsyncCallbacks();
  72. Browser.mainLoop.pause();
  73. window.setTimeout(function () {
  74. Browser.resumeAsyncCallbacks();
  75. Browser.mainLoop.resume();
  76. }, 0);
  77. },
  78. // Some custom WebGL code for blitting our eye textures to the
  79. // framebuffer we get from WebXR.
  80. shaderProgram: null,
  81. programInfo: null,
  82. buffer: null,
  83. // Vertex shader source.
  84. vsSource: `
  85. const vec2 scale = vec2(0.5, 0.5);
  86. attribute vec4 aVertexPosition;
  87. varying highp vec2 vTextureCoord;
  88. void main () {
  89. gl_Position = aVertexPosition;
  90. vTextureCoord = aVertexPosition.xy * scale + scale;
  91. }
  92. `,
  93. // Fragment shader source.
  94. fsSource: `
  95. varying highp vec2 vTextureCoord;
  96. uniform sampler2D uSampler;
  97. void main() {
  98. gl_FragColor = texture2D(uSampler, vTextureCoord);
  99. }
  100. `,
  101. initShaderProgram: (gl, vsSource, fsSource) => {
  102. const vertexShader = GodotWebXR.loadShader(gl, gl.VERTEX_SHADER, vsSource);
  103. const fragmentShader = GodotWebXR.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  104. const shaderProgram = gl.createProgram();
  105. gl.attachShader(shaderProgram, vertexShader);
  106. gl.attachShader(shaderProgram, fragmentShader);
  107. gl.linkProgram(shaderProgram);
  108. if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  109. GodotRuntime.error(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`);
  110. return null;
  111. }
  112. return shaderProgram;
  113. },
  114. loadShader: (gl, type, source) => {
  115. const shader = gl.createShader(type);
  116. gl.shaderSource(shader, source);
  117. gl.compileShader(shader);
  118. if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  119. GodotRuntime.error(`An error occurred compiling the shader: ${gl.getShaderInfoLog(shader)}`);
  120. gl.deleteShader(shader);
  121. return null;
  122. }
  123. return shader;
  124. },
  125. initBuffer: (gl) => {
  126. const positionBuffer = gl.createBuffer();
  127. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  128. const positions = [
  129. -1.0, -1.0,
  130. 1.0, -1.0,
  131. -1.0, 1.0,
  132. 1.0, 1.0,
  133. ];
  134. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  135. return positionBuffer;
  136. },
  137. blitTexture: (gl, texture) => {
  138. if (GodotWebXR.shaderProgram === null) {
  139. GodotWebXR.shaderProgram = GodotWebXR.initShaderProgram(gl, GodotWebXR.vsSource, GodotWebXR.fsSource);
  140. GodotWebXR.programInfo = {
  141. program: GodotWebXR.shaderProgram,
  142. attribLocations: {
  143. vertexPosition: gl.getAttribLocation(GodotWebXR.shaderProgram, 'aVertexPosition'),
  144. },
  145. uniformLocations: {
  146. uSampler: gl.getUniformLocation(GodotWebXR.shaderProgram, 'uSampler'),
  147. },
  148. };
  149. GodotWebXR.buffer = GodotWebXR.initBuffer(gl);
  150. }
  151. const orig_program = gl.getParameter(gl.CURRENT_PROGRAM);
  152. gl.useProgram(GodotWebXR.shaderProgram);
  153. gl.bindBuffer(gl.ARRAY_BUFFER, GodotWebXR.buffer);
  154. gl.vertexAttribPointer(GodotWebXR.programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);
  155. gl.enableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition);
  156. gl.activeTexture(gl.TEXTURE0);
  157. gl.bindTexture(gl.TEXTURE_2D, texture);
  158. gl.uniform1i(GodotWebXR.programInfo.uniformLocations.uSampler, 0);
  159. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  160. // Restore state.
  161. gl.bindTexture(gl.TEXTURE_2D, null);
  162. gl.disableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition);
  163. gl.bindBuffer(gl.ARRAY_BUFFER, null);
  164. gl.useProgram(orig_program);
  165. },
  166. // Holds the controllers list between function calls.
  167. controllers: [],
  168. // Updates controllers array, where the left hand (or sole tracker) is
  169. // the first element, and the right hand is the second element, and any
  170. // others placed at the 3rd position and up.
  171. sampleControllers: () => {
  172. if (!GodotWebXR.session || !GodotWebXR.frame) {
  173. return;
  174. }
  175. let other_index = 2;
  176. const controllers = [];
  177. GodotWebXR.session.inputSources.forEach((input_source) => {
  178. if (input_source.targetRayMode === 'tracked-pointer') {
  179. if (input_source.handedness === 'right') {
  180. controllers[1] = input_source;
  181. } else if (input_source.handedness === 'left' || !controllers[0]) {
  182. controllers[0] = input_source;
  183. }
  184. } else {
  185. controllers[other_index++] = input_source;
  186. }
  187. });
  188. GodotWebXR.controllers = controllers;
  189. },
  190. getControllerId: (input_source) => GodotWebXR.controllers.indexOf(input_source),
  191. },
  192. godot_webxr_is_supported__proxy: 'sync',
  193. godot_webxr_is_supported__sig: 'i',
  194. godot_webxr_is_supported: function () {
  195. return !!navigator.xr;
  196. },
  197. godot_webxr_is_session_supported__proxy: 'sync',
  198. godot_webxr_is_session_supported__sig: 'vii',
  199. godot_webxr_is_session_supported: function (p_session_mode, p_callback) {
  200. const session_mode = GodotRuntime.parseString(p_session_mode);
  201. const cb = GodotRuntime.get_func(p_callback);
  202. if (navigator.xr) {
  203. navigator.xr.isSessionSupported(session_mode).then(function (supported) {
  204. const c_str = GodotRuntime.allocString(session_mode);
  205. cb(c_str, supported ? 1 : 0);
  206. GodotRuntime.free(c_str);
  207. });
  208. } else {
  209. const c_str = GodotRuntime.allocString(session_mode);
  210. cb(c_str, 0);
  211. GodotRuntime.free(c_str);
  212. }
  213. },
  214. godot_webxr_initialize__deps: ['emscripten_webgl_get_current_context'],
  215. godot_webxr_initialize__proxy: 'sync',
  216. godot_webxr_initialize__sig: 'viiiiiiiiii',
  217. godot_webxr_initialize: function (p_session_mode, p_required_features, p_optional_features, p_requested_reference_spaces, p_on_session_started, p_on_session_ended, p_on_session_failed, p_on_controller_changed, p_on_input_event, p_on_simple_event) {
  218. GodotWebXR.monkeyPatchRequestAnimationFrame(true);
  219. const session_mode = GodotRuntime.parseString(p_session_mode);
  220. const required_features = GodotRuntime.parseString(p_required_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
  221. const optional_features = GodotRuntime.parseString(p_optional_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
  222. const requested_reference_space_types = GodotRuntime.parseString(p_requested_reference_spaces).split(',').map((s) => s.trim());
  223. const onstarted = GodotRuntime.get_func(p_on_session_started);
  224. const onended = GodotRuntime.get_func(p_on_session_ended);
  225. const onfailed = GodotRuntime.get_func(p_on_session_failed);
  226. const oncontroller = GodotRuntime.get_func(p_on_controller_changed);
  227. const oninputevent = GodotRuntime.get_func(p_on_input_event);
  228. const onsimpleevent = GodotRuntime.get_func(p_on_simple_event);
  229. const session_init = {};
  230. if (required_features.length > 0) {
  231. session_init['requiredFeatures'] = required_features;
  232. }
  233. if (optional_features.length > 0) {
  234. session_init['optionalFeatures'] = optional_features;
  235. }
  236. navigator.xr.requestSession(session_mode, session_init).then(function (session) {
  237. GodotWebXR.session = session;
  238. session.addEventListener('end', function (evt) {
  239. onended();
  240. });
  241. session.addEventListener('inputsourceschange', function (evt) {
  242. let controller_changed = false;
  243. [evt.added, evt.removed].forEach((lst) => {
  244. lst.forEach((input_source) => {
  245. if (input_source.targetRayMode === 'tracked-pointer') {
  246. controller_changed = true;
  247. }
  248. });
  249. });
  250. if (controller_changed) {
  251. oncontroller();
  252. }
  253. });
  254. ['selectstart', 'select', 'selectend', 'squeezestart', 'squeeze', 'squeezeend'].forEach((input_event) => {
  255. session.addEventListener(input_event, function (evt) {
  256. const c_str = GodotRuntime.allocString(input_event);
  257. oninputevent(c_str, GodotWebXR.getControllerId(evt.inputSource));
  258. GodotRuntime.free(c_str);
  259. });
  260. });
  261. session.addEventListener('visibilitychange', function (evt) {
  262. const c_str = GodotRuntime.allocString('visibility_state_changed');
  263. onsimpleevent(c_str);
  264. GodotRuntime.free(c_str);
  265. });
  266. const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef
  267. const gl = GL.getContext(gl_context_handle).GLctx;
  268. GodotWebXR.gl = gl;
  269. gl.makeXRCompatible().then(function () {
  270. session.updateRenderState({
  271. baseLayer: new XRWebGLLayer(session, gl),
  272. });
  273. function onReferenceSpaceSuccess(reference_space, reference_space_type) {
  274. GodotWebXR.space = reference_space;
  275. // Using reference_space.addEventListener() crashes when
  276. // using the polyfill with the WebXR Emulator extension,
  277. // so we set the event property instead.
  278. reference_space.onreset = function (evt) {
  279. const c_str = GodotRuntime.allocString('reference_space_reset');
  280. onsimpleevent(c_str);
  281. GodotRuntime.free(c_str);
  282. };
  283. // Now that both GodotWebXR.session and GodotWebXR.space are
  284. // set, we need to pause and resume the main loop for the XR
  285. // main loop to kick in.
  286. GodotWebXR.pauseResumeMainLoop();
  287. // Call in setTimeout() so that errors in the onstarted()
  288. // callback don't bubble up here and cause Godot to try the
  289. // next reference space.
  290. window.setTimeout(function () {
  291. const c_str = GodotRuntime.allocString(reference_space_type);
  292. onstarted(c_str);
  293. GodotRuntime.free(c_str);
  294. }, 0);
  295. }
  296. function requestReferenceSpace() {
  297. const reference_space_type = requested_reference_space_types.shift();
  298. session.requestReferenceSpace(reference_space_type)
  299. .then((refSpace) => {
  300. onReferenceSpaceSuccess(refSpace, reference_space_type);
  301. })
  302. .catch(() => {
  303. if (requested_reference_space_types.length === 0) {
  304. const c_str = GodotRuntime.allocString('Unable to get any of the requested reference space types');
  305. onfailed(c_str);
  306. GodotRuntime.free(c_str);
  307. } else {
  308. requestReferenceSpace();
  309. }
  310. });
  311. }
  312. requestReferenceSpace();
  313. }).catch(function (error) {
  314. const c_str = GodotRuntime.allocString(`Unable to make WebGL context compatible with WebXR: ${error}`);
  315. onfailed(c_str);
  316. GodotRuntime.free(c_str);
  317. });
  318. }).catch(function (error) {
  319. const c_str = GodotRuntime.allocString(`Unable to start session: ${error}`);
  320. onfailed(c_str);
  321. GodotRuntime.free(c_str);
  322. });
  323. },
  324. godot_webxr_uninitialize__proxy: 'sync',
  325. godot_webxr_uninitialize__sig: 'v',
  326. godot_webxr_uninitialize: function () {
  327. if (GodotWebXR.session) {
  328. GodotWebXR.session.end()
  329. // Prevent exception when session has already ended.
  330. .catch((e) => { });
  331. }
  332. // Clean-up the textures we allocated for each view.
  333. const gl = GodotWebXR.gl;
  334. for (let i = 0; i < GodotWebXR.textures.length; i++) {
  335. const texture = GodotWebXR.textures[i];
  336. if (texture !== null) {
  337. gl.deleteTexture(texture);
  338. }
  339. GodotWebXR.textures[i] = null;
  340. const texture_id = GodotWebXR.texture_ids[i];
  341. if (texture_id !== null) {
  342. GL.textures[texture_id] = null;
  343. }
  344. GodotWebXR.texture_ids[i] = null;
  345. }
  346. GodotWebXR.session = null;
  347. GodotWebXR.space = null;
  348. GodotWebXR.frame = null;
  349. GodotWebXR.pose = null;
  350. // Disable the monkey-patched window.requestAnimationFrame() and
  351. // pause/restart the main loop to activate it on all platforms.
  352. GodotWebXR.monkeyPatchRequestAnimationFrame(false);
  353. GodotWebXR.pauseResumeMainLoop();
  354. },
  355. godot_webxr_get_view_count__proxy: 'sync',
  356. godot_webxr_get_view_count__sig: 'i',
  357. godot_webxr_get_view_count: function () {
  358. if (!GodotWebXR.session || !GodotWebXR.pose) {
  359. return 0;
  360. }
  361. return GodotWebXR.pose.views.length;
  362. },
  363. godot_webxr_get_render_targetsize__proxy: 'sync',
  364. godot_webxr_get_render_targetsize__sig: 'i',
  365. godot_webxr_get_render_targetsize: function () {
  366. if (!GodotWebXR.session || !GodotWebXR.pose) {
  367. return 0;
  368. }
  369. const glLayer = GodotWebXR.session.renderState.baseLayer;
  370. const view = GodotWebXR.pose.views[0];
  371. const viewport = glLayer.getViewport(view);
  372. const buf = GodotRuntime.malloc(2 * 4);
  373. GodotRuntime.setHeapValue(buf + 0, viewport.width, 'i32');
  374. GodotRuntime.setHeapValue(buf + 4, viewport.height, 'i32');
  375. return buf;
  376. },
  377. godot_webxr_get_transform_for_eye__proxy: 'sync',
  378. godot_webxr_get_transform_for_eye__sig: 'ii',
  379. godot_webxr_get_transform_for_eye: function (p_eye) {
  380. if (!GodotWebXR.session || !GodotWebXR.pose) {
  381. return 0;
  382. }
  383. const views = GodotWebXR.pose.views;
  384. let matrix;
  385. if (p_eye === 0) {
  386. matrix = GodotWebXR.pose.transform.matrix;
  387. } else {
  388. matrix = views[p_eye - 1].transform.matrix;
  389. }
  390. const buf = GodotRuntime.malloc(16 * 4);
  391. for (let i = 0; i < 16; i++) {
  392. GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
  393. }
  394. return buf;
  395. },
  396. godot_webxr_get_projection_for_eye__proxy: 'sync',
  397. godot_webxr_get_projection_for_eye__sig: 'ii',
  398. godot_webxr_get_projection_for_eye: function (p_eye) {
  399. if (!GodotWebXR.session || !GodotWebXR.pose) {
  400. return 0;
  401. }
  402. const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
  403. const matrix = GodotWebXR.pose.views[view_index].projectionMatrix;
  404. const buf = GodotRuntime.malloc(16 * 4);
  405. for (let i = 0; i < 16; i++) {
  406. GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
  407. }
  408. return buf;
  409. },
  410. godot_webxr_get_external_texture_for_eye__proxy: 'sync',
  411. godot_webxr_get_external_texture_for_eye__sig: 'ii',
  412. godot_webxr_get_external_texture_for_eye: function (p_eye) {
  413. if (!GodotWebXR.session) {
  414. return 0;
  415. }
  416. const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
  417. if (GodotWebXR.texture_ids[view_index]) {
  418. return GodotWebXR.texture_ids[view_index];
  419. }
  420. // Check pose separately and after returning the cached texture id,
  421. // because we won't get a pose in some cases if we lose tracking, and
  422. // we don't want to return 0 just because tracking was lost.
  423. if (!GodotWebXR.pose) {
  424. return 0;
  425. }
  426. const glLayer = GodotWebXR.session.renderState.baseLayer;
  427. const view = GodotWebXR.pose.views[view_index];
  428. const viewport = glLayer.getViewport(view);
  429. const gl = GodotWebXR.gl;
  430. const texture = gl.createTexture();
  431. gl.bindTexture(gl.TEXTURE_2D, texture);
  432. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.width, viewport.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  433. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  434. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  435. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  436. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  437. gl.bindTexture(gl.TEXTURE_2D, null);
  438. const texture_id = GL.getNewId(GL.textures);
  439. GL.textures[texture_id] = texture;
  440. GodotWebXR.textures[view_index] = texture;
  441. GodotWebXR.texture_ids[view_index] = texture_id;
  442. return texture_id;
  443. },
  444. godot_webxr_commit_for_eye__proxy: 'sync',
  445. godot_webxr_commit_for_eye__sig: 'vi',
  446. godot_webxr_commit_for_eye: function (p_eye) {
  447. if (!GodotWebXR.session || !GodotWebXR.pose) {
  448. return;
  449. }
  450. const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
  451. const glLayer = GodotWebXR.session.renderState.baseLayer;
  452. const view = GodotWebXR.pose.views[view_index];
  453. const viewport = glLayer.getViewport(view);
  454. const gl = GodotWebXR.gl;
  455. const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
  456. const orig_viewport = gl.getParameter(gl.VIEWPORT);
  457. // Bind to WebXR's framebuffer.
  458. gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
  459. gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
  460. GodotWebXR.blitTexture(gl, GodotWebXR.textures[view_index]);
  461. // Restore state.
  462. gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer);
  463. gl.viewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]);
  464. },
  465. godot_webxr_sample_controller_data__proxy: 'sync',
  466. godot_webxr_sample_controller_data__sig: 'v',
  467. godot_webxr_sample_controller_data: function () {
  468. GodotWebXR.sampleControllers();
  469. },
  470. godot_webxr_get_controller_count__proxy: 'sync',
  471. godot_webxr_get_controller_count__sig: 'i',
  472. godot_webxr_get_controller_count: function () {
  473. if (!GodotWebXR.session || !GodotWebXR.frame) {
  474. return 0;
  475. }
  476. return GodotWebXR.controllers.length;
  477. },
  478. godot_webxr_is_controller_connected__proxy: 'sync',
  479. godot_webxr_is_controller_connected__sig: 'ii',
  480. godot_webxr_is_controller_connected: function (p_controller) {
  481. if (!GodotWebXR.session || !GodotWebXR.frame) {
  482. return false;
  483. }
  484. return !!GodotWebXR.controllers[p_controller];
  485. },
  486. godot_webxr_get_controller_transform__proxy: 'sync',
  487. godot_webxr_get_controller_transform__sig: 'ii',
  488. godot_webxr_get_controller_transform: function (p_controller) {
  489. if (!GodotWebXR.session || !GodotWebXR.frame) {
  490. return 0;
  491. }
  492. const controller = GodotWebXR.controllers[p_controller];
  493. if (!controller) {
  494. return 0;
  495. }
  496. const frame = GodotWebXR.frame;
  497. const space = GodotWebXR.space;
  498. const pose = frame.getPose(controller.targetRaySpace, space);
  499. if (!pose) {
  500. // This can mean that the controller lost tracking.
  501. return 0;
  502. }
  503. const matrix = pose.transform.matrix;
  504. const buf = GodotRuntime.malloc(16 * 4);
  505. for (let i = 0; i < 16; i++) {
  506. GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
  507. }
  508. return buf;
  509. },
  510. godot_webxr_get_controller_buttons__proxy: 'sync',
  511. godot_webxr_get_controller_buttons__sig: 'ii',
  512. godot_webxr_get_controller_buttons: function (p_controller) {
  513. if (GodotWebXR.controllers.length === 0) {
  514. return 0;
  515. }
  516. const controller = GodotWebXR.controllers[p_controller];
  517. if (!controller || !controller.gamepad) {
  518. return 0;
  519. }
  520. const button_count = controller.gamepad.buttons.length;
  521. const buf = GodotRuntime.malloc((button_count + 1) * 4);
  522. GodotRuntime.setHeapValue(buf, button_count, 'i32');
  523. for (let i = 0; i < button_count; i++) {
  524. GodotRuntime.setHeapValue(buf + 4 + (i * 4), controller.gamepad.buttons[i].value, 'float');
  525. }
  526. return buf;
  527. },
  528. godot_webxr_get_controller_axes__proxy: 'sync',
  529. godot_webxr_get_controller_axes__sig: 'ii',
  530. godot_webxr_get_controller_axes: function (p_controller) {
  531. if (GodotWebXR.controllers.length === 0) {
  532. return 0;
  533. }
  534. const controller = GodotWebXR.controllers[p_controller];
  535. if (!controller || !controller.gamepad) {
  536. return 0;
  537. }
  538. const axes_count = controller.gamepad.axes.length;
  539. const buf = GodotRuntime.malloc((axes_count + 1) * 4);
  540. GodotRuntime.setHeapValue(buf, axes_count, 'i32');
  541. for (let i = 0; i < axes_count; i++) {
  542. let value = controller.gamepad.axes[i];
  543. if (i === 1 || i === 3) {
  544. // Invert the Y-axis on thumbsticks and trackpads, in order to
  545. // match OpenXR and other XR platform SDKs.
  546. value *= -1.0;
  547. }
  548. GodotRuntime.setHeapValue(buf + 4 + (i * 4), value, 'float');
  549. }
  550. return buf;
  551. },
  552. godot_webxr_get_visibility_state__proxy: 'sync',
  553. godot_webxr_get_visibility_state__sig: 'i',
  554. godot_webxr_get_visibility_state: function () {
  555. if (!GodotWebXR.session || !GodotWebXR.session.visibilityState) {
  556. return 0;
  557. }
  558. return GodotRuntime.allocString(GodotWebXR.session.visibilityState);
  559. },
  560. godot_webxr_get_bounds_geometry__proxy: 'sync',
  561. godot_webxr_get_bounds_geometry__sig: 'i',
  562. godot_webxr_get_bounds_geometry: function () {
  563. if (!GodotWebXR.space || !GodotWebXR.space.boundsGeometry) {
  564. return 0;
  565. }
  566. const point_count = GodotWebXR.space.boundsGeometry.length;
  567. if (point_count === 0) {
  568. return 0;
  569. }
  570. const buf = GodotRuntime.malloc(((point_count * 3) + 1) * 4);
  571. GodotRuntime.setHeapValue(buf, point_count, 'i32');
  572. for (let i = 0; i < point_count; i++) {
  573. const point = GodotWebXR.space.boundsGeometry[i];
  574. GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.x, 'float');
  575. GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.y, 'float');
  576. GodotRuntime.setHeapValue(buf + ((i * 3) + 3) * 4, point.z, 'float');
  577. }
  578. return buf;
  579. },
  580. };
  581. autoAddDeps(GodotWebXR, '$GodotWebXR');
  582. mergeInto(LibraryManager.library, GodotWebXR);