From 9721a27427fa5342de34eaf9fea30b47748e3df5 Mon Sep 17 00:00:00 2001 From: clayjohn Date: Mon, 12 Jun 2023 21:51:23 -0700 Subject: [PATCH] Use Gaussian approximation for backbuffer mipmaps in GL Compatibility renderer --- drivers/gles3/effects/copy_effects.cpp | 76 +++++++++++++++++++++++ drivers/gles3/effects/copy_effects.h | 1 + drivers/gles3/shaders/copy.glsl | 39 +++++++++++- drivers/gles3/storage/texture_storage.cpp | 14 +++-- 4 files changed, 122 insertions(+), 8 deletions(-) diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index b8c56018dcfb..658c0e6145d8 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -31,6 +31,7 @@ #ifdef GLES3_ENABLED #include "copy_effects.h" +#include "../storage/texture_storage.h" using namespace GLES3; @@ -133,6 +134,7 @@ void CopyEffects::copy_screen() { draw_screen_triangle(); } +// Intended for efficiently mipmapping textures. void CopyEffects::bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region) { GLuint framebuffers[2]; glGenFramebuffers(2, framebuffers); @@ -158,6 +160,80 @@ void CopyEffects::bilinear_blur(GLuint p_source_texture, int p_mipmap_count, con glDeleteFramebuffers(2, framebuffers); } +// Intended for approximating a gaussian blur. Used for 2D backbuffer mipmaps. Slightly less efficient than bilinear_blur(). +void CopyEffects::gaussian_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region, const Size2i &p_size) { + GLuint framebuffer; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, p_source_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + Size2i base_size = p_size; + + Rect2i source_region = p_region; + Rect2i dest_region = p_region; + + Size2 float_size = Size2(p_size); + Rect2 normalized_source_region = Rect2(p_region); + normalized_source_region.position = normalized_source_region.position / float_size; + normalized_source_region.size = normalized_source_region.size / float_size; + Rect2 normalized_dest_region = Rect2(p_region); + for (int i = 1; i < p_mipmap_count; i++) { + dest_region.position.x >>= 1; + dest_region.position.y >>= 1; + dest_region.size.x = MAX(1, dest_region.size.x >> 1); + dest_region.size.y = MAX(1, dest_region.size.y >> 1); + base_size.x >>= 1; + base_size.y >>= 1; + + glBindTexture(GL_TEXTURE_2D, p_source_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, i - 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i - 1); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_source_texture, i); +#ifdef DEV_ENABLED + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + WARN_PRINT("Could not bind Gaussian blur framebuffer, status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); + } +#endif + + glViewport(0, 0, base_size.x, base_size.y); + + bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR); + if (!success) { + return; + } + + float_size = Size2(base_size); + normalized_dest_region.position = Size2(dest_region.position) / float_size; + normalized_dest_region.size = Size2(dest_region.size) / float_size; + + copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, normalized_dest_region.position.x, normalized_dest_region.position.y, normalized_dest_region.size.x, normalized_dest_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR); + copy.shader.version_set_uniform(CopyShaderGLES3::SOURCE_SECTION, normalized_source_region.position.x, normalized_source_region.position.y, normalized_source_region.size.x, normalized_source_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR); + copy.shader.version_set_uniform(CopyShaderGLES3::PIXEL_SIZE, 1.0 / float_size.x, 1.0 / float_size.y, copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR); + + draw_screen_quad(); + + source_region = dest_region; + normalized_source_region = normalized_dest_region; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &framebuffer); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_mipmap_count - 1); + glBindTexture(GL_TEXTURE_2D, 0); + + glViewport(0, 0, p_size.x, p_size.y); +} + void CopyEffects::set_color(const Color &p_color, const Rect2i &p_region) { bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR); if (!success) { diff --git a/drivers/gles3/effects/copy_effects.h b/drivers/gles3/effects/copy_effects.h index 105fbcfe98bb..b2bceb84af74 100644 --- a/drivers/gles3/effects/copy_effects.h +++ b/drivers/gles3/effects/copy_effects.h @@ -64,6 +64,7 @@ class CopyEffects { void copy_to_rect(const Rect2 &p_rect); void copy_screen(); void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region); + void gaussian_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region, const Size2i &p_size); void set_color(const Color &p_color, const Rect2i &p_region); void draw_screen_triangle(); void draw_screen_quad(); diff --git a/drivers/gles3/shaders/copy.glsl b/drivers/gles3/shaders/copy.glsl index 796ba79c2eed..3a8556955516 100644 --- a/drivers/gles3/shaders/copy.glsl +++ b/drivers/gles3/shaders/copy.glsl @@ -16,17 +16,24 @@ layout(location = 0) in vec2 vertex_attrib; out vec2 uv_interp; /* clang-format on */ -#ifdef USE_COPY_SECTION +#if defined(USE_COPY_SECTION) || defined(MODE_GAUSSIAN_BLUR) +// Defined in 0-1 coords. uniform highp vec4 copy_section; #endif +#ifdef MODE_GAUSSIAN_BLUR +uniform highp vec4 source_section; +#endif void main() { uv_interp = vertex_attrib * 0.5 + 0.5; gl_Position = vec4(vertex_attrib, 1.0, 1.0); -#ifdef USE_COPY_SECTION +#if defined(USE_COPY_SECTION) || defined(MODE_GAUSSIAN_BLUR) gl_Position.xy = (copy_section.xy + uv_interp.xy * copy_section.zw) * 2.0 - 1.0; #endif +#ifdef MODE_GAUSSIAN_BLUR + uv_interp = source_section.xy + uv_interp * source_section.zw; +#endif } /* clang-format off */ @@ -39,6 +46,7 @@ uniform vec4 color_in; #endif #ifdef MODE_GAUSSIAN_BLUR +// Defined in 0-1 coords. uniform highp vec2 pixel_size; #endif @@ -55,4 +63,31 @@ void main() { #ifdef MODE_SIMPLE_COLOR frag_color = color_in; #endif + +// Efficient box filter from Jimenez: http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare +// Approximates a Gaussian in a single pass. +#ifdef MODE_GAUSSIAN_BLUR + vec4 A = textureLod(source, uv_interp + pixel_size * vec2(-1.0, -1.0), 0.0); + vec4 B = textureLod(source, uv_interp + pixel_size * vec2(0.0, -1.0), 0.0); + vec4 C = textureLod(source, uv_interp + pixel_size * vec2(1.0, -1.0), 0.0); + vec4 D = textureLod(source, uv_interp + pixel_size * vec2(-0.5, -0.5), 0.0); + vec4 E = textureLod(source, uv_interp + pixel_size * vec2(0.5, -0.5), 0.0); + vec4 F = textureLod(source, uv_interp + pixel_size * vec2(-1.0, 0.0), 0.0); + vec4 G = textureLod(source, uv_interp, 0.0); + vec4 H = textureLod(source, uv_interp + pixel_size * vec2(1.0, 0.0), 0.0); + vec4 I = textureLod(source, uv_interp + pixel_size * vec2(-0.5, 0.5), 0.0); + vec4 J = textureLod(source, uv_interp + pixel_size * vec2(0.5, 0.5), 0.0); + vec4 K = textureLod(source, uv_interp + pixel_size * vec2(-1.0, 1.0), 0.0); + vec4 L = textureLod(source, uv_interp + pixel_size * vec2(0.0, 1.0), 0.0); + vec4 M = textureLod(source, uv_interp + pixel_size * vec2(1.0, 1.0), 0.0); + + float weight = 0.5 / 4.0; + float lesser_weight = 0.125 / 4.0; + + frag_color = (D + E + I + J) * weight; + frag_color += (A + B + G + F) * lesser_weight; + frag_color += (B + C + H + G) * lesser_weight; + frag_color += (F + G + L + K) * lesser_weight; + frag_color += (G + H + M + L) * lesser_weight; +#endif } diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index c4fef89cfd23..fd187527ac2b 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1827,10 +1827,10 @@ void TextureStorage::_create_render_target_backbuffer(RenderTarget *rt) { return; } - // Initialize all levels to opaque Magenta. + // Initialize all levels to clear black. for (int j = 0; j < count; j++) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->backbuffer, j); - glClearColor(1.0, 0.0, 1.0, 1.0); + glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } @@ -2548,18 +2548,18 @@ void TextureStorage::render_target_copy_to_back_buffer(RID p_render_target, cons } glDisable(GL_BLEND); - //single texture copy for backbuffer + // Single texture copy for backbuffer. glBindFramebuffer(GL_FRAMEBUFFER, rt->backbuffer_fbo); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, rt->color); GLES3::CopyEffects::get_singleton()->copy_screen(); if (p_gen_mipmaps) { - GLES3::CopyEffects::get_singleton()->bilinear_blur(rt->backbuffer, rt->mipmap_count, region); + GLES3::CopyEffects::get_singleton()->gaussian_blur(rt->backbuffer, rt->mipmap_count, region, rt->size); glBindFramebuffer(GL_FRAMEBUFFER, rt->backbuffer_fbo); } - glEnable(GL_BLEND); // 2D almost always uses blend. + glEnable(GL_BLEND); // 2D starts with blend enabled. } void TextureStorage::render_target_clear_back_buffer(RID p_render_target, const Rect2i &p_region, const Color &p_color) { @@ -2604,8 +2604,10 @@ void TextureStorage::render_target_gen_back_buffer_mipmaps(RID p_render_target, return; //nothing to do } } + glDisable(GL_BLEND); + GLES3::CopyEffects::get_singleton()->gaussian_blur(rt->backbuffer, rt->mipmap_count, region, rt->size); + glEnable(GL_BLEND); // 2D starts with blend enabled. - GLES3::CopyEffects::get_singleton()->bilinear_blur(rt->backbuffer, rt->mipmap_count, region); glBindFramebuffer(GL_FRAMEBUFFER, rt->backbuffer_fbo); }