From 275c496bc87f6cf973cfa70bc1bf4a35ecd60d1e Mon Sep 17 00:00:00 2001 From: David Snopek Date: Wed, 8 Nov 2023 14:54:29 -0600 Subject: [PATCH] Add MSAA support for WebXR --- drivers/gles3/storage/config.cpp | 8 ++- .../storage/render_scene_buffers_gles3.cpp | 72 +++++++++++++------ .../storage/render_scene_buffers_gles3.h | 1 + drivers/gles3/storage/texture_storage.cpp | 14 ++++ drivers/gles3/storage/texture_storage.h | 3 + modules/webxr/webxr_interface_js.cpp | 38 ++++------ platform/web/godot_webgl2.h | 2 + .../js/libs/library_godot_webgl2.externs.js | 16 +++++ platform/web/js/libs/library_godot_webgl2.js | 17 +++++ 9 files changed, 122 insertions(+), 49 deletions(-) diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index 1200bf9626bf3..5d01ab03466fb 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -99,8 +99,14 @@ Config::Config() { msaa_supported = extensions.has("GL_EXT_framebuffer_multisample"); #endif #ifndef IOS_ENABLED +#ifdef WEB_ENABLED + msaa_multiview_supported = extensions.has("OCULUS_multiview"); + rt_msaa_multiview_supported = msaa_multiview_supported; +#else msaa_multiview_supported = extensions.has("GL_EXT_multiview_texture_multisample"); - multiview_supported = extensions.has("GL_OVR_multiview2") || extensions.has("GL_OVR_multiview"); +#endif + + multiview_supported = extensions.has("OCULUS_multiview") || extensions.has("GL_OVR_multiview2") || extensions.has("GL_OVR_multiview"); #endif #ifdef ANDROID_ENABLED diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index 33bc9ab2602dc..33bb8088565f6 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -51,10 +51,42 @@ RenderSceneBuffersGLES3::~RenderSceneBuffersGLES3() { free_render_buffer_data(); } +void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count) { + if (p_view_count > 1) { + if (p_samples > 1) { +#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) + glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, p_samples, 0, p_view_count); + glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, p_samples, 0, p_view_count); +#else + ERR_PRINT_ONCE("Multiview MSAA isn't supported on this platform."); +#endif + } else { +#ifndef IOS_ENABLED + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, 0, p_view_count); + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, 0, p_view_count); +#else + ERR_PRINT_ONCE("Multiview isn't supported on this platform."); +#endif + } + } else { + if (p_samples > 1) { +#ifdef ANDROID_ENABLED + glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0, p_samples); + glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0, p_samples); +#else + ERR_PRINT_ONCE("MSAA via EXT_multisampled_render_to_texture isn't supported on this platform."); +#endif + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0); + } + } +} + GLuint RenderSceneBuffersGLES3::_rt_get_cached_fbo(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count) { FBDEF new_fbo; -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) // There shouldn't be more then 3 entries in this... for (const FBDEF &cached_fbo : msaa3d.cached_fbos) { if (cached_fbo.color == p_color && cached_fbo.depth == p_depth) { @@ -68,13 +100,7 @@ GLuint RenderSceneBuffersGLES3::_rt_get_cached_fbo(GLuint p_color, GLuint p_dept glGenFramebuffers(1, &new_fbo.fbo); glBindFramebuffer(GL_FRAMEBUFFER, new_fbo.fbo); - if (p_view_count > 1) { - glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, p_samples, 0, p_view_count); - glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, p_samples, 0, p_view_count); - } else { - glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0, p_samples); - glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0, p_samples); - } + _rt_attach_textures(p_color, p_depth, p_samples, p_view_count); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { @@ -317,8 +343,6 @@ void RenderSceneBuffersGLES3::configure(const RenderSceneBuffersConfiguration *p // hence we'll use our FBO cache here. msaa3d.needs_resolve = false; msaa3d.check_fbo_cache = true; -#endif -#ifdef ANDROID_ENABLED } else if (use_internal_buffer) { // We can combine MSAA and scaling/effects. msaa3d.needs_resolve = false; @@ -329,13 +353,7 @@ void RenderSceneBuffersGLES3::configure(const RenderSceneBuffersConfiguration *p glGenFramebuffers(1, &msaa3d.fbo); glBindFramebuffer(GL_FRAMEBUFFER, msaa3d.fbo); - if (use_multiview) { - glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, internal3d.color, 0, msaa3d.samples, 0, view_count); - glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, internal3d.depth, 0, msaa3d.samples, 0, view_count); - } else { - glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, internal3d.color, 0, msaa3d.samples); - glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, internal3d.depth, 0, msaa3d.samples); - } + _rt_attach_textures(internal3d.color, internal3d.depth, msaa3d.samples, view_count); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { @@ -515,13 +533,14 @@ void RenderSceneBuffersGLES3::free_render_buffer_data() { } GLuint RenderSceneBuffersGLES3::get_render_fbo() { - if (msaa3d.check_fbo_cache) { - GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + GLuint rt_fbo = 0; + if (msaa3d.check_fbo_cache) { GLuint color = texture_storage->render_target_get_color(render_target); GLuint depth = texture_storage->render_target_get_depth(render_target); - return _rt_get_cached_fbo(color, depth, msaa3d.samples, view_count); + rt_fbo = _rt_get_cached_fbo(color, depth, msaa3d.samples, view_count); } else if (msaa3d.fbo != 0) { // We have an MSAA fbo, render to our MSAA buffer return msaa3d.fbo; @@ -529,10 +548,19 @@ GLuint RenderSceneBuffersGLES3::get_render_fbo() { // We have an internal buffer, render to our internal buffer! return internal3d.fbo; } else { - GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + rt_fbo = texture_storage->render_target_get_fbo(render_target); + } - return texture_storage->render_target_get_fbo(render_target); + if (texture_storage->render_target_is_reattach_textures(render_target)) { + GLuint color = texture_storage->render_target_get_color(render_target); + GLuint depth = texture_storage->render_target_get_depth(render_target); + + glBindFramebuffer(GL_FRAMEBUFFER, rt_fbo); + _rt_attach_textures(color, depth, msaa3d.samples, view_count); + glBindFramebuffer(GL_FRAMEBUFFER, texture_storage->system_fbo); } + + return rt_fbo; } #endif // GLES3_ENABLED diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.h b/drivers/gles3/storage/render_scene_buffers_gles3.h index 95097f54d8804..7a6811bf3c79c 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.h +++ b/drivers/gles3/storage/render_scene_buffers_gles3.h @@ -95,6 +95,7 @@ class RenderSceneBuffersGLES3 : public RenderSceneBuffers { void _clear_intermediate_buffers(); void _clear_back_buffers(); + void _rt_attach_textures(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count); GLuint _rt_get_cached_fbo(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count); public: diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 77dd15fa28d4c..4e34fbcf0a270 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -2320,6 +2320,20 @@ GLuint TextureStorage::render_target_get_depth(RID p_render_target) const { } } +void TextureStorage::render_target_set_reattach_textures(RID p_render_target, bool p_reattach_textures) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_NULL(rt); + + rt->reattach_textures = p_reattach_textures; +} + +bool TextureStorage::render_target_is_reattach_textures(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_NULL_V(rt, false); + + return rt->reattach_textures; +} + void TextureStorage::render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_NULL(rt); diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 2ce719eb08714..a4e5eb260e58a 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -365,6 +365,7 @@ struct RenderTarget { bool used_in_frame = false; RS::ViewportMSAA msaa = RS::VIEWPORT_MSAA_DISABLED; + bool reattach_textures = false; struct RTOverridden { bool is_overridden = false; @@ -639,6 +640,8 @@ class TextureStorage : public RendererTextureStorage { GLuint render_target_get_fbo(RID p_render_target) const; GLuint render_target_get_color(RID p_render_target) const; GLuint render_target_get_depth(RID p_render_target) const; + void render_target_set_reattach_textures(RID p_render_target, bool p_reattach_textures) const; + bool render_target_is_reattach_textures(RID p_render_target) const; virtual void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) override; virtual Rect2i render_target_get_sdf_rect(RID p_render_target) const override; diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 47f20ce1a32ab..828476edfb404 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -309,7 +309,7 @@ void WebXRInterfaceJS::uninitialize() { godot_webxr_uninitialize(); - GLES3::TextureStorage *texture_storage = dynamic_cast(RSG::texture_storage); + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); if (texture_storage != nullptr) { for (KeyValue &E : texture_cache) { // Forcibly mark as not part of a render target so we can free it. @@ -438,16 +438,11 @@ Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_a } bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) { - GLES3::TextureStorage *texture_storage = dynamic_cast(RSG::texture_storage); + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); if (texture_storage == nullptr) { return false; } - GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target); - if (rt == nullptr) { - return false; - } - // Cache the resources so we don't have to get them from JS twice. color_texture = _get_color_texture(); depth_texture = _get_depth_texture(); @@ -460,23 +455,9 @@ bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) { // // See: https://immersive-web.github.io/layers/#xropaquetextures // - // This is why we're doing this sort of silly check: if the color and depth - // textures are the same this frame as last frame, we need to attach them - // again, despite the fact that the GLuint for them hasn't changed. - if (rt->overridden.is_overridden && rt->overridden.color == color_texture && rt->overridden.depth == depth_texture) { - GLES3::Config *config = GLES3::Config::get_singleton(); - bool use_multiview = rt->view_count > 1 && config->multiview_supported; - - glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo); - if (use_multiview) { - glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->color, 0, 0, rt->view_count); - glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->color, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0); - } - glBindFramebuffer(GL_FRAMEBUFFER, texture_storage->system_fbo); - } + // So, even if the color and depth textures have the same GLuint as the last + // frame, we need to re-attach them again. + texture_storage->render_target_set_reattach_textures(p_render_target, true); return true; } @@ -484,7 +465,12 @@ bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) { Vector WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { Vector blit_to_screen; - // We don't need to do anything here. + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + if (texture_storage == nullptr) { + return blit_to_screen; + } + + texture_storage->render_target_set_reattach_textures(p_render_target, false); return blit_to_screen; }; @@ -513,7 +499,7 @@ RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) { return cache->get(); } - GLES3::TextureStorage *texture_storage = dynamic_cast(RSG::texture_storage); + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); if (texture_storage == nullptr) { return RID(); } diff --git a/platform/web/godot_webgl2.h b/platform/web/godot_webgl2.h index 3ade9e423915f..2c9af4313fb89 100644 --- a/platform/web/godot_webgl2.h +++ b/platform/web/godot_webgl2.h @@ -44,9 +44,11 @@ extern "C" { #endif void godot_webgl2_glFramebufferTextureMultiviewOVR(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +void godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR(GLenum target, GLenum attachment, GLuint texture, GLint level, GLsizei samples, GLint baseViewIndex, GLsizei numViews); void godot_webgl2_glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); #define glFramebufferTextureMultiviewOVR godot_webgl2_glFramebufferTextureMultiviewOVR +#define glFramebufferTextureMultisampleMultiviewOVR godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR #ifdef __cplusplus } diff --git a/platform/web/js/libs/library_godot_webgl2.externs.js b/platform/web/js/libs/library_godot_webgl2.externs.js index 18d8d0815a771..99190ab009beb 100644 --- a/platform/web/js/libs/library_godot_webgl2.externs.js +++ b/platform/web/js/libs/library_godot_webgl2.externs.js @@ -34,3 +34,19 @@ OVR_multiview2.prototype.FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR; * @return {void} */ OVR_multiview2.prototype.framebufferTextureMultiviewOVR = function(target, attachment, texture, level, baseViewIndex, numViews) {}; + +/** + * @constructor OCULUS_multiview + */ +function OCULUS_multiview() {} + +/** + * @param {number} target + * @param {number} attachment + * @param {WebGLTexture} texture + * @param {number} level + * @param {number} baseViewIndex + * @param {number} numViews + * @return {void} + */ +OCULUS_multiview.prototype.framebufferTextureMultisampleMultiviewOVR = function(target, attachment, texture, level, samples, baseViewIndex, numViews) {}; diff --git a/platform/web/js/libs/library_godot_webgl2.js b/platform/web/js/libs/library_godot_webgl2.js index dbaec9f01b1c9..005b4b7917712 100644 --- a/platform/web/js/libs/library_godot_webgl2.js +++ b/platform/web/js/libs/library_godot_webgl2.js @@ -61,6 +61,23 @@ const GodotWebGL2 = { const /** OVR_multiview2 */ ext = context.multiviewExt; ext.framebufferTextureMultiviewOVR(target, attachment, GL.textures[texture], level, base_view_index, num_views); }, + + godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR__deps: ['emscripten_webgl_get_current_context'], + godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR__proxy: 'sync', + godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR__sig: 'viiiiiii', + godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR: function (target, attachment, texture, level, samples, base_view_index, num_views) { + const context = GL.currentContext; + if (typeof context.oculusMultiviewExt === 'undefined') { + const /** OCULUS_multiview */ ext = context.GLctx.getExtension('OCULUS_multiview'); + if (!ext) { + GodotRuntime.error('Trying to call glFramebufferTextureMultisampleMultiviewOVR() without the OCULUS_multiview extension'); + return; + } + context.oculusMultiviewExt = ext; + } + const /** OCULUS_multiview */ ext = context.oculusMultiviewExt; + ext.framebufferTextureMultisampleMultiviewOVR(target, attachment, GL.textures[texture], level, samples, base_view_index, num_views); + }, }; autoAddDeps(GodotWebGL2, '$GodotWebGL2');