Get WebXR minimally working again in Godot 4

This commit is contained in:
David Snopek 2022-08-16 17:33:26 -05:00
parent 55bed82b05
commit b5c14e5f15
5 changed files with 59 additions and 194 deletions

View file

@ -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):

View file

@ -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();

View file

@ -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',

View file

@ -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();
// 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<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);
}

View file

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