From 8a0c2976c913b194ca2ab5726ff8481b23c504a9 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 19 Nov 2024 11:20:59 -0600 Subject: [PATCH] WebXR: Add support for Space Warp --- drivers/gles3/rasterizer_scene_gles3.cpp | 2 +- drivers/gles3/storage/texture_storage.cpp | 75 +++++++++++++++------ drivers/gles3/storage/texture_storage.h | 7 +- modules/webxr/godot_webxr.h | 2 + modules/webxr/native/library_godot_webxr.js | 58 +++++++++++++--- modules/webxr/webxr_interface_js.cpp | 43 ++++++++++-- modules/webxr/webxr_interface_js.h | 8 +++ 7 files changed, 160 insertions(+), 35 deletions(-) diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 2121b4d23975..4ad309de0793 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2472,7 +2472,7 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ scene_state.reset_gl_state(); - GLuint motion_vectors_fbo = rt->overridden.velocity_fbo; + GLuint motion_vectors_fbo = rt->velocity_fbo; if (motion_vectors_fbo != 0 && GLES3::Config::get_singleton()->max_vertex_attribs >= 22) { RENDER_TIMESTAMP("Motion Vectors Pass"); glBindFramebuffer(GL_FRAMEBUFFER, motion_vectors_fbo); diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 41614e2d5385..b0083576db65 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -2224,8 +2224,8 @@ void TextureStorage::_update_render_target_velocity(RenderTarget *rt) { uint32_t view_count = rt->view_count; GLuint texture_target = view_count > 1 ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; - GLuint velocity_texture_id = texture_get_texid(rt->overridden.velocity); - glBindTexture(texture_target, velocity_texture_id); + rt->velocity_texture = texture_get_texid(rt->overridden.velocity); + glBindTexture(texture_target, rt->velocity_texture); glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -2233,16 +2233,16 @@ void TextureStorage::_update_render_target_velocity(RenderTarget *rt) { #ifndef IOS_ENABLED if (view_count > 1) { - glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, velocity_texture_id, 0, 0, view_count); + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->velocity_texture, 0, 0, view_count); } else { #else { #endif - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, velocity_texture_id, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->velocity_texture, 0); } - GLuint velocity_depth_texture_id = texture_get_texid(rt->overridden.velocity_depth); - glBindTexture(texture_target, velocity_depth_texture_id); + rt->velocity_depth_texture = texture_get_texid(rt->overridden.velocity_depth); + glBindTexture(texture_target, rt->velocity_depth_texture); glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -2250,12 +2250,12 @@ void TextureStorage::_update_render_target_velocity(RenderTarget *rt) { #ifndef IOS_ENABLED if (view_count > 1) { - glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, velocity_depth_texture_id, 0, 0, view_count); + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->velocity_depth_texture, 0, 0, view_count); } else { #else { #endif - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, velocity_depth_texture_id, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->velocity_depth_texture, 0); } GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -2263,7 +2263,7 @@ void TextureStorage::_update_render_target_velocity(RenderTarget *rt) { glDeleteFramebuffers(1, &new_velocity_fbo); WARN_PRINT(vformat("Could not create motion vector render target, status: %s.", GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status))); } else { - rt->overridden.velocity_fbo = new_velocity_fbo; + rt->velocity_fbo = new_velocity_fbo; } glBindTexture(texture_target, 0); @@ -2390,11 +2390,13 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) { return; } - for (KeyValue &E : rt->overridden.velocity_fbo_cache) { - glDeleteFramebuffers(1, &E.value); + for (KeyValue &E : rt->overridden.velocity_fbo_cache) { + glDeleteFramebuffers(1, &E.value.fbo); } rt->overridden.velocity_fbo_cache.clear(); - rt->overridden.velocity_fbo = 0; + rt->velocity_fbo = 0; + rt->velocity_texture = 0; + rt->velocity_depth_texture = 0; // Dispose of the cached fbo's and the allocated textures for (KeyValue &E : rt->overridden.fbo_cache) { @@ -2582,19 +2584,50 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color velocity_hash_key = hash_murmur3_one_64(p_velocity_depth_texture.get_id(), velocity_hash_key); velocity_hash_key = hash_fmix32(velocity_hash_key); - RBMap::Element *fbo = rt->overridden.velocity_fbo_cache.find(velocity_hash_key); - if (fbo != nullptr) { - rt->overridden.velocity_fbo = fbo->get(); + RBMap::Element *velocity_cache = rt->overridden.velocity_fbo_cache.find(velocity_hash_key); + if (velocity_cache != nullptr) { + rt->velocity_fbo = velocity_cache->get().fbo; + rt->velocity_texture = velocity_cache->get().color; + rt->velocity_depth_texture = velocity_cache->get().depth; + create_new_velocity_fbo = false; + + if (rt->reattach_textures) { + glBindFramebuffer(GL_FRAMEBUFFER, rt->velocity_fbo); + +#ifndef IOS_ENABLED + if (rt->view_count > 1) { + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->velocity_texture, 0, 0, rt->view_count); + } else { +#else + { +#endif + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->velocity_texture, 0); + } + +#ifndef IOS_ENABLED + if (rt->view_count > 1) { + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->velocity_depth_texture, 0, 0, rt->view_count); + } else { +#else + { +#endif + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->velocity_depth_texture, 0); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } } if (p_velocity_texture.is_null()) { - for (KeyValue &E : rt->overridden.velocity_fbo_cache) { - glDeleteFramebuffers(1, &E.value); + for (KeyValue &E : rt->overridden.velocity_fbo_cache) { + glDeleteFramebuffers(1, &E.value.fbo); } rt->overridden.velocity_fbo_cache.clear(); - rt->overridden.velocity_fbo = 0; + rt->velocity_fbo = 0; + rt->velocity_texture = 0; + rt->velocity_depth_texture = 0; create_new_velocity_fbo = false; } @@ -2618,7 +2651,11 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color if (create_new_velocity_fbo) { _update_render_target_velocity(rt); - rt->overridden.velocity_fbo_cache.insert(velocity_hash_key, rt->overridden.velocity_fbo); + RenderTarget::RTOverridden::FBOCacheEntry new_entry; + new_entry.fbo = rt->velocity_fbo; + new_entry.color = rt->velocity_texture; + new_entry.depth = rt->velocity_depth_texture; + rt->overridden.velocity_fbo_cache.insert(velocity_hash_key, new_entry); } } diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 2db93668b1cb..2a8d9dded8c9 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -348,6 +348,9 @@ struct RenderTarget { GLuint backbuffer_fbo = 0; GLuint backbuffer = 0; GLuint backbuffer_depth = 0; + GLuint velocity_fbo = 0; + GLuint velocity_texture = 0; + GLuint velocity_depth_texture = 0; Size2i velocity_target_size; @@ -389,9 +392,7 @@ struct RenderTarget { Vector allocated_textures; }; RBMap fbo_cache; - - GLuint velocity_fbo = 0; - RBMap velocity_fbo_cache; + RBMap velocity_fbo_cache; } overridden; RID texture; diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index 1b3e3b6a412a..36ef8a6cee0f 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -73,6 +73,8 @@ extern bool godot_webxr_get_projection_for_view(int p_view, float *r_transform); extern unsigned int godot_webxr_get_color_texture(); extern unsigned int godot_webxr_get_depth_texture(); extern unsigned int godot_webxr_get_velocity_texture(); +extern unsigned int godot_webxr_get_velocity_depth_texture(); +extern bool godot_webxr_get_motion_vector_target_size(int *r_size); extern bool godot_webxr_update_input_source( int p_input_source_id, diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 155409f93114..7fdb710abe46 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -44,6 +44,9 @@ const GodotWebXR = { touches: new Array(5), onsimpleevent: null, + required_features: [], + optional_features: [], + // Monkey-patch the requestAnimationFrame() used by Emscripten for the main // loop, so that we can swap it out for XRSession.requestAnimationFrame() // when an XR session is started. @@ -84,8 +87,13 @@ const GodotWebXR = { }, 0); }, + getDefaultViewCount: () => { + // If we don't know the view count yet, we default to 1, unless space-warp was requested, in which case we default to 2. + return GodotWebXR.required_features.includes('space-warp') || GodotWebXR.optional_features.includes('space-warp') ? 2 : 1; + }, + getLayer: () => { - const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : 1; + const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : GodotWebXR.getDefaultViewCount(); let layer = GodotWebXR.layer; // If the view count hasn't changed since creating this layer, then @@ -239,8 +247,8 @@ const GodotWebXR = { GodotWebXR.monkeyPatchRequestAnimationFrame(true); const session_mode = GodotRuntime.parseString(p_session_mode); - const required_features = GodotRuntime.parseString(p_required_features).split(',').map((s) => s.trim()).filter((s) => s !== ''); - const optional_features = GodotRuntime.parseString(p_optional_features).split(',').map((s) => s.trim()).filter((s) => s !== ''); + GodotWebXR.required_features = GodotRuntime.parseString(p_required_features).split(',').map((s) => s.trim()).filter((s) => s !== ''); + GodotWebXR.optional_features = GodotRuntime.parseString(p_optional_features).split(',').map((s) => s.trim()).filter((s) => s !== ''); const requested_reference_space_types = GodotRuntime.parseString(p_requested_reference_spaces).split(',').map((s) => s.trim()); const onstarted = GodotRuntime.get_func(p_on_session_started); const onended = GodotRuntime.get_func(p_on_session_ended); @@ -249,11 +257,11 @@ const GodotWebXR = { const onsimpleevent = GodotRuntime.get_func(p_on_simple_event); const session_init = {}; - if (required_features.length > 0) { - session_init['requiredFeatures'] = required_features; + if (GodotWebXR.required_features.length > 0) { + session_init['requiredFeatures'] = GodotWebXR.required_features; } - if (optional_features.length > 0) { - session_init['optionalFeatures'] = optional_features; + if (GodotWebXR.optional_features.length > 0) { + session_init['optionalFeatures'] = GodotWebXR.optional_features; } navigator.xr.requestSession(session_mode, session_init).then(function (session) { @@ -466,7 +474,9 @@ const GodotWebXR = { if (subimage === null) { return 0; } - if (!subimage.depthStencilTexture) { + // If subimage.motionVectorTexture exists, then we use this depth texture for motion vectors, + // rather than for the color pass - so in that case return 0. + if (!subimage.depthStencilTexture || subimage.motionVectorTexture) { return 0; } return GodotWebXR.getTextureId(subimage.depthStencilTexture); @@ -485,6 +495,38 @@ const GodotWebXR = { return GodotWebXR.getTextureId(subimage.motionVectorTexture); }, + godot_webxr_get_velocity_depth_texture__proxy: 'sync', + godot_webxr_get_velocity_depth_texture__sig: 'i', + godot_webxr_get_velocity_depth_texture: function () { + const subimage = GodotWebXR.getSubImage(); + if (subimage === null) { + return 0; + } + // This is only used for motion vectors, if subimage.motionVectorTexture exists. + if (!subimage.motionVectorTexture || !subimage.depthStencilTexture) { + return 0; + } + return GodotWebXR.getTextureId(subimage.depthStencilTexture); + }, + + godot_webxr_get_motion_vector_target_size__proxy: 'sync', + godot_webxr_get_motion_vector_target_size__sig: 'ii', + godot_webxr_get_motion_vector_target_size: function (r_size) { + const subimage = GodotWebXR.getSubImage(); + if (subimage === null) { + return false; + } + + if (!subimage.motionVectorTextureWidth || !subimage.motionVectorTextureHeight) { + return false; + } + + GodotRuntime.setHeapValue(r_size + 0, subimage.motionVectorTextureWidth, 'i32'); + GodotRuntime.setHeapValue(r_size + 4, subimage.motionVectorTextureHeight, 'i32'); + + return true; + }, + godot_webxr_update_input_source__proxy: 'sync', godot_webxr_update_input_source__sig: 'iiiiiiiiiiiiiii', godot_webxr_update_input_source: function (p_input_source_id, r_target_pose, r_target_ray_mode, r_touch_index, r_has_grip_pose, r_grip_pose, r_has_standard_mapping, r_button_count, r_buttons, r_axes_count, r_axes, r_has_hand_data, r_hand_joints, r_hand_radii) { diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index f45135a61188..c5b3d747dba1 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -500,6 +500,8 @@ bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) { // Cache the resources so we don't have to get them from JS twice. color_texture = _get_color_texture(); depth_texture = _get_depth_texture(); + velocity_texture = _get_velocity_texture(); + velocity_depth_texture = _get_velocity_depth_texture(); // Per the WebXR spec, it returns "opaque textures" to us, which may be the // same WebGLTexture object (which would be the same GLuint in C++) but @@ -547,6 +549,24 @@ RID WebXRInterfaceJS::_get_depth_texture() { return _get_texture(texture_id); } +RID WebXRInterfaceJS::_get_velocity_texture() { + unsigned int texture_id = godot_webxr_get_velocity_texture(); + if (texture_id == 0) { + return RID(); + } + + return _get_texture(texture_id); +} + +RID WebXRInterfaceJS::_get_velocity_depth_texture() { + unsigned int texture_id = godot_webxr_get_velocity_depth_texture(); + if (texture_id == 0) { + return RID(); + } + + return _get_texture(texture_id); +} + RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) { RBMap::Element *cache = texture_cache.find(p_texture_id); if (cache != nullptr) { @@ -584,12 +604,27 @@ RID WebXRInterfaceJS::get_depth_texture() { } RID WebXRInterfaceJS::get_velocity_texture() { - unsigned int texture_id = godot_webxr_get_velocity_texture(); - if (texture_id == 0) { - return RID(); + return velocity_texture; +} + +RID WebXRInterfaceJS::get_velocity_depth_texture() { + return velocity_depth_texture; +} + +Size2i WebXRInterfaceJS::get_velocity_target_size() { + if (motion_vector_targetsize.width != 0 && motion_vector_targetsize.height != 0) { + return motion_vector_targetsize; } - return _get_texture(texture_id); + int js_size[2]; + bool has_size = godot_webxr_get_motion_vector_target_size(js_size); + + if (has_size) { + motion_vector_targetsize.width = js_size[0]; + motion_vector_targetsize.height = js_size[1]; + } + + return motion_vector_targetsize; } void WebXRInterfaceJS::process() { diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index d02c8d267770..d593520474cb 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -63,6 +63,7 @@ class WebXRInterfaceJS : public WebXRInterface { XRInterface::EnvironmentBlendMode environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE; Size2 render_targetsize; + Size2i motion_vector_targetsize; RBMap texture_cache; struct Touch { bool is_touching = false; @@ -84,9 +85,14 @@ class WebXRInterfaceJS : public WebXRInterface { RID color_texture; RID depth_texture; + RID velocity_texture; + RID velocity_depth_texture; RID _get_color_texture(); RID _get_depth_texture(); + RID _get_velocity_texture(); + RID _get_velocity_depth_texture(); + RID _get_texture(unsigned int p_texture_id); Transform3D _js_matrix_to_transform(float *p_js_matrix); void _update_input_source(int p_input_source_id); @@ -137,6 +143,8 @@ class WebXRInterfaceJS : public WebXRInterface { virtual RID get_color_texture() override; virtual RID get_depth_texture() override; virtual RID get_velocity_texture() override; + virtual RID get_velocity_depth_texture() override; + virtual Size2i get_velocity_target_size() override; virtual void process() override;