Procházet zdrojové kódy

Get WebXR minimally working again in Godot 4

David Snopek před 3 roky
rodič
revize
b5c14e5f15

+ 1 - 1
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):

+ 1 - 2
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();

+ 19 - 172
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',

+ 37 - 19
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 <stdlib.h>
 
@@ -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();
 
-	transform_for_eye = _js_matrix_to_transform(js_matrix);
-	free(js_matrix);
+		// 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;
+	}
 
-	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<BlitToScreen> 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<GLES3::TextureStorage *>(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);
 		}

+ 1 - 0
modules/webxr/webxr_interface_js.h

@@ -45,6 +45,7 @@ class WebXRInterfaceJS : public WebXRInterface {
 private:
 	bool initialized;
 	Ref<XRPositionalTracker> head_tracker;
+	Transform3D head_transform;
 
 	String session_mode;
 	String required_features;