From 48cf3764626e1ed30450d15e00befb75a4eae329 Mon Sep 17 00:00:00 2001
From: Marshall Mohror <mohror64@gmail.com>
Date: Wed, 20 Oct 2021 18:36:06 -0500
Subject: [PATCH] OpenGL: Implement FXAA

---
 src/video_core/host_shaders/CMakeLists.txt    |   2 +
 src/video_core/host_shaders/fxaa.frag         |  72 ++++++++++++
 src/video_core/host_shaders/fxaa.vert         |  40 +++++++
 .../renderer_opengl/gl_resource_manager.cpp   |   2 +-
 .../renderer_opengl/renderer_opengl.cpp       | 109 ++++++++++++------
 .../renderer_opengl/renderer_opengl.h         |   4 +
 6 files changed, 194 insertions(+), 35 deletions(-)
 create mode 100644 src/video_core/host_shaders/fxaa.frag
 create mode 100644 src/video_core/host_shaders/fxaa.vert

diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index b0e15773cc..6b5ea649a0 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -13,6 +13,8 @@ set(SHADER_FILES
     convert_depth_to_float.frag
     convert_float_to_depth.frag
     full_screen_triangle.vert
+    fxaa.frag
+    fxaa.vert
     opengl_copy_bc4.comp
     opengl_present.frag
     opengl_present.vert
diff --git a/src/video_core/host_shaders/fxaa.frag b/src/video_core/host_shaders/fxaa.frag
new file mode 100644
index 0000000000..23f910d4cb
--- /dev/null
+++ b/src/video_core/host_shaders/fxaa.frag
@@ -0,0 +1,72 @@
+// Adapted from
+// https://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/
+
+#version 460
+
+#ifdef VULKAN
+
+#define BINDING_COLOR_TEXTURE 1
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#define BINDING_COLOR_TEXTURE 0
+
+#endif
+
+layout (location = 0) in vec4 posPos;
+
+layout (location = 0) out vec4 frag_color;
+
+layout (binding = BINDING_COLOR_TEXTURE) uniform sampler2D input_texture;
+
+const float FXAA_SPAN_MAX = 8.0;
+const float FXAA_REDUCE_MUL = 1.0 / 8.0;
+const float FXAA_REDUCE_MIN = 1.0 / 128.0;
+
+#define FxaaTexLod0(t, p) textureLod(t, p, 0.0)
+#define FxaaTexOff(t, p, o) textureLodOffset(t, p, 0.0, o)
+
+vec3 FxaaPixelShader(vec4 posPos, sampler2D tex) {
+
+    vec3 rgbNW = FxaaTexLod0(tex, posPos.zw).xyz;
+    vec3 rgbNE = FxaaTexOff(tex, posPos.zw, ivec2(1,0)).xyz;
+    vec3 rgbSW = FxaaTexOff(tex, posPos.zw, ivec2(0,1)).xyz;
+    vec3 rgbSE = FxaaTexOff(tex, posPos.zw, ivec2(1,1)).xyz;
+    vec3 rgbM  = FxaaTexLod0(tex, posPos.xy).xyz;
+/*---------------------------------------------------------*/
+    vec3 luma = vec3(0.299, 0.587, 0.114);
+    float lumaNW = dot(rgbNW, luma);
+    float lumaNE = dot(rgbNE, luma);
+    float lumaSW = dot(rgbSW, luma);
+    float lumaSE = dot(rgbSE, luma);
+    float lumaM  = dot(rgbM,  luma);
+/*---------------------------------------------------------*/
+    float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+    float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+/*---------------------------------------------------------*/
+    vec2 dir;
+    dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+    dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+/*---------------------------------------------------------*/
+    float dirReduce = max(
+        (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),
+        FXAA_REDUCE_MIN);
+    float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
+    dir = min(vec2( FXAA_SPAN_MAX,  FXAA_SPAN_MAX),
+          max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
+          dir * rcpDirMin)) / textureSize(tex, 0);
+/*--------------------------------------------------------*/
+    vec3 rgbA = (1.0 / 2.0) * (
+        FxaaTexLod0(tex, posPos.xy + dir * (1.0 / 3.0 - 0.5)).xyz +
+        FxaaTexLod0(tex, posPos.xy + dir * (2.0 / 3.0 - 0.5)).xyz);
+    vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * (
+        FxaaTexLod0(tex, posPos.xy + dir * (0.0 / 3.0 - 0.5)).xyz +
+        FxaaTexLod0(tex, posPos.xy + dir * (3.0 / 3.0 - 0.5)).xyz);
+    float lumaB = dot(rgbB, luma);
+    if((lumaB < lumaMin) || (lumaB > lumaMax)) return rgbA;
+    return rgbB;
+}
+
+void main() {
+  frag_color = vec4(FxaaPixelShader(posPos, input_texture), 1.0);
+}
diff --git a/src/video_core/host_shaders/fxaa.vert b/src/video_core/host_shaders/fxaa.vert
new file mode 100644
index 0000000000..715fce462c
--- /dev/null
+++ b/src/video_core/host_shaders/fxaa.vert
@@ -0,0 +1,40 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 460
+
+out gl_PerVertex {
+    vec4 gl_Position;
+};
+
+const vec2 vertices[4] =
+    vec2[4](vec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, -1.0));
+
+layout (location = 0) out vec4 posPos;
+
+#ifdef VULKAN
+
+#define BINDING_COLOR_TEXTURE 1
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#define BINDING_COLOR_TEXTURE 0
+
+#endif
+
+layout (binding = BINDING_COLOR_TEXTURE) uniform sampler2D input_texture;
+
+const float FXAA_SUBPIX_SHIFT = 0;
+
+void main() {
+#ifdef VULKAN
+  vec2 vertex = vertices[gl_VertexIndex];
+#else
+  vec2 vertex = vertices[gl_VertexID];
+#endif
+  gl_Position = vec4(vertex, 0.0, 1.0);
+  vec2 vert_tex_coord = (vertex + 1.0) / 2.0;
+  posPos.xy = vert_tex_coord;
+  posPos.zw = vert_tex_coord - (0.5 + FXAA_SUBPIX_SHIFT) / textureSize(input_texture, 0);
+}
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index 8695c29e34..70947838cc 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -166,7 +166,7 @@ void OGLFramebuffer::Create() {
         return;
 
     MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
-    glGenFramebuffers(1, &handle);
+    glCreateFramebuffers(1, &handle);
 }
 
 void OGLFramebuffer::Release() {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 227697c4f7..dbe66a1b6b 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -21,6 +21,8 @@
 #include "core/memory.h"
 #include "core/perf_stats.h"
 #include "core/telemetry_session.h"
+#include "video_core/host_shaders/fxaa_frag.h"
+#include "video_core/host_shaders/fxaa_vert.h"
 #include "video_core/host_shaders/opengl_present_frag.h"
 #include "video_core/host_shaders/opengl_present_vert.h"
 #include "video_core/host_shaders/present_bicubic_frag.h"
@@ -254,6 +256,8 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
 
 void RendererOpenGL::InitOpenGLObjects() {
     // Create shader programs
+    fxaa_vertex = CreateProgram(HostShaders::FXAA_VERT, GL_VERTEX_SHADER);
+    fxaa_fragment = CreateProgram(HostShaders::FXAA_FRAG, GL_FRAGMENT_SHADER);
     present_vertex = CreateProgram(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER);
     present_bilinear_fragment = CreateProgram(HostShaders::OPENGL_PRESENT_FRAG, GL_FRAGMENT_SHADER);
     present_bicubic_fragment = CreateProgram(HostShaders::PRESENT_BICUBIC_FRAG, GL_FRAGMENT_SHADER);
@@ -287,6 +291,8 @@ void RendererOpenGL::InitOpenGLObjects() {
 
     // Clear screen to black
     LoadColorToActiveGLTexture(0, 0, 0, 0, screen_info.texture);
+
+    fxaa_framebuffer.Create();
 }
 
 void RendererOpenGL::AddTelemetryFields() {
@@ -338,14 +344,83 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
     texture.resource.Release();
     texture.resource.Create(GL_TEXTURE_2D);
     glTextureStorage2D(texture.resource.handle, 1, internal_format, texture.width, texture.height);
+    fxaa_texture.Release();
+    fxaa_texture.Create(GL_TEXTURE_2D);
+    glTextureStorage2D(fxaa_texture.handle, 1, GL_RGBA16F, texture.width, texture.height);
+    glNamedFramebufferTexture(fxaa_framebuffer.handle, GL_COLOR_ATTACHMENT0, fxaa_texture.handle,
+                              0);
 }
 
 void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
+    // TODO: Signal state tracker about these changes
+    state_tracker.NotifyScreenDrawVertexArray();
+    state_tracker.NotifyPolygonModes();
+    state_tracker.NotifyViewport0();
+    state_tracker.NotifyScissor0();
+    state_tracker.NotifyColorMask(0);
+    state_tracker.NotifyBlend0();
+    state_tracker.NotifyFramebuffer();
+    state_tracker.NotifyFrontFace();
+    state_tracker.NotifyCullTest();
+    state_tracker.NotifyDepthTest();
+    state_tracker.NotifyStencilTest();
+    state_tracker.NotifyPolygonOffset();
+    state_tracker.NotifyRasterizeEnable();
+    state_tracker.NotifyFramebufferSRGB();
+    state_tracker.NotifyLogicOp();
+    state_tracker.NotifyClipControl();
+    state_tracker.NotifyAlphaTest();
+
+    state_tracker.ClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
+
     // Update background color before drawing
     glClearColor(Settings::values.bg_red.GetValue() / 255.0f,
                  Settings::values.bg_green.GetValue() / 255.0f,
                  Settings::values.bg_blue.GetValue() / 255.0f, 1.0f);
 
+    glEnable(GL_CULL_FACE);
+    glDisable(GL_COLOR_LOGIC_OP);
+    glDisable(GL_DEPTH_TEST);
+    glDisable(GL_STENCIL_TEST);
+    glDisable(GL_POLYGON_OFFSET_FILL);
+    glDisable(GL_RASTERIZER_DISCARD);
+    glDisable(GL_ALPHA_TEST);
+    glDisablei(GL_BLEND, 0);
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    glCullFace(GL_BACK);
+    glFrontFace(GL_CW);
+    glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+    glBindTextureUnit(0, screen_info.display_texture);
+
+    if (Settings::values.anti_aliasing.GetValue() == Settings::AntiAliasing::Fxaa) {
+        program_manager.BindPresentPrograms(fxaa_vertex.handle, fxaa_fragment.handle);
+
+        glEnablei(GL_SCISSOR_TEST, 0);
+        glScissorIndexed(0, 0, 0,
+                         framebuffer_crop_rect.GetWidth() != 0 ? framebuffer_crop_rect.GetWidth()
+                                                               : screen_info.texture.width,
+                         framebuffer_crop_rect.GetHeight() != 0 ? framebuffer_crop_rect.GetHeight()
+                                                                : screen_info.texture.height);
+        glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(screen_info.texture.width),
+                           static_cast<GLfloat>(screen_info.texture.height));
+        glDepthRangeIndexed(0, 0.0, 0.0);
+
+        glBindSampler(0, present_sampler.handle);
+        GLint old_read_fb;
+        GLint old_draw_fb;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fxaa_framebuffer.handle);
+
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+
+        glBindTextureUnit(0, fxaa_texture.handle);
+    }
+
     // Set projection matrix
     const std::array ortho_matrix =
         MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height));
@@ -422,47 +497,14 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
     };
     glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices));
 
-    // TODO: Signal state tracker about these changes
-    state_tracker.NotifyScreenDrawVertexArray();
-    state_tracker.NotifyPolygonModes();
-    state_tracker.NotifyViewport0();
-    state_tracker.NotifyScissor0();
-    state_tracker.NotifyColorMask(0);
-    state_tracker.NotifyBlend0();
-    state_tracker.NotifyFramebuffer();
-    state_tracker.NotifyFrontFace();
-    state_tracker.NotifyCullTest();
-    state_tracker.NotifyDepthTest();
-    state_tracker.NotifyStencilTest();
-    state_tracker.NotifyPolygonOffset();
-    state_tracker.NotifyRasterizeEnable();
-    state_tracker.NotifyFramebufferSRGB();
-    state_tracker.NotifyLogicOp();
-    state_tracker.NotifyClipControl();
-    state_tracker.NotifyAlphaTest();
-
-    state_tracker.ClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
-    glEnable(GL_CULL_FACE);
     if (screen_info.display_srgb) {
         glEnable(GL_FRAMEBUFFER_SRGB);
     } else {
         glDisable(GL_FRAMEBUFFER_SRGB);
     }
-    glDisable(GL_COLOR_LOGIC_OP);
-    glDisable(GL_DEPTH_TEST);
-    glDisable(GL_STENCIL_TEST);
-    glDisable(GL_POLYGON_OFFSET_FILL);
-    glDisable(GL_RASTERIZER_DISCARD);
-    glDisable(GL_ALPHA_TEST);
-    glDisablei(GL_BLEND, 0);
     glDisablei(GL_SCISSOR_TEST, 0);
-    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-    glCullFace(GL_BACK);
-    glFrontFace(GL_CW);
-    glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
     glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width),
                        static_cast<GLfloat>(layout.height));
-    glDepthRangeIndexed(0, 0.0, 0.0);
 
     glEnableVertexAttribArray(PositionLocation);
     glEnableVertexAttribArray(TexCoordLocation);
@@ -482,7 +524,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
         glBindVertexBuffer(0, vertex_buffer.handle, 0, sizeof(ScreenRectVertex));
     }
 
-    glBindTextureUnit(0, screen_info.display_texture);
     if (Settings::values.scaling_filter.GetValue() != Settings::ScalingFilter::NearestNeighbor) {
         glBindSampler(0, present_sampler.handle);
     } else {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 62a746e412..f6c66f8045 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -111,6 +111,8 @@ private:
     OGLSampler present_sampler;
     OGLSampler present_sampler_nn;
     OGLBuffer vertex_buffer;
+    OGLProgram fxaa_vertex;
+    OGLProgram fxaa_fragment;
     OGLProgram present_vertex;
     OGLProgram present_bilinear_fragment;
     OGLProgram present_bicubic_fragment;
@@ -123,6 +125,8 @@ private:
 
     /// Display information for Switch screen
     ScreenInfo screen_info;
+    OGLTexture fxaa_texture;
+    OGLFramebuffer fxaa_framebuffer;
 
     /// OpenGL framebuffer data
     std::vector<u8> gl_framebuffer_data;