mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-04-15 09:10:46 -04:00
Merge branch 'taa-implementation' into 'main'
feat: add Temporal Anti-Aliasing (TAA) support for OpenGL and Vulkan See merge request citron/emulator!11
This commit is contained in:
@@ -215,12 +215,14 @@
|
|||||||
<item>@string/anti_aliasing_none</item>
|
<item>@string/anti_aliasing_none</item>
|
||||||
<item>@string/anti_aliasing_fxaa</item>
|
<item>@string/anti_aliasing_fxaa</item>
|
||||||
<item>@string/anti_aliasing_smaa</item>
|
<item>@string/anti_aliasing_smaa</item>
|
||||||
|
<item>@string/anti_aliasing_taa</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<integer-array name="rendererAntiAliasingValues">
|
<integer-array name="rendererAntiAliasingValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<item>2</item>
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="cpuBackendArm64Names">
|
<string-array name="cpuBackendArm64Names">
|
||||||
|
|||||||
@@ -641,6 +641,7 @@
|
|||||||
<string name="anti_aliasing_none">None</string>
|
<string name="anti_aliasing_none">None</string>
|
||||||
<string name="anti_aliasing_fxaa">FXAA</string>
|
<string name="anti_aliasing_fxaa">FXAA</string>
|
||||||
<string name="anti_aliasing_smaa">SMAA</string>
|
<string name="anti_aliasing_smaa">SMAA</string>
|
||||||
|
<string name="anti_aliasing_taa">TAA</string>
|
||||||
|
|
||||||
<!-- Screen Layouts -->
|
<!-- Screen Layouts -->
|
||||||
<string name="screen_layout_auto">Auto</string>
|
<string name="screen_layout_auto">Auto</string>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map =
|
|||||||
{Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
|
{Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
|
||||||
{Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
|
{Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
|
||||||
{Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
|
{Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
|
||||||
|
{Settings::AntiAliasing::Taa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "TAA"))},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
|
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ ENUM(ResolutionSetup, Res1_2X, Res3_4X, Res1X, Res3_2X, Res2X, Res3X, Res4X, Res
|
|||||||
|
|
||||||
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, Fsr2, MaxEnum);
|
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, Fsr2, MaxEnum);
|
||||||
|
|
||||||
ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
|
ENUM(AntiAliasing, None, Fxaa, Smaa, Taa, MaxEnum);
|
||||||
|
|
||||||
ENUM(FSR2QualityMode, Quality, Balanced, Performance, UltraPerformance);
|
ENUM(FSR2QualityMode, Quality, Balanced, Performance, UltraPerformance);
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ add_library(video_core STATIC
|
|||||||
renderer_opengl/present/present_uniforms.h
|
renderer_opengl/present/present_uniforms.h
|
||||||
renderer_opengl/present/smaa.cpp
|
renderer_opengl/present/smaa.cpp
|
||||||
renderer_opengl/present/smaa.h
|
renderer_opengl/present/smaa.h
|
||||||
|
renderer_opengl/present/taa.cpp
|
||||||
|
renderer_opengl/present/taa.h
|
||||||
renderer_opengl/present/util.h
|
renderer_opengl/present/util.h
|
||||||
renderer_opengl/present/window_adapt_pass.cpp
|
renderer_opengl/present/window_adapt_pass.cpp
|
||||||
renderer_opengl/present/window_adapt_pass.h
|
renderer_opengl/present/window_adapt_pass.h
|
||||||
@@ -191,6 +193,8 @@ add_library(video_core STATIC
|
|||||||
renderer_vulkan/present/fxaa.cpp
|
renderer_vulkan/present/fxaa.cpp
|
||||||
renderer_vulkan/present/fxaa.h
|
renderer_vulkan/present/fxaa.h
|
||||||
renderer_vulkan/present/layer.cpp
|
renderer_vulkan/present/layer.cpp
|
||||||
|
renderer_vulkan/present/taa.cpp
|
||||||
|
renderer_vulkan/present/taa.h
|
||||||
renderer_vulkan/present/layer.h
|
renderer_vulkan/present/layer.h
|
||||||
renderer_vulkan/present/present_push_constants.h
|
renderer_vulkan/present/present_push_constants.h
|
||||||
renderer_vulkan/present/smaa.cpp
|
renderer_vulkan/present/smaa.cpp
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ set(SHADER_FILES
|
|||||||
smaa_blending_weight_calculation.frag
|
smaa_blending_weight_calculation.frag
|
||||||
smaa_neighborhood_blending.vert
|
smaa_neighborhood_blending.vert
|
||||||
smaa_neighborhood_blending.frag
|
smaa_neighborhood_blending.frag
|
||||||
|
taa.frag
|
||||||
|
taa.vert
|
||||||
vulkan_blit_depth_stencil.frag
|
vulkan_blit_depth_stencil.frag
|
||||||
vulkan_color_clear.frag
|
vulkan_color_clear.frag
|
||||||
vulkan_color_clear.vert
|
vulkan_color_clear.vert
|
||||||
@@ -67,6 +69,8 @@ set(SHADER_FILES
|
|||||||
vulkan_present_scaleforce_fp16.frag
|
vulkan_present_scaleforce_fp16.frag
|
||||||
vulkan_present_scaleforce_fp32.frag
|
vulkan_present_scaleforce_fp32.frag
|
||||||
vulkan_quad_indexed.comp
|
vulkan_quad_indexed.comp
|
||||||
|
vulkan_taa.frag
|
||||||
|
vulkan_taa.vert
|
||||||
vulkan_turbo_mode.comp
|
vulkan_turbo_mode.comp
|
||||||
vulkan_uint8.comp
|
vulkan_uint8.comp
|
||||||
)
|
)
|
||||||
|
|||||||
163
src/video_core/host_shaders/taa.frag
Normal file
163
src/video_core/host_shaders/taa.frag
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
#ifdef VULKAN
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 1
|
||||||
|
#define BINDING_PREVIOUS_TEXTURE 2
|
||||||
|
#define BINDING_MOTION_TEXTURE 3
|
||||||
|
#define BINDING_DEPTH_TEXTURE 4
|
||||||
|
|
||||||
|
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 0
|
||||||
|
#define BINDING_PREVIOUS_TEXTURE 1
|
||||||
|
#define BINDING_MOTION_TEXTURE 2
|
||||||
|
#define BINDING_DEPTH_TEXTURE 3
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 posPos;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
// Textures
|
||||||
|
layout (binding = BINDING_COLOR_TEXTURE) uniform sampler2D current_texture;
|
||||||
|
layout (binding = BINDING_PREVIOUS_TEXTURE) uniform sampler2D previous_texture;
|
||||||
|
layout (binding = BINDING_MOTION_TEXTURE) uniform sampler2D motion_texture;
|
||||||
|
layout (binding = BINDING_DEPTH_TEXTURE) uniform sampler2D depth_texture;
|
||||||
|
|
||||||
|
// TAA parameters
|
||||||
|
layout (binding = 5) uniform TaaParams {
|
||||||
|
vec2 jitter_offset;
|
||||||
|
float frame_count;
|
||||||
|
float blend_factor;
|
||||||
|
vec2 inv_resolution;
|
||||||
|
float motion_scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TAA configuration
|
||||||
|
const float TAA_CLAMP_FACTOR = 0.9; // More aggressive clamping to reduce ghosting
|
||||||
|
const float TAA_SHARPENING = 0.15; // Reduced sharpening to prevent artifacts
|
||||||
|
const float TAA_REJECTION_SAMPLES = 8.0;
|
||||||
|
|
||||||
|
// Halton sequence for jittering (2,3)
|
||||||
|
const vec2 HALTON_SEQUENCE[8] = vec2[8](
|
||||||
|
vec2(0.0, 0.0),
|
||||||
|
vec2(0.5, 0.333333),
|
||||||
|
vec2(0.25, 0.666667),
|
||||||
|
vec2(0.75, 0.111111),
|
||||||
|
vec2(0.125, 0.444444),
|
||||||
|
vec2(0.625, 0.777778),
|
||||||
|
vec2(0.375, 0.222222),
|
||||||
|
vec2(0.875, 0.555556)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get Halton jitter for frame
|
||||||
|
vec2 GetHaltonJitter(float frame_index) {
|
||||||
|
int index = int(mod(frame_index, TAA_REJECTION_SAMPLES));
|
||||||
|
return HALTON_SEQUENCE[index] - 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp color to neighborhood to prevent ghosting
|
||||||
|
vec3 ClampToNeighborhood(vec3 current, vec3 history) {
|
||||||
|
vec2 texel_size = inv_resolution;
|
||||||
|
vec3 color_min = current;
|
||||||
|
vec3 color_max = current;
|
||||||
|
|
||||||
|
// Sample 3x3 neighborhood around current pixel
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
vec2 offset = vec2(float(x), float(y)) * texel_size;
|
||||||
|
vec3 neighbor = texture(current_texture, posPos.xy + offset).rgb;
|
||||||
|
color_min = min(color_min, neighbor);
|
||||||
|
color_max = max(color_max, neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp history to neighborhood with some tolerance
|
||||||
|
vec3 clamped = clamp(history, color_min, color_max);
|
||||||
|
return mix(history, clamped, TAA_CLAMP_FACTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Motion vector based history rejection
|
||||||
|
bool IsValidMotion(vec2 motion_vector) {
|
||||||
|
// Reject if motion is too large (likely disocclusion) or too small (likely invalid)
|
||||||
|
float motion_length = length(motion_vector);
|
||||||
|
return motion_length > 0.001 && motion_length < 0.05; // Valid motion range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge detection for sharpening
|
||||||
|
float GetEdgeLuminance(vec2 uv) {
|
||||||
|
vec2 texel_size = inv_resolution;
|
||||||
|
float luma = dot(texture(current_texture, uv).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
|
||||||
|
float luma_l = dot(texture(current_texture, uv + vec2(-texel_size.x, 0.0)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
float luma_r = dot(texture(current_texture, uv + vec2(texel_size.x, 0.0)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
float luma_u = dot(texture(current_texture, uv + vec2(0.0, -texel_size.y)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
float luma_d = dot(texture(current_texture, uv + vec2(0.0, texel_size.y)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
|
||||||
|
float edge_h = abs(luma_l - luma_r);
|
||||||
|
float edge_v = abs(luma_u - luma_d);
|
||||||
|
|
||||||
|
return max(edge_h, edge_v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 current_uv = posPos.xy; // Jittered UV for current frame
|
||||||
|
vec2 previous_uv = posPos.zw; // Non-jittered UV for history
|
||||||
|
|
||||||
|
// Sample current frame with jitter
|
||||||
|
vec3 current_color = texture(current_texture, current_uv).rgb;
|
||||||
|
|
||||||
|
// Get motion vector (use non-jittered UV for consistency)
|
||||||
|
vec2 motion_vector = texture(motion_texture, previous_uv).xy * motion_scale;
|
||||||
|
|
||||||
|
// Calculate history UV using motion vector (start from non-jittered position)
|
||||||
|
vec2 history_uv = previous_uv - motion_vector;
|
||||||
|
|
||||||
|
// Sample previous frame at history position
|
||||||
|
vec3 history_color = texture(previous_texture, history_uv).rgb;
|
||||||
|
|
||||||
|
// Motion vector validation
|
||||||
|
bool valid_motion = IsValidMotion(motion_vector);
|
||||||
|
|
||||||
|
// Edge detection for adaptive blending
|
||||||
|
float edge_strength = GetEdgeLuminance(current_uv);
|
||||||
|
float adaptive_blend = mix(blend_factor, 0.8, edge_strength);
|
||||||
|
|
||||||
|
// Clamp history to neighborhood to prevent ghosting
|
||||||
|
vec3 clamped_history = ClampToNeighborhood(current_color, history_color);
|
||||||
|
|
||||||
|
// Temporal blending with improved ghosting prevention
|
||||||
|
vec3 taa_result;
|
||||||
|
if (valid_motion && frame_count > 0.0) {
|
||||||
|
// Use more aggressive blending to reduce ghosting
|
||||||
|
float final_blend = max(adaptive_blend, 0.3); // Minimum 30% current frame
|
||||||
|
taa_result = mix(clamped_history, current_color, final_blend);
|
||||||
|
} else {
|
||||||
|
// Fallback to current frame if motion is invalid or first frame
|
||||||
|
taa_result = current_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional sharpening to counteract TAA blur
|
||||||
|
if (TAA_SHARPENING > 0.0) {
|
||||||
|
vec2 texel_size = inv_resolution;
|
||||||
|
vec3 sharpened = current_color * (1.0 + 4.0 * TAA_SHARPENING) -
|
||||||
|
TAA_SHARPENING * (
|
||||||
|
texture(current_texture, current_uv + vec2(texel_size.x, 0.0)).rgb +
|
||||||
|
texture(current_texture, current_uv - vec2(texel_size.x, 0.0)).rgb +
|
||||||
|
texture(current_texture, current_uv + vec2(0.0, texel_size.y)).rgb +
|
||||||
|
texture(current_texture, current_uv - vec2(0.0, texel_size.y)).rgb
|
||||||
|
);
|
||||||
|
|
||||||
|
taa_result = mix(taa_result, sharpened, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve alpha from current frame
|
||||||
|
float alpha = texture(current_texture, current_uv).a;
|
||||||
|
|
||||||
|
frag_color = vec4(taa_result, alpha);
|
||||||
|
}
|
||||||
46
src/video_core/host_shaders/taa.vert
Normal file
46
src/video_core/host_shaders/taa.vert
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
out gl_PerVertex {
|
||||||
|
vec4 gl_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
const vec2 vertices[3] =
|
||||||
|
vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1, 3));
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 posPos;
|
||||||
|
|
||||||
|
#ifdef VULKAN
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 0
|
||||||
|
#define VERTEX_ID gl_VertexIndex
|
||||||
|
|
||||||
|
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 0
|
||||||
|
#define VERTEX_ID gl_VertexID
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
layout (binding = BINDING_COLOR_TEXTURE) uniform sampler2D input_texture;
|
||||||
|
|
||||||
|
// TAA jitter offset (passed as uniform)
|
||||||
|
layout (binding = 1) uniform TaaParams {
|
||||||
|
vec2 jitter_offset;
|
||||||
|
float frame_count;
|
||||||
|
float blend_factor;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 vertex = vertices[VERTEX_ID];
|
||||||
|
gl_Position = vec4(vertex, 0.0, 1.0);
|
||||||
|
vec2 vert_tex_coord = (vertex + 1.0) / 2.0;
|
||||||
|
|
||||||
|
// Apply jitter for temporal sampling (already scaled in C++)
|
||||||
|
vec2 jittered_tex_coord = vert_tex_coord + jitter_offset;
|
||||||
|
|
||||||
|
posPos.xy = jittered_tex_coord;
|
||||||
|
posPos.zw = vert_tex_coord; // Previous frame position (no jitter)
|
||||||
|
}
|
||||||
164
src/video_core/host_shaders/vulkan_taa.frag
Normal file
164
src/video_core/host_shaders/vulkan_taa.frag
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
#ifdef VULKAN
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 1
|
||||||
|
#define BINDING_PREVIOUS_TEXTURE 2
|
||||||
|
#define BINDING_MOTION_TEXTURE 3
|
||||||
|
#define BINDING_DEPTH_TEXTURE 4
|
||||||
|
|
||||||
|
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 0
|
||||||
|
#define BINDING_PREVIOUS_TEXTURE 1
|
||||||
|
#define BINDING_MOTION_TEXTURE 2
|
||||||
|
#define BINDING_DEPTH_TEXTURE 3
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 posPos;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
// Textures
|
||||||
|
layout (binding = BINDING_COLOR_TEXTURE) uniform sampler2D current_texture;
|
||||||
|
layout (binding = BINDING_PREVIOUS_TEXTURE) uniform sampler2D previous_texture;
|
||||||
|
layout (binding = BINDING_MOTION_TEXTURE) uniform sampler2D motion_texture;
|
||||||
|
layout (binding = BINDING_DEPTH_TEXTURE) uniform sampler2D depth_texture;
|
||||||
|
|
||||||
|
// TAA parameters
|
||||||
|
layout (binding = 5) uniform TaaParams {
|
||||||
|
vec2 jitter_offset;
|
||||||
|
float frame_count;
|
||||||
|
float blend_factor;
|
||||||
|
vec2 inv_resolution;
|
||||||
|
float motion_scale;
|
||||||
|
float padding[3]; // Padding to 32-byte alignment
|
||||||
|
};
|
||||||
|
|
||||||
|
// TAA configuration
|
||||||
|
const float TAA_CLAMP_FACTOR = 0.9; // More aggressive clamping to reduce ghosting
|
||||||
|
const float TAA_SHARPENING = 0.15; // Reduced sharpening to prevent artifacts
|
||||||
|
const float TAA_REJECTION_SAMPLES = 8.0;
|
||||||
|
|
||||||
|
// Halton sequence for jittering (2,3)
|
||||||
|
const vec2 HALTON_SEQUENCE[8] = vec2[8](
|
||||||
|
vec2(0.0, 0.0),
|
||||||
|
vec2(0.5, 0.333333),
|
||||||
|
vec2(0.25, 0.666667),
|
||||||
|
vec2(0.75, 0.111111),
|
||||||
|
vec2(0.125, 0.444444),
|
||||||
|
vec2(0.625, 0.777778),
|
||||||
|
vec2(0.375, 0.222222),
|
||||||
|
vec2(0.875, 0.555556)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get Halton jitter for frame
|
||||||
|
vec2 GetHaltonJitter(float frame_index) {
|
||||||
|
int index = int(mod(frame_index, TAA_REJECTION_SAMPLES));
|
||||||
|
return HALTON_SEQUENCE[index] - 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp color to neighborhood to prevent ghosting
|
||||||
|
vec3 ClampToNeighborhood(vec3 current, vec3 history) {
|
||||||
|
vec2 texel_size = inv_resolution;
|
||||||
|
vec3 color_min = current;
|
||||||
|
vec3 color_max = current;
|
||||||
|
|
||||||
|
// Sample 3x3 neighborhood around current pixel
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
vec2 offset = vec2(float(x), float(y)) * texel_size;
|
||||||
|
vec3 neighbor = texture(current_texture, posPos.xy + offset).rgb;
|
||||||
|
color_min = min(color_min, neighbor);
|
||||||
|
color_max = max(color_max, neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp history to neighborhood with some tolerance
|
||||||
|
vec3 clamped = clamp(history, color_min, color_max);
|
||||||
|
return mix(history, clamped, TAA_CLAMP_FACTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Motion vector based history rejection
|
||||||
|
bool IsValidMotion(vec2 motion_vector) {
|
||||||
|
// Reject if motion is too large (likely disocclusion) or too small (likely invalid)
|
||||||
|
float motion_length = length(motion_vector);
|
||||||
|
return motion_length > 0.001 && motion_length < 0.05; // Valid motion range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge detection for sharpening
|
||||||
|
float GetEdgeLuminance(vec2 uv) {
|
||||||
|
vec2 texel_size = inv_resolution;
|
||||||
|
float luma = dot(texture(current_texture, uv).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
|
||||||
|
float luma_l = dot(texture(current_texture, uv + vec2(-texel_size.x, 0.0)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
float luma_r = dot(texture(current_texture, uv + vec2(texel_size.x, 0.0)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
float luma_u = dot(texture(current_texture, uv + vec2(0.0, -texel_size.y)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
float luma_d = dot(texture(current_texture, uv + vec2(0.0, texel_size.y)).rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
|
||||||
|
float edge_h = abs(luma_l - luma_r);
|
||||||
|
float edge_v = abs(luma_u - luma_d);
|
||||||
|
|
||||||
|
return max(edge_h, edge_v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 current_uv = posPos.xy; // Jittered UV for current frame
|
||||||
|
vec2 previous_uv = posPos.zw; // Non-jittered UV for history
|
||||||
|
|
||||||
|
// Sample current frame with jitter
|
||||||
|
vec3 current_color = texture(current_texture, current_uv).rgb;
|
||||||
|
|
||||||
|
// Get motion vector (use non-jittered UV for consistency)
|
||||||
|
vec2 motion_vector = texture(motion_texture, previous_uv).xy * motion_scale;
|
||||||
|
|
||||||
|
// Calculate history UV using motion vector (start from non-jittered position)
|
||||||
|
vec2 history_uv = previous_uv - motion_vector;
|
||||||
|
|
||||||
|
// Sample previous frame at history position
|
||||||
|
vec3 history_color = texture(previous_texture, history_uv).rgb;
|
||||||
|
|
||||||
|
// Motion vector validation
|
||||||
|
bool valid_motion = IsValidMotion(motion_vector);
|
||||||
|
|
||||||
|
// Edge detection for adaptive blending
|
||||||
|
float edge_strength = GetEdgeLuminance(current_uv);
|
||||||
|
float adaptive_blend = mix(blend_factor, 0.8, edge_strength);
|
||||||
|
|
||||||
|
// Clamp history to neighborhood to prevent ghosting
|
||||||
|
vec3 clamped_history = ClampToNeighborhood(current_color, history_color);
|
||||||
|
|
||||||
|
// Temporal blending with improved ghosting prevention
|
||||||
|
vec3 taa_result;
|
||||||
|
if (valid_motion && frame_count > 0.0) {
|
||||||
|
// Use more aggressive blending to reduce ghosting
|
||||||
|
float final_blend = max(adaptive_blend, 0.3); // Minimum 30% current frame
|
||||||
|
taa_result = mix(clamped_history, current_color, final_blend);
|
||||||
|
} else {
|
||||||
|
// Fallback to current frame if motion is invalid or first frame
|
||||||
|
taa_result = current_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional sharpening to counteract TAA blur
|
||||||
|
if (TAA_SHARPENING > 0.0) {
|
||||||
|
vec2 texel_size = inv_resolution;
|
||||||
|
vec3 sharpened = current_color * (1.0 + 4.0 * TAA_SHARPENING) -
|
||||||
|
TAA_SHARPENING * (
|
||||||
|
texture(current_texture, current_uv + vec2(texel_size.x, 0.0)).rgb +
|
||||||
|
texture(current_texture, current_uv - vec2(texel_size.x, 0.0)).rgb +
|
||||||
|
texture(current_texture, current_uv + vec2(0.0, texel_size.y)).rgb +
|
||||||
|
texture(current_texture, current_uv - vec2(0.0, texel_size.y)).rgb
|
||||||
|
);
|
||||||
|
|
||||||
|
taa_result = mix(taa_result, sharpened, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve alpha from current frame
|
||||||
|
float alpha = texture(current_texture, current_uv).a;
|
||||||
|
|
||||||
|
frag_color = vec4(taa_result, alpha);
|
||||||
|
}
|
||||||
47
src/video_core/host_shaders/vulkan_taa.vert
Normal file
47
src/video_core/host_shaders/vulkan_taa.vert
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
#ifdef VULKAN
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 0
|
||||||
|
#define VERTEX_ID gl_VertexIndex
|
||||||
|
|
||||||
|
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||||
|
|
||||||
|
#define BINDING_COLOR_TEXTURE 0
|
||||||
|
#define VERTEX_ID gl_VertexID
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
out gl_PerVertex {
|
||||||
|
vec4 gl_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
const vec2 vertices[3] =
|
||||||
|
vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1, 3));
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 posPos;
|
||||||
|
|
||||||
|
// TAA jitter offset (passed as uniform)
|
||||||
|
layout (binding = 5) uniform TaaParams {
|
||||||
|
vec2 jitter_offset;
|
||||||
|
float frame_count;
|
||||||
|
float blend_factor;
|
||||||
|
vec2 inv_resolution;
|
||||||
|
float motion_scale;
|
||||||
|
float padding[3]; // Padding to 32-byte alignment
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 vertex = vertices[VERTEX_ID];
|
||||||
|
gl_Position = vec4(vertex, 0.0, 1.0);
|
||||||
|
vec2 vert_tex_coord = (vertex + 1.0) / 2.0;
|
||||||
|
|
||||||
|
// Apply jitter for temporal sampling (already scaled in C++)
|
||||||
|
vec2 jittered_tex_coord = vert_tex_coord + jitter_offset;
|
||||||
|
|
||||||
|
posPos.xy = jittered_tex_coord;
|
||||||
|
posPos.zw = vert_tex_coord; // Previous frame position (no jitter)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "video_core/renderer_opengl/present/layer.h"
|
#include "video_core/renderer_opengl/present/layer.h"
|
||||||
#include "video_core/renderer_opengl/present/present_uniforms.h"
|
#include "video_core/renderer_opengl/present/present_uniforms.h"
|
||||||
#include "video_core/renderer_opengl/present/smaa.h"
|
#include "video_core/renderer_opengl/present/smaa.h"
|
||||||
|
#include "video_core/renderer_opengl/present/taa.h"
|
||||||
#include "video_core/surface.h"
|
#include "video_core/surface.h"
|
||||||
#include "video_core/textures/decoders.h"
|
#include "video_core/textures/decoders.h"
|
||||||
|
|
||||||
@@ -59,10 +60,16 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
|
|||||||
texture = fxaa->Draw(program_manager, info.display_texture);
|
texture = fxaa->Draw(program_manager, info.display_texture);
|
||||||
break;
|
break;
|
||||||
case Settings::AntiAliasing::Smaa:
|
case Settings::AntiAliasing::Smaa:
|
||||||
default:
|
|
||||||
CreateSMAA();
|
CreateSMAA();
|
||||||
texture = smaa->Draw(program_manager, info.display_texture);
|
texture = smaa->Draw(program_manager, info.display_texture);
|
||||||
break;
|
break;
|
||||||
|
case Settings::AntiAliasing::Taa:
|
||||||
|
CreateTAA();
|
||||||
|
texture = taa->Draw(program_manager, info.display_texture,
|
||||||
|
GL_NONE, GL_NONE, GL_NONE, 0); // TODO: Add proper motion vectors
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +222,7 @@ void Layer::ConfigureFramebufferTexture(const Tegra::FramebufferConfig& framebuf
|
|||||||
|
|
||||||
void Layer::CreateFXAA() {
|
void Layer::CreateFXAA() {
|
||||||
smaa.reset();
|
smaa.reset();
|
||||||
|
taa.reset();
|
||||||
if (!fxaa) {
|
if (!fxaa) {
|
||||||
fxaa = std::make_unique<FXAA>(
|
fxaa = std::make_unique<FXAA>(
|
||||||
Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
|
Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
|
||||||
@@ -224,6 +232,7 @@ void Layer::CreateFXAA() {
|
|||||||
|
|
||||||
void Layer::CreateSMAA() {
|
void Layer::CreateSMAA() {
|
||||||
fxaa.reset();
|
fxaa.reset();
|
||||||
|
taa.reset();
|
||||||
if (!smaa) {
|
if (!smaa) {
|
||||||
smaa = std::make_unique<SMAA>(
|
smaa = std::make_unique<SMAA>(
|
||||||
Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
|
Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
|
||||||
@@ -231,4 +240,14 @@ void Layer::CreateSMAA() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Layer::CreateTAA() {
|
||||||
|
fxaa.reset();
|
||||||
|
smaa.reset();
|
||||||
|
auto scaled_width = Settings::values.resolution_info.ScaleUp(framebuffer_texture.width);
|
||||||
|
auto scaled_height = Settings::values.resolution_info.ScaleUp(framebuffer_texture.height);
|
||||||
|
if (!taa || taa->NeedsRecreation(scaled_width, scaled_height)) {
|
||||||
|
taa = std::make_unique<TAA>(scaled_width, scaled_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace OpenGL
|
} // namespace OpenGL
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class FXAA;
|
|||||||
class ProgramManager;
|
class ProgramManager;
|
||||||
class RasterizerOpenGL;
|
class RasterizerOpenGL;
|
||||||
class SMAA;
|
class SMAA;
|
||||||
|
class TAA;
|
||||||
|
|
||||||
/// Structure used for storing information about the textures for the Switch screen
|
/// Structure used for storing information about the textures for the Switch screen
|
||||||
struct TextureInfo {
|
struct TextureInfo {
|
||||||
@@ -66,6 +67,7 @@ private:
|
|||||||
|
|
||||||
void CreateFXAA();
|
void CreateFXAA();
|
||||||
void CreateSMAA();
|
void CreateSMAA();
|
||||||
|
void CreateTAA();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RasterizerOpenGL& rasterizer;
|
RasterizerOpenGL& rasterizer;
|
||||||
@@ -82,6 +84,7 @@ private:
|
|||||||
std::unique_ptr<FSR2> fsr2;
|
std::unique_ptr<FSR2> fsr2;
|
||||||
std::unique_ptr<FXAA> fxaa;
|
std::unique_ptr<FXAA> fxaa;
|
||||||
std::unique_ptr<SMAA> smaa;
|
std::unique_ptr<SMAA> smaa;
|
||||||
|
std::unique_ptr<TAA> taa;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace OpenGL
|
} // namespace OpenGL
|
||||||
|
|||||||
162
src/video_core/renderer_opengl/present/taa.cpp
Normal file
162
src/video_core/renderer_opengl/present/taa.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// Shader headers generated by CMake
|
||||||
|
#include "video_core/host_shaders/taa_frag.h"
|
||||||
|
#include "video_core/host_shaders/taa_vert.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
|
#include "video_core/renderer_opengl/present/taa.h"
|
||||||
|
#include "video_core/renderer_opengl/present/util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
TAA::TAA(u32 render_width, u32 render_height) : width(render_width), height(render_height), current_frame(0) {
|
||||||
|
// Validate dimensions
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
LOG_ERROR(Render_OpenGL, "TAA: Invalid dimensions {}x{}", width, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vert_shader = CreateProgram(HostShaders::TAA_VERT, GL_VERTEX_SHADER);
|
||||||
|
frag_shader = CreateProgram(HostShaders::TAA_FRAG, GL_FRAGMENT_SHADER);
|
||||||
|
|
||||||
|
sampler = CreateBilinearSampler();
|
||||||
|
|
||||||
|
// Create uniform buffer
|
||||||
|
uniform_buffer.Create();
|
||||||
|
glNamedBufferStorage(uniform_buffer.handle, sizeof(TaaParams), nullptr,
|
||||||
|
GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT);
|
||||||
|
|
||||||
|
// Initialize TAA parameters
|
||||||
|
params.frame_count = 0.0f;
|
||||||
|
params.blend_factor = 0.25f; // Increased blend factor to reduce ghosting
|
||||||
|
params.inv_resolution[0] = 1.0f / static_cast<float>(width);
|
||||||
|
params.inv_resolution[1] = 1.0f / static_cast<float>(height);
|
||||||
|
params.motion_scale = 1.0f;
|
||||||
|
params.jitter_offset[0] = 0.0f;
|
||||||
|
params.jitter_offset[1] = 0.0f;
|
||||||
|
|
||||||
|
CreateFramebuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
TAA::~TAA() = default;
|
||||||
|
|
||||||
|
void TAA::CreateFramebuffers() {
|
||||||
|
framebuffer.Create();
|
||||||
|
|
||||||
|
// Current frame texture (RGBA16F for HDR support)
|
||||||
|
current_texture.Create(GL_TEXTURE_2D);
|
||||||
|
glTextureStorage2D(current_texture.handle, 1, GL_RGBA16F, width, height);
|
||||||
|
glNamedFramebufferTexture(framebuffer.handle, GL_COLOR_ATTACHMENT0, current_texture.handle, 0);
|
||||||
|
|
||||||
|
// Previous frame texture
|
||||||
|
previous_texture.Create(GL_TEXTURE_2D);
|
||||||
|
glTextureStorage2D(previous_texture.handle, 1, GL_RGBA16F, width, height);
|
||||||
|
|
||||||
|
// Motion vector texture (RG16F for 2D motion vectors) - initialize with zeros
|
||||||
|
motion_texture.Create(GL_TEXTURE_2D);
|
||||||
|
glTextureStorage2D(motion_texture.handle, 1, GL_RG16F, width, height);
|
||||||
|
// Clear motion texture to zero motion vectors
|
||||||
|
glClearTexImage(motion_texture.handle, 0, GL_RG, GL_FLOAT, nullptr);
|
||||||
|
|
||||||
|
// Depth texture (R32F for depth buffer) - initialize with far depth
|
||||||
|
depth_texture.Create(GL_TEXTURE_2D);
|
||||||
|
glTextureStorage2D(depth_texture.handle, 1, GL_R32F, width, height);
|
||||||
|
// Clear depth texture to far depth (1.0)
|
||||||
|
const float far_depth = 1.0f;
|
||||||
|
glClearTexImage(depth_texture.handle, 0, GL_RED, GL_FLOAT, &far_depth);
|
||||||
|
|
||||||
|
// Check framebuffer completeness
|
||||||
|
GLenum status = glCheckNamedFramebufferStatus(framebuffer.handle, GL_FRAMEBUFFER);
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
LOG_ERROR(Render_OpenGL, "TAA framebuffer incomplete: 0x{:04X}", status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::UpdateJitter(u32 frame_count) {
|
||||||
|
// Halton sequence (2,3) for low-discrepancy sampling
|
||||||
|
constexpr float halton_2[8] = {0.0f, 0.5f, 0.25f, 0.75f, 0.125f, 0.625f, 0.375f, 0.875f};
|
||||||
|
constexpr float halton_3[8] = {0.0f, 0.333333f, 0.666667f, 0.111111f, 0.444444f, 0.777778f, 0.222222f, 0.555556f};
|
||||||
|
|
||||||
|
// Ensure safe array access
|
||||||
|
const size_t index = static_cast<size_t>(frame_count) % 8;
|
||||||
|
// Reduce jitter intensity to minimize visible jittering
|
||||||
|
const float jitter_scale = 0.5f; // Reduce jitter by 50%
|
||||||
|
params.jitter_offset[0] = (halton_2[index] - 0.5f) * jitter_scale * params.inv_resolution[0];
|
||||||
|
params.jitter_offset[1] = (halton_3[index] - 0.5f) * jitter_scale * params.inv_resolution[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint TAA::Draw(ProgramManager& program_manager, GLuint input_texture, GLuint prev_texture,
|
||||||
|
GLuint motion_text, GLuint depth_text, u32 frame_count) {
|
||||||
|
// Validate input texture
|
||||||
|
if (input_texture == 0) {
|
||||||
|
LOG_ERROR(Render_OpenGL, "TAA: Invalid input texture");
|
||||||
|
return input_texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parameters
|
||||||
|
params.frame_count = static_cast<float>(frame_count);
|
||||||
|
UpdateJitter(frame_count);
|
||||||
|
|
||||||
|
// Update uniform buffer
|
||||||
|
void* buffer_data = glMapNamedBuffer(uniform_buffer.handle, GL_WRITE_ONLY);
|
||||||
|
if (buffer_data) {
|
||||||
|
memcpy(buffer_data, ¶ms, sizeof(TaaParams));
|
||||||
|
glUnmapNamedBuffer(uniform_buffer.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind TAA program
|
||||||
|
program_manager.BindPresentPrograms(vert_shader.handle, frag_shader.handle);
|
||||||
|
|
||||||
|
// Bind framebuffer
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle);
|
||||||
|
|
||||||
|
// Bind textures - use fallback textures if motion/depth are not available
|
||||||
|
glBindTextureUnit(0, input_texture); // Current frame
|
||||||
|
glBindTextureUnit(1, previous_texture.handle); // Previous frame (from TAA)
|
||||||
|
|
||||||
|
// Use motion texture if provided, otherwise use a dummy texture
|
||||||
|
if (motion_text != 0 && motion_text != GL_NONE) {
|
||||||
|
glBindTextureUnit(2, motion_text);
|
||||||
|
} else {
|
||||||
|
glBindTextureUnit(2, motion_texture.handle); // Use our dummy motion texture
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use depth texture if provided, otherwise use a dummy texture
|
||||||
|
if (depth_text != 0 && depth_text != GL_NONE) {
|
||||||
|
glBindTextureUnit(3, depth_text);
|
||||||
|
} else {
|
||||||
|
glBindTextureUnit(3, depth_texture.handle); // Use our dummy depth texture
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind samplers
|
||||||
|
glBindSampler(0, sampler.handle);
|
||||||
|
glBindSampler(1, sampler.handle);
|
||||||
|
glBindSampler(2, sampler.handle);
|
||||||
|
glBindSampler(3, sampler.handle);
|
||||||
|
|
||||||
|
// Bind uniform buffer
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, 5, uniform_buffer.handle);
|
||||||
|
|
||||||
|
// Draw full-screen triangle
|
||||||
|
glFrontFace(GL_CCW);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
|
glFrontFace(GL_CW);
|
||||||
|
|
||||||
|
// Copy current frame to previous frame for next iteration
|
||||||
|
glCopyImageSubData(current_texture.handle, GL_TEXTURE_2D, 0, 0, 0, 0,
|
||||||
|
previous_texture.handle, GL_TEXTURE_2D, 0, 0, 0, 0,
|
||||||
|
width, height, 1);
|
||||||
|
|
||||||
|
return current_texture.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::SwapBuffers() {
|
||||||
|
// Swap current and previous textures
|
||||||
|
std::swap(current_texture, previous_texture);
|
||||||
|
current_frame++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
||||||
61
src/video_core/renderer_opengl/present/taa.h
Normal file
61
src/video_core/renderer_opengl/present/taa.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
class ProgramManager;
|
||||||
|
|
||||||
|
class TAA {
|
||||||
|
public:
|
||||||
|
explicit TAA(u32 render_width, u32 render_height);
|
||||||
|
~TAA();
|
||||||
|
|
||||||
|
GLuint Draw(ProgramManager& program_manager, GLuint input_texture, GLuint previous_texture,
|
||||||
|
GLuint motion_texture, GLuint depth_texture, u32 frame_count);
|
||||||
|
|
||||||
|
void SwapBuffers();
|
||||||
|
|
||||||
|
bool NeedsRecreation(u32 render_width, u32 render_height) const {
|
||||||
|
return this->width != render_width || this->height != render_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateFramebuffers();
|
||||||
|
void UpdateJitter(u32 frame_count);
|
||||||
|
|
||||||
|
OGLProgram vert_shader;
|
||||||
|
OGLProgram frag_shader;
|
||||||
|
OGLSampler sampler;
|
||||||
|
|
||||||
|
// Current and previous frame buffers
|
||||||
|
OGLFramebuffer framebuffer;
|
||||||
|
OGLTexture current_texture;
|
||||||
|
OGLTexture previous_texture;
|
||||||
|
OGLTexture motion_texture;
|
||||||
|
OGLTexture depth_texture;
|
||||||
|
|
||||||
|
// Uniform buffer for TAA parameters
|
||||||
|
OGLBuffer uniform_buffer;
|
||||||
|
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
u32 current_frame;
|
||||||
|
|
||||||
|
// TAA parameters
|
||||||
|
struct TaaParams {
|
||||||
|
alignas(8) float jitter_offset[2];
|
||||||
|
alignas(4) float frame_count;
|
||||||
|
alignas(4) float blend_factor;
|
||||||
|
alignas(8) float inv_resolution[2];
|
||||||
|
alignas(4) float motion_scale;
|
||||||
|
alignas(4) float padding[3]; // Padding to 32-byte alignment
|
||||||
|
};
|
||||||
|
|
||||||
|
TaaParams params;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "video_core/renderer_vulkan/present/layer.h"
|
#include "video_core/renderer_vulkan/present/layer.h"
|
||||||
#include "video_core/renderer_vulkan/present/present_push_constants.h"
|
#include "video_core/renderer_vulkan/present/present_push_constants.h"
|
||||||
#include "video_core/renderer_vulkan/present/smaa.h"
|
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/taa.h"
|
||||||
#include "video_core/renderer_vulkan/present/util.h"
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
||||||
#include "video_core/textures/decoders.h"
|
#include "video_core/textures/decoders.h"
|
||||||
@@ -205,6 +206,9 @@ void Layer::SetAntiAliasPass() {
|
|||||||
case Settings::AntiAliasing::Smaa:
|
case Settings::AntiAliasing::Smaa:
|
||||||
anti_alias = std::make_unique<SMAA>(device, memory_allocator, image_count, render_area);
|
anti_alias = std::make_unique<SMAA>(device, memory_allocator, image_count, render_area);
|
||||||
break;
|
break;
|
||||||
|
case Settings::AntiAliasing::Taa:
|
||||||
|
anti_alias = std::make_unique<TAA>(device, memory_allocator, image_count, render_area);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
anti_alias = std::make_unique<NoAA>();
|
anti_alias = std::make_unique<NoAA>();
|
||||||
break;
|
break;
|
||||||
|
|||||||
362
src/video_core/renderer_vulkan/present/taa.cpp
Normal file
362
src/video_core/renderer_vulkan/present/taa.cpp
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
#include "video_core/host_shaders/vulkan_taa_frag_spv.h"
|
||||||
|
#include "video_core/host_shaders/vulkan_taa_vert_spv.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/taa.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
TAA::TAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent)
|
||||||
|
: m_device(device), m_allocator(allocator), m_extent(extent),
|
||||||
|
m_image_count(static_cast<u32>(image_count)) {
|
||||||
|
|
||||||
|
// Validate dimensions
|
||||||
|
if (extent.width == 0 || extent.height == 0) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "TAA: Invalid dimensions {}x{}", extent.width, extent.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize TAA parameters
|
||||||
|
m_params.frame_count = 0.0f;
|
||||||
|
m_params.blend_factor = 0.25f; // Increased blend factor to reduce ghosting
|
||||||
|
m_params.inv_resolution[0] = 1.0f / static_cast<float>(extent.width);
|
||||||
|
m_params.inv_resolution[1] = 1.0f / static_cast<float>(extent.height);
|
||||||
|
m_params.motion_scale = 1.0f;
|
||||||
|
m_params.jitter_offset[0] = 0.0f;
|
||||||
|
m_params.jitter_offset[1] = 0.0f;
|
||||||
|
|
||||||
|
CreateImages();
|
||||||
|
CreateRenderPasses();
|
||||||
|
CreateSampler();
|
||||||
|
CreateShaders();
|
||||||
|
CreateDescriptorPool();
|
||||||
|
CreateDescriptorSetLayouts();
|
||||||
|
CreateDescriptorSets();
|
||||||
|
CreatePipelineLayouts();
|
||||||
|
CreatePipelines();
|
||||||
|
}
|
||||||
|
|
||||||
|
TAA::~TAA() = default;
|
||||||
|
|
||||||
|
void TAA::CreateImages() {
|
||||||
|
for (u32 i = 0; i < m_image_count; i++) {
|
||||||
|
Image& image = m_dynamic_images.emplace_back();
|
||||||
|
|
||||||
|
// Current frame texture (RGBA16F for HDR support)
|
||||||
|
image.image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
|
||||||
|
image.image_view =
|
||||||
|
CreateWrappedImageView(m_device, image.image, VK_FORMAT_R16G16B16A16_SFLOAT);
|
||||||
|
|
||||||
|
// Previous frame texture
|
||||||
|
image.previous_image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
|
||||||
|
image.previous_image_view =
|
||||||
|
CreateWrappedImageView(m_device, image.previous_image, VK_FORMAT_R16G16B16A16_SFLOAT);
|
||||||
|
|
||||||
|
// Motion vector texture (RG16F for 2D motion vectors)
|
||||||
|
image.motion_image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16_SFLOAT);
|
||||||
|
image.motion_image_view =
|
||||||
|
CreateWrappedImageView(m_device, image.motion_image, VK_FORMAT_R16G16_SFLOAT);
|
||||||
|
|
||||||
|
// Depth texture (R32F for depth buffer)
|
||||||
|
image.depth_image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R32_SFLOAT);
|
||||||
|
image.depth_image_view =
|
||||||
|
CreateWrappedImageView(m_device, image.depth_image, VK_FORMAT_R32_SFLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create uniform buffer - using VMA allocator like other Vulkan implementations
|
||||||
|
m_uniform_buffer = CreateWrappedBuffer(m_allocator, sizeof(TaaParams), MemoryUsage::Upload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreateRenderPasses() {
|
||||||
|
m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT);
|
||||||
|
|
||||||
|
for (auto& image : m_dynamic_images) {
|
||||||
|
image.framebuffer =
|
||||||
|
CreateWrappedFramebuffer(m_device, m_renderpass, image.image_view, m_extent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreateSampler() {
|
||||||
|
const VkSamplerCreateInfo sampler_info{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.magFilter = VK_FILTER_LINEAR,
|
||||||
|
.minFilter = VK_FILTER_LINEAR,
|
||||||
|
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
||||||
|
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||||
|
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||||
|
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||||
|
.mipLodBias = 0.0f,
|
||||||
|
.anisotropyEnable = VK_FALSE,
|
||||||
|
.maxAnisotropy = 0.0f,
|
||||||
|
.compareEnable = VK_FALSE,
|
||||||
|
.compareOp = VK_COMPARE_OP_NEVER,
|
||||||
|
.minLod = 0.0f,
|
||||||
|
.maxLod = 0.0f,
|
||||||
|
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||||
|
.unnormalizedCoordinates = VK_FALSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
m_sampler = m_device.GetLogical().CreateSampler(sampler_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreateShaders() {
|
||||||
|
m_vertex_shader = CreateWrappedShaderModule(m_device, VULKAN_TAA_VERT_SPV);
|
||||||
|
m_fragment_shader = CreateWrappedShaderModule(m_device, VULKAN_TAA_FRAG_SPV);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreateDescriptorPool() {
|
||||||
|
const std::array<VkDescriptorPoolSize, 2> pool_sizes{{
|
||||||
|
{
|
||||||
|
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = m_image_count * 5, // 5 textures per image
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||||
|
.descriptorCount = m_image_count,
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
|
||||||
|
const VkDescriptorPoolCreateInfo pool_info{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.maxSets = m_image_count,
|
||||||
|
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
||||||
|
.pPoolSizes = pool_sizes.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
m_descriptor_pool = m_device.GetLogical().CreateDescriptorPool(pool_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreateDescriptorSetLayouts() {
|
||||||
|
const std::array<VkDescriptorSetLayoutBinding, 6> layout_bindings{{
|
||||||
|
{
|
||||||
|
.binding = 0,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.pImmutableSamplers = nullptr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.binding = 1,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.pImmutableSamplers = nullptr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.binding = 2,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.pImmutableSamplers = nullptr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.binding = 3,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.pImmutableSamplers = nullptr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.binding = 4,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.pImmutableSamplers = nullptr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.binding = 5,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.pImmutableSamplers = nullptr,
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
|
||||||
|
const VkDescriptorSetLayoutCreateInfo layout_info{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.bindingCount = static_cast<u32>(layout_bindings.size()),
|
||||||
|
.pBindings = layout_bindings.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
m_descriptor_set_layout = m_device.GetLogical().CreateDescriptorSetLayout(layout_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreateDescriptorSets() {
|
||||||
|
VkDescriptorSetLayout layout = *m_descriptor_set_layout;
|
||||||
|
for (auto& image : m_dynamic_images) {
|
||||||
|
image.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, {layout});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreatePipelineLayouts() {
|
||||||
|
m_pipeline_layout = CreateWrappedPipelineLayout(m_device, m_descriptor_set_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::CreatePipelines() {
|
||||||
|
m_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout,
|
||||||
|
std::tie(m_vertex_shader, m_fragment_shader));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) {
|
||||||
|
auto& image = m_dynamic_images[image_index];
|
||||||
|
|
||||||
|
// Update uniform buffer
|
||||||
|
std::span<u8> mapped_span = m_uniform_buffer.Mapped();
|
||||||
|
if (!mapped_span.empty()) {
|
||||||
|
memcpy(mapped_span.data(), &m_params, sizeof(TaaParams));
|
||||||
|
m_uniform_buffer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all TAA descriptor sets
|
||||||
|
std::vector<VkDescriptorImageInfo> image_infos;
|
||||||
|
std::vector<VkWriteDescriptorSet> updates;
|
||||||
|
image_infos.reserve(6);
|
||||||
|
|
||||||
|
// Binding 0: Dummy texture (not used by shader)
|
||||||
|
updates.push_back(
|
||||||
|
CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 0));
|
||||||
|
|
||||||
|
// Binding 1: Current frame texture (input)
|
||||||
|
updates.push_back(
|
||||||
|
CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 1));
|
||||||
|
|
||||||
|
// Binding 2: Previous frame texture
|
||||||
|
updates.push_back(
|
||||||
|
CreateWriteDescriptorSet(image_infos, *m_sampler, *image.previous_image_view, image.descriptor_sets[0], 2));
|
||||||
|
|
||||||
|
// Binding 3: Motion vector texture
|
||||||
|
updates.push_back(
|
||||||
|
CreateWriteDescriptorSet(image_infos, *m_sampler, *image.motion_image_view, image.descriptor_sets[0], 3));
|
||||||
|
|
||||||
|
// Binding 4: Depth texture
|
||||||
|
updates.push_back(
|
||||||
|
CreateWriteDescriptorSet(image_infos, *m_sampler, *image.depth_image_view, image.descriptor_sets[0], 4));
|
||||||
|
|
||||||
|
// Binding 5: Uniform buffer
|
||||||
|
const VkDescriptorBufferInfo buffer_info{
|
||||||
|
.buffer = *m_uniform_buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = sizeof(TaaParams),
|
||||||
|
};
|
||||||
|
|
||||||
|
updates.push_back(VkWriteDescriptorSet{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.dstSet = image.descriptor_sets[0],
|
||||||
|
.dstBinding = 5,
|
||||||
|
.dstArrayElement = 0,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||||
|
.pImageInfo = nullptr,
|
||||||
|
.pBufferInfo = &buffer_info,
|
||||||
|
.pTexelBufferView = nullptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
m_device.GetLogical().UpdateDescriptorSets(updates, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::UpdateJitter(u32 frame_count) {
|
||||||
|
// Halton sequence (2,3) for low-discrepancy sampling
|
||||||
|
constexpr float halton_2[8] = {0.0f, 0.5f, 0.25f, 0.75f, 0.125f, 0.625f, 0.375f, 0.875f};
|
||||||
|
constexpr float halton_3[8] = {0.0f, 0.333333f, 0.666667f, 0.111111f, 0.444444f, 0.777778f, 0.222222f, 0.555556f};
|
||||||
|
|
||||||
|
// Ensure safe array access
|
||||||
|
const size_t index = static_cast<size_t>(frame_count) % 8;
|
||||||
|
// Reduce jitter intensity to minimize visible jittering
|
||||||
|
const float jitter_scale = 0.5f; // Reduce jitter by 50%
|
||||||
|
m_params.jitter_offset[0] = (halton_2[index] - 0.5f) * jitter_scale * m_params.inv_resolution[0];
|
||||||
|
m_params.jitter_offset[1] = (halton_3[index] - 0.5f) * jitter_scale * m_params.inv_resolution[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::UploadImages(Scheduler& scheduler) {
|
||||||
|
if (m_images_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_images_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
|
||||||
|
VkImageView* inout_image_view) {
|
||||||
|
UpdateJitter(m_current_frame);
|
||||||
|
m_params.frame_count = static_cast<float>(m_current_frame);
|
||||||
|
|
||||||
|
UpdateDescriptorSets(*inout_image_view, image_index);
|
||||||
|
UploadImages(scheduler);
|
||||||
|
|
||||||
|
auto& image = m_dynamic_images[image_index];
|
||||||
|
|
||||||
|
const VkRenderPassBeginInfo renderpass_begin_info{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.renderPass = *m_renderpass,
|
||||||
|
.framebuffer = *image.framebuffer,
|
||||||
|
.renderArea =
|
||||||
|
{
|
||||||
|
.offset = {0, 0},
|
||||||
|
.extent = m_extent,
|
||||||
|
},
|
||||||
|
.clearValueCount = 0,
|
||||||
|
.pClearValues = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([this, &image](vk::CommandBuffer cmdbuf) {
|
||||||
|
BeginRenderPass(cmdbuf, *m_renderpass, *image.framebuffer, m_extent);
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipeline_layout, 0,
|
||||||
|
image.descriptor_sets, {});
|
||||||
|
const VkViewport viewport{
|
||||||
|
.x = 0.0f,
|
||||||
|
.y = 0.0f,
|
||||||
|
.width = static_cast<float>(m_extent.width),
|
||||||
|
.height = static_cast<float>(m_extent.height),
|
||||||
|
.minDepth = 0.0f,
|
||||||
|
.maxDepth = 1.0f,
|
||||||
|
};
|
||||||
|
cmdbuf.SetViewport(0, {viewport});
|
||||||
|
const VkRect2D scissor{
|
||||||
|
.offset = {0, 0},
|
||||||
|
.extent = m_extent,
|
||||||
|
};
|
||||||
|
cmdbuf.SetScissor(0, {scissor});
|
||||||
|
cmdbuf.Draw(3, 1, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
|
||||||
|
// Copy current frame to previous frame for next iteration
|
||||||
|
scheduler.Record([this, &image](vk::CommandBuffer cmdbuf) {
|
||||||
|
const VkImageCopy copy_region{
|
||||||
|
.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||||
|
.srcOffset = {0, 0, 0},
|
||||||
|
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||||
|
.dstOffset = {0, 0, 0},
|
||||||
|
.extent = {m_extent.width, m_extent.height, 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
cmdbuf.CopyImage(*image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
|
*image.previous_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
copy_region);
|
||||||
|
});
|
||||||
|
|
||||||
|
*inout_image = *image.image;
|
||||||
|
*inout_image_view = *image.image_view;
|
||||||
|
|
||||||
|
m_current_frame++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
85
src/video_core/renderer_vulkan/present/taa.h
Normal file
85
src/video_core/renderer_vulkan/present/taa.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "video_core/renderer_vulkan/present/anti_alias_pass.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class Scheduler;
|
||||||
|
class StagingBufferPool;
|
||||||
|
|
||||||
|
class TAA final : public AntiAliasPass {
|
||||||
|
public:
|
||||||
|
explicit TAA(const Device& device, MemoryAllocator& allocator, size_t image_count,
|
||||||
|
VkExtent2D extent);
|
||||||
|
~TAA() override;
|
||||||
|
|
||||||
|
void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
|
||||||
|
VkImageView* inout_image_view) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateImages();
|
||||||
|
void CreateRenderPasses();
|
||||||
|
void CreateSampler();
|
||||||
|
void CreateShaders();
|
||||||
|
void CreateDescriptorPool();
|
||||||
|
void CreateDescriptorSetLayouts();
|
||||||
|
void CreateDescriptorSets();
|
||||||
|
void CreatePipelineLayouts();
|
||||||
|
void CreatePipelines();
|
||||||
|
void UpdateDescriptorSets(VkImageView image_view, size_t image_index);
|
||||||
|
void UploadImages(Scheduler& scheduler);
|
||||||
|
void UpdateJitter(u32 frame_count);
|
||||||
|
|
||||||
|
const Device& m_device;
|
||||||
|
MemoryAllocator& m_allocator;
|
||||||
|
const VkExtent2D m_extent;
|
||||||
|
const u32 m_image_count;
|
||||||
|
|
||||||
|
vk::ShaderModule m_vertex_shader{};
|
||||||
|
vk::ShaderModule m_fragment_shader{};
|
||||||
|
vk::DescriptorPool m_descriptor_pool{};
|
||||||
|
vk::DescriptorSetLayout m_descriptor_set_layout{};
|
||||||
|
vk::PipelineLayout m_pipeline_layout{};
|
||||||
|
vk::Pipeline m_pipeline{};
|
||||||
|
vk::RenderPass m_renderpass{};
|
||||||
|
vk::Buffer m_uniform_buffer{};
|
||||||
|
|
||||||
|
struct Image {
|
||||||
|
vk::DescriptorSets descriptor_sets{};
|
||||||
|
vk::Framebuffer framebuffer{};
|
||||||
|
vk::Image image{};
|
||||||
|
vk::ImageView image_view{};
|
||||||
|
// TAA specific textures
|
||||||
|
vk::Image previous_image{};
|
||||||
|
vk::ImageView previous_image_view{};
|
||||||
|
vk::Image motion_image{};
|
||||||
|
vk::ImageView motion_image_view{};
|
||||||
|
vk::Image depth_image{};
|
||||||
|
vk::ImageView depth_image_view{};
|
||||||
|
};
|
||||||
|
std::vector<Image> m_dynamic_images{};
|
||||||
|
bool m_images_ready{};
|
||||||
|
|
||||||
|
vk::Sampler m_sampler{};
|
||||||
|
|
||||||
|
// TAA parameters
|
||||||
|
struct TaaParams {
|
||||||
|
alignas(8) float jitter_offset[2];
|
||||||
|
alignas(4) float frame_count;
|
||||||
|
alignas(4) float blend_factor;
|
||||||
|
alignas(8) float inv_resolution[2];
|
||||||
|
alignas(4) float motion_scale;
|
||||||
|
alignas(4) float padding[3]; // Padding to 32-byte alignment
|
||||||
|
};
|
||||||
|
|
||||||
|
TaaParams m_params{};
|
||||||
|
u32 m_current_frame = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
Reference in New Issue
Block a user