diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 41133a7819..8d93c61ec1 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -166,7 +166,7 @@ ENUM(ResolutionSetup, Res7X, Res8X); -ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, Area, MaxEnum); +ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczo, ScaleForce, Fsr, Area, MaxEnum); ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index cdc05e60e0..74d4dafc59 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -576,6 +576,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(ScalingFilter, Bilinear, tr("Bilinear")), PAIR(ScalingFilter, Bicubic, tr("Bicubic")), PAIR(ScalingFilter, Gaussian, tr("Gaussian")), + PAIR(ScalingFilter, Lanczo, tr("Lanczo")), PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")), PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")), PAIR(ScalingFilter, Area, tr("Area")), diff --git a/src/qt_common/shared_translation.h b/src/qt_common/shared_translation.h index 48a2cb5205..a894da290a 100644 --- a/src/qt_common/shared_translation.h +++ b/src/qt_common/shared_translation.h @@ -40,6 +40,8 @@ static const std::map scaling_filter_texts_map {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))}, {Settings::ScalingFilter::Gaussian, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))}, + {Settings::ScalingFilter::Lanczo, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Lanczo"))}, {Settings::ScalingFilter::ScaleForce, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))}, {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 688e10d2e4..e7dac21f98 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -1,5 +1,5 @@ -# SPDX-FileCopyrightText: 2018 yuzu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) @@ -45,6 +45,7 @@ set(SHADER_FILES present_area.frag present_bicubic.frag present_gaussian.frag + present_lanczo.frag queries_prefix_scan_sum.comp queries_prefix_scan_sum_nosubgroups.comp resolve_conditional_render.comp diff --git a/src/video_core/host_shaders/present_lanczo.frag b/src/video_core/host_shaders/present_lanczo.frag new file mode 100644 index 0000000000..5afc985bc3 --- /dev/null +++ b/src/video_core/host_shaders/present_lanczo.frag @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// https://en.wikipedia.org/wiki/Lanczos_resampling + +#version 460 core + +layout (location = 0) in vec2 frag_tex_coord; +layout (location = 0) out vec4 color; +layout (binding = 0) uniform sampler2D color_texture; + +#define PI 3.1415926535897932384626433 + +float sinc(float x) { + return x == 0.0f ? 1.0f : sin(PI * x) / (PI * x); +} + +float lanczos(vec2 v, float a) { + float d = sqrt(v.x * v.x + v.y * v.y); + return sinc(d) / sinc(d / a); +} + +vec4 textureLanczos(sampler2D textureSampler, vec2 p) { + const int r = 1; //radius (1 = 3 steps) + vec3 c_sum = vec3(0.0f); + float w_sum = 0.0f; + vec2 res = vec2(textureSize(textureSampler, 0)); + vec2 cc = floor(p * res) / res; + // kernel size = (r * 2 + 1) * (r * 2 + 1) + for (int x = -r; x <= r; x++) + for (int y = -r; y <= r; y++) { + vec2 kp = 0.5f * (vec2(x, y) / res); // 0.5 = half-pixel level resampling + vec2 uv = cc + kp; + float w = lanczos(kp, float(r)); + c_sum += w * texture(textureSampler, p + kp).rgb; + w_sum += w; + } + return vec4(c_sum / w_sum, 1.0f); +} + +void main() { + color = textureLanczos(color_texture, frag_tex_coord); +} diff --git a/src/video_core/renderer_opengl/gl_blit_screen.cpp b/src/video_core/renderer_opengl/gl_blit_screen.cpp index 2071fe8d15..9fff39143e 100644 --- a/src/video_core/renderer_opengl/gl_blit_screen.cpp +++ b/src/video_core/renderer_opengl/gl_blit_screen.cpp @@ -89,6 +89,9 @@ void BlitScreen::CreateWindowAdapt() { case Settings::ScalingFilter::Gaussian: window_adapt = MakeGaussian(device); break; + case Settings::ScalingFilter::Lanczo: + window_adapt = MakeLanczo(device); + break; case Settings::ScalingFilter::ScaleForce: window_adapt = MakeScaleForce(device); break; diff --git a/src/video_core/renderer_opengl/present/filters.cpp b/src/video_core/renderer_opengl/present/filters.cpp index c5ac8e7823..a9b3cdd0d9 100644 --- a/src/video_core/renderer_opengl/present/filters.cpp +++ b/src/video_core/renderer_opengl/present/filters.cpp @@ -37,6 +37,11 @@ std::unique_ptr MakeGaussian(const Device& device) { HostShaders::PRESENT_GAUSSIAN_FRAG); } +std::unique_ptr MakeLanczo(const Device& device) { + return std::make_unique(device, CreateBilinearSampler(), + HostShaders::PRESENT_LANCZO_FRAG); +} + std::unique_ptr MakeScaleForce(const Device& device) { return std::make_unique( device, CreateBilinearSampler(), diff --git a/src/video_core/renderer_opengl/present/filters.h b/src/video_core/renderer_opengl/present/filters.h index be2ce24842..c098d0da2e 100644 --- a/src/video_core/renderer_opengl/present/filters.h +++ b/src/video_core/renderer_opengl/present/filters.h @@ -18,6 +18,7 @@ std::unique_ptr MakeNearestNeighbor(const Device& device); std::unique_ptr MakeBilinear(const Device& device); std::unique_ptr MakeBicubic(const Device& device); std::unique_ptr MakeGaussian(const Device& device); +std::unique_ptr MakeLanczo(const Device& device); std::unique_ptr MakeScaleForce(const Device& device); std::unique_ptr MakeArea(const Device& device); diff --git a/src/video_core/renderer_vulkan/present/filters.cpp b/src/video_core/renderer_vulkan/present/filters.cpp index 7843f38d2c..a3a6bfc2f6 100644 --- a/src/video_core/renderer_vulkan/present/filters.cpp +++ b/src/video_core/renderer_vulkan/present/filters.cpp @@ -12,6 +12,7 @@ #include "video_core/host_shaders/present_area_frag_spv.h" #include "video_core/host_shaders/present_bicubic_frag_spv.h" #include "video_core/host_shaders/present_gaussian_frag_spv.h" +#include "video_core/host_shaders/present_lanczso_frag_spv.h" #include "video_core/host_shaders/vulkan_present_frag_spv.h" #include "video_core/host_shaders/vulkan_present_scaleforce_fp16_frag_spv.h" #include "video_core/host_shaders/vulkan_present_scaleforce_fp32_frag_spv.h" @@ -59,6 +60,11 @@ std::unique_ptr MakeGaussian(const Device& device, VkFormat fra BuildShader(device, PRESENT_GAUSSIAN_FRAG_SPV)); } +std::unique_ptr MakeLanczo(const Device& device, VkFormat frame_format) { + return std::make_unique(device, frame_format, CreateBilinearSampler(device), + BuildShader(device, PRESENT_LANCZO_FRAG_SPV)); +} + std::unique_ptr MakeScaleForce(const Device& device, VkFormat frame_format) { return std::make_unique(device, frame_format, CreateBilinearSampler(device), SelectScaleForceShader(device)); diff --git a/src/video_core/renderer_vulkan/present/filters.h b/src/video_core/renderer_vulkan/present/filters.h index c8259487f8..c51938db24 100644 --- a/src/video_core/renderer_vulkan/present/filters.h +++ b/src/video_core/renderer_vulkan/present/filters.h @@ -19,6 +19,7 @@ std::unique_ptr MakeNearestNeighbor(const Device& device, VkFor std::unique_ptr MakeBilinear(const Device& device, VkFormat frame_format); std::unique_ptr MakeBicubic(const Device& device, VkFormat frame_format); std::unique_ptr MakeGaussian(const Device& device, VkFormat frame_format); +std::unique_ptr MakeLanczo(const Device& device, VkFormat frame_format); std::unique_ptr MakeScaleForce(const Device& device, VkFormat frame_format); std::unique_ptr MakeArea(const Device& device, VkFormat frame_format); diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 39f07b966d..b398062dae 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -46,6 +46,9 @@ void BlitScreen::SetWindowAdaptPass() { case Settings::ScalingFilter::Gaussian: window_adapt = MakeGaussian(device, swapchain_view_format); break; + case Settings::ScalingFilter::Lanczo: + window_adapt = MakeLanczo(device, swapchain_view_format); + break; case Settings::ScalingFilter::ScaleForce: window_adapt = MakeScaleForce(device, swapchain_view_format); break;