Skip to content

Commit

Permalink
WebXR: Add support for Space Warp
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnopek committed Nov 19, 2024
1 parent b5c7820 commit 889c6e5
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 35 deletions.
2 changes: 1 addition & 1 deletion drivers/gles3/rasterizer_scene_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2472,7 +2472,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &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);
Expand Down
77 changes: 58 additions & 19 deletions drivers/gles3/storage/texture_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2224,46 +2224,46 @@ 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);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#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);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

#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);
if (status != GL_FRAMEBUFFER_COMPLETE) {
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);
Expand Down Expand Up @@ -2390,11 +2390,13 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) {
return;
}

for (KeyValue<uint32_t, GLuint> &E : rt->overridden.velocity_fbo_cache) {
glDeleteFramebuffers(1, &E.value);
for (KeyValue<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry> &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<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry> &E : rt->overridden.fbo_cache) {
Expand Down Expand Up @@ -2582,19 +2584,52 @@ 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<uint32_t, GLuint>::Element *fbo = rt->overridden.velocity_fbo_cache.find(velocity_hash_key);
if (fbo != nullptr) {
rt->overridden.velocity_fbo = fbo->get();
RBMap<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry>::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) {
uint32_t view_count = rt->view_count;

glBindFramebuffer(GL_FRAMEBUFFER, rt->velocity_fbo);

#ifndef IOS_ENABLED
if (view_count > 1) {
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, rt->velocity_texture, 0);
}

#ifndef IOS_ENABLED
if (view_count > 1) {
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, rt->velocity_depth_texture, 0);
}

glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}

if (p_velocity_texture.is_null()) {
for (KeyValue<uint32_t, GLuint> &E : rt->overridden.velocity_fbo_cache) {
glDeleteFramebuffers(1, &E.value);
for (KeyValue<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry> &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;
}

Expand All @@ -2618,7 +2653,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);
}
}

Expand Down
7 changes: 4 additions & 3 deletions drivers/gles3/storage/texture_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -389,9 +392,7 @@ struct RenderTarget {
Vector<GLuint> allocated_textures;
};
RBMap<uint32_t, FBOCacheEntry> fbo_cache;

GLuint velocity_fbo = 0;
RBMap<uint32_t, GLuint> velocity_fbo_cache;
RBMap<uint32_t, FBOCacheEntry> velocity_fbo_cache;
} overridden;

RID texture;
Expand Down
2 changes: 2 additions & 0 deletions modules/webxr/godot_webxr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
55 changes: 47 additions & 8 deletions modules/webxr/native/library_godot_webxr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -85,7 +88,9 @@ const GodotWebXR = {
},

getLayer: () => {
const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : 1;
const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length :
// 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.
((GodotWebXR.required_features.includes("space-warp") || GodotWebXR.optional_features.includes("space-warp")) ? 2 : 1);
let layer = GodotWebXR.layer;

// If the view count hasn't changed since creating this layer, then
Expand Down Expand Up @@ -239,8 +244,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);
Expand All @@ -249,11 +254,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) {
Expand Down Expand Up @@ -466,7 +471,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);
Expand All @@ -485,6 +492,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) {
Expand Down
43 changes: 39 additions & 4 deletions modules/webxr/webxr_interface_js.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<unsigned int, RID>::Element *cache = texture_cache.find(p_texture_id);
if (cache != nullptr) {
Expand Down Expand Up @@ -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() {
Expand Down
8 changes: 8 additions & 0 deletions modules/webxr/webxr_interface_js.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned int, RID> texture_cache;
struct Touch {
bool is_touching = false;
Expand All @@ -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);
Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 889c6e5

Please sign in to comment.