From b5c14e5f158c76713974c7c43d1d511797882dee Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 16 Aug 2022 17:33:26 -0500 Subject: [PATCH] Get WebXR minimally working again in Godot 4 --- modules/webxr/config.py | 2 +- modules/webxr/godot_webxr.h | 3 +- modules/webxr/native/library_godot_webxr.js | 191 ++------------------ modules/webxr/webxr_interface_js.cpp | 56 ++++-- modules/webxr/webxr_interface_js.h | 1 + 5 files changed, 59 insertions(+), 194 deletions(-) diff --git a/modules/webxr/config.py b/modules/webxr/config.py index f676ef3483a5..8d75e7f531b5 100644 --- a/modules/webxr/config.py +++ b/modules/webxr/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return not env["disable_3d"] + return env["opengl3"] and not env["disable_3d"] def configure(env): diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index 52104895d4e2..34d068be3e7d 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -65,8 +65,7 @@ extern int godot_webxr_get_view_count(); extern int *godot_webxr_get_render_target_size(); extern float *godot_webxr_get_transform_for_eye(int p_eye); extern float *godot_webxr_get_projection_for_eye(int p_eye); -extern int godot_webxr_get_external_texture_for_eye(int p_eye); -extern void godot_webxr_commit_for_eye(int p_eye); +extern void godot_webxr_commit_for_eye(int p_eye, unsigned int p_destination_fbo); extern void godot_webxr_sample_controller_data(); extern int godot_webxr_get_controller_count(); diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index c4b21defce77..9b75796ee533 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -32,9 +32,6 @@ const GodotWebXR = { $GodotWebXR: { gl: null, - texture_ids: [null, null], - textures: [null, null], - session: null, space: null, frame: null, @@ -77,110 +74,6 @@ const GodotWebXR = { }, 0); }, - // Some custom WebGL code for blitting our eye textures to the - // framebuffer we get from WebXR. - shaderProgram: null, - programInfo: null, - buffer: null, - // Vertex shader source. - vsSource: ` - const vec2 scale = vec2(0.5, 0.5); - attribute vec4 aVertexPosition; - - varying highp vec2 vTextureCoord; - - void main () { - gl_Position = aVertexPosition; - vTextureCoord = aVertexPosition.xy * scale + scale; - } - `, - // Fragment shader source. - fsSource: ` - varying highp vec2 vTextureCoord; - - uniform sampler2D uSampler; - - void main() { - gl_FragColor = texture2D(uSampler, vTextureCoord); - } - `, - - initShaderProgram: (gl, vsSource, fsSource) => { - const vertexShader = GodotWebXR.loadShader(gl, gl.VERTEX_SHADER, vsSource); - const fragmentShader = GodotWebXR.loadShader(gl, gl.FRAGMENT_SHADER, fsSource); - - const shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertexShader); - gl.attachShader(shaderProgram, fragmentShader); - gl.linkProgram(shaderProgram); - - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - GodotRuntime.error(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`); - return null; - } - - return shaderProgram; - }, - loadShader: (gl, type, source) => { - const shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - GodotRuntime.error(`An error occurred compiling the shader: ${gl.getShaderInfoLog(shader)}`); - gl.deleteShader(shader); - return null; - } - - return shader; - }, - initBuffer: (gl) => { - const positionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - const positions = [ - -1.0, -1.0, - 1.0, -1.0, - -1.0, 1.0, - 1.0, 1.0, - ]; - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); - return positionBuffer; - }, - blitTexture: (gl, texture) => { - if (GodotWebXR.shaderProgram === null) { - GodotWebXR.shaderProgram = GodotWebXR.initShaderProgram(gl, GodotWebXR.vsSource, GodotWebXR.fsSource); - GodotWebXR.programInfo = { - program: GodotWebXR.shaderProgram, - attribLocations: { - vertexPosition: gl.getAttribLocation(GodotWebXR.shaderProgram, 'aVertexPosition'), - }, - uniformLocations: { - uSampler: gl.getUniformLocation(GodotWebXR.shaderProgram, 'uSampler'), - }, - }; - GodotWebXR.buffer = GodotWebXR.initBuffer(gl); - } - - const orig_program = gl.getParameter(gl.CURRENT_PROGRAM); - gl.useProgram(GodotWebXR.shaderProgram); - - gl.bindBuffer(gl.ARRAY_BUFFER, GodotWebXR.buffer); - gl.vertexAttribPointer(GodotWebXR.programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.uniform1i(GodotWebXR.programInfo.uniformLocations.uSampler, 0); - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - - // Restore state. - gl.bindTexture(gl.TEXTURE_2D, null); - gl.disableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.useProgram(orig_program); - }, - // Holds the controllers list between function calls. controllers: [], @@ -370,22 +263,6 @@ const GodotWebXR = { .catch((e) => { }); } - // Clean-up the textures we allocated for each view. - const gl = GodotWebXR.gl; - for (let i = 0; i < GodotWebXR.textures.length; i++) { - const texture = GodotWebXR.textures[i]; - if (texture !== null) { - gl.deleteTexture(texture); - } - GodotWebXR.textures[i] = null; - - const texture_id = GodotWebXR.texture_ids[i]; - if (texture_id !== null) { - GL.textures[texture_id] = null; - } - GodotWebXR.texture_ids[i] = null; - } - GodotWebXR.session = null; GodotWebXR.space = null; GodotWebXR.frame = null; @@ -460,50 +337,9 @@ const GodotWebXR = { return buf; }, - godot_webxr_get_external_texture_for_eye__proxy: 'sync', - godot_webxr_get_external_texture_for_eye__sig: 'ii', - godot_webxr_get_external_texture_for_eye: function (p_eye) { - if (!GodotWebXR.session) { - return 0; - } - - const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0; - if (GodotWebXR.texture_ids[view_index]) { - return GodotWebXR.texture_ids[view_index]; - } - - // Check pose separately and after returning the cached texture id, - // because we won't get a pose in some cases if we lose tracking, and - // we don't want to return 0 just because tracking was lost. - if (!GodotWebXR.pose) { - return 0; - } - - const glLayer = GodotWebXR.session.renderState.baseLayer; - const view = GodotWebXR.pose.views[view_index]; - const viewport = glLayer.getViewport(view); - const gl = GodotWebXR.gl; - - const texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.width, viewport.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.bindTexture(gl.TEXTURE_2D, null); - - const texture_id = GL.getNewId(GL.textures); - GL.textures[texture_id] = texture; - GodotWebXR.textures[view_index] = texture; - GodotWebXR.texture_ids[view_index] = texture_id; - return texture_id; - }, - godot_webxr_commit_for_eye__proxy: 'sync', - godot_webxr_commit_for_eye__sig: 'vi', - godot_webxr_commit_for_eye: function (p_eye) { + godot_webxr_commit_for_eye__sig: 'vii', + godot_webxr_commit_for_eye: function (p_eye, p_destination_fbo) { if (!GodotWebXR.session || !GodotWebXR.pose) { return; } @@ -514,18 +350,29 @@ const GodotWebXR = { const viewport = glLayer.getViewport(view); const gl = GodotWebXR.gl; + const framebuffer = GL.framebuffers[p_destination_fbo]; + const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); - const orig_viewport = gl.getParameter(gl.VIEWPORT); + const orig_read_framebuffer = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING); + const orig_read_buffer = gl.getParameter(gl.READ_BUFFER); + const orig_draw_framebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING); - // Bind to WebXR's framebuffer. - gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer); - gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height); + // Copy from Godot render target into framebuffer from WebXR. + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer); + gl.readBuffer(gl.COLOR_ATTACHMENT0); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, glLayer.framebuffer); - GodotWebXR.blitTexture(gl, GodotWebXR.textures[view_index]); + // Flip Y upside down on destination. + gl.blitFramebuffer(0, 0, viewport.width, viewport.height, + viewport.x, viewport.height, viewport.width, viewport.y, + gl.COLOR_BUFFER_BIT, gl.NEAREST); // Restore state. gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer); - gl.viewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, orig_read_framebuffer); + gl.readBuffer(orig_read_buffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, orig_draw_framebuffer); }, godot_webxr_sample_controller_data__proxy: 'sync', diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 7d97dbfa0b10..d0c7484aa1e2 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -34,9 +34,11 @@ #include "core/input/input.h" #include "core/os/os.h" +#include "drivers/gles3/storage/texture_storage.h" #include "emscripten.h" #include "godot_webxr.h" #include "servers/rendering/renderer_compositor.h" +#include "servers/rendering/rendering_server_globals.h" #include @@ -232,6 +234,8 @@ bool WebXRInterfaceJS::initialize() { } // we must create a tracker for our head + head_transform.basis = Basis(); + head_transform.origin = Vector3(); head_tracker.instantiate(); head_tracker->set_tracker_type(XRServer::TRACKER_HEAD); head_tracker->set_tracker_name("head"); @@ -334,15 +338,17 @@ Transform3D WebXRInterfaceJS::get_camera_transform() { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, transform_for_eye); - float *js_matrix = godot_webxr_get_transform_for_eye(0); - if (!initialized || js_matrix == nullptr) { - return transform_for_eye; + if (initialized) { + float world_scale = xr_server->get_world_scale(); + + // just scale our origin point of our transform + Transform3D _head_transform = head_transform; + _head_transform.origin *= world_scale; + + transform_for_eye = (xr_server->get_reference_frame()) * _head_transform; } - transform_for_eye = _js_matrix_to_transform(js_matrix); - free(js_matrix); - - return xr_server->get_reference_frame() * transform_for_eye; + return transform_for_eye; }; Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { @@ -360,6 +366,14 @@ Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Tran transform_for_eye = _js_matrix_to_transform(js_matrix); free(js_matrix); + float world_scale = xr_server->get_world_scale(); + // Scale only the center point of our eye transform, so we don't scale the + // distance between the eyes. + Transform3D _head_transform = head_transform; + transform_for_eye.origin -= _head_transform.origin; + _head_transform.origin *= world_scale; + transform_for_eye.origin += _head_transform.origin; + return p_cam_transform * xr_server->get_reference_frame() * transform_for_eye; }; @@ -394,29 +408,33 @@ Vector WebXRInterfaceJS::post_draw_viewport(RID p_render_target, c return blit_to_screen; } - // @todo Refactor this to be based on "views" rather than "eyes". - godot_webxr_commit_for_eye(1); - if (godot_webxr_get_view_count() > 1) { - godot_webxr_commit_for_eye(2); + GLES3::TextureStorage *texture_storage = dynamic_cast(RSG::texture_storage); + if (!texture_storage) { + return blit_to_screen; } + GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target); + + // @todo Support multiple eyes! + godot_webxr_commit_for_eye(1, rt->fbo); + return blit_to_screen; }; void WebXRInterfaceJS::process() { if (initialized) { - godot_webxr_sample_controller_data(); - + // Get the "head" position. + float *js_matrix = godot_webxr_get_transform_for_eye(0); + if (js_matrix != nullptr) { + head_transform = _js_matrix_to_transform(js_matrix); + free(js_matrix); + } if (head_tracker.is_valid()) { - // TODO set default pose to our head location (i.e. get_camera_transform without world scale and reference frame applied) - // head_tracker->set_pose("default", head_transform, Vector3(), Vector3()); + head_tracker->set_pose("default", head_transform, Vector3(), Vector3()); } + godot_webxr_sample_controller_data(); int controller_count = godot_webxr_get_controller_count(); - if (controller_count == 0) { - return; - } - for (int i = 0; i < controller_count; i++) { _update_tracker(i); } diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index dbe89dad83a7..319adc2ac991 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -45,6 +45,7 @@ class WebXRInterfaceJS : public WebXRInterface { private: bool initialized; Ref head_tracker; + Transform3D head_transform; String session_mode; String required_features;