mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-01-26 20:53:29 +00:00
Merge branch 'feat_renderer_Lanczos' into 'main'
feat(renderer): Add configurable Lanczos upscaling filter See merge request citron/emulator!84
This commit is contained in:
@@ -127,6 +127,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
|
||||
INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QStringLiteral());
|
||||
INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"),
|
||||
tr("Determines how sharpened the image will look while using FSR's dynamic contrast."));
|
||||
INSERT(Settings, lanczos_quality, tr("Lanczos Quality:"), tr("The quality of the Lanczos filter. Higher is sharper but more expensive."));
|
||||
INSERT(Settings, fsr2_quality_mode, tr("FSR 2.0 Quality Mode:"),
|
||||
tr("Selects the quality mode for FSR 2.0 upscaling. Quality provides better image quality, Performance provides better performance."));
|
||||
|
||||
@@ -401,6 +402,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
|
||||
PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")),
|
||||
PAIR(ScalingFilter, Bilinear, tr("Bilinear")),
|
||||
PAIR(ScalingFilter, Bicubic, tr("Bicubic")),
|
||||
PAIR(ScalingFilter, Lanczos, tr("Lanczos")),
|
||||
PAIR(ScalingFilter, Gaussian, tr("Gaussian")),
|
||||
PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")),
|
||||
PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")),
|
||||
|
||||
@@ -39,6 +39,7 @@ static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map
|
||||
{Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
|
||||
{Settings::ScalingFilter::Gaussian,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
|
||||
{Settings::ScalingFilter::Lanczos, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Lanczos"))},
|
||||
{Settings::ScalingFilter::ScaleForce,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
|
||||
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
|
||||
|
||||
@@ -348,6 +348,17 @@ struct Values {
|
||||
true,
|
||||
true};
|
||||
|
||||
|
||||
SwitchableSetting<int, true> lanczos_quality{linkage,
|
||||
3, // Default value
|
||||
2, // Minimum value
|
||||
4, // Maximum value
|
||||
"lanczos_quality",
|
||||
Category::Renderer,
|
||||
Specialization::Scalar,
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<FSR2QualityMode, true> fsr2_quality_mode{linkage,
|
||||
FSR2QualityMode::Quality, // Quality by default
|
||||
FSR2QualityMode::Quality, // Min value
|
||||
|
||||
@@ -147,7 +147,7 @@ ENUM(NvdecEmulation, Off, Cpu, Gpu);
|
||||
ENUM(ResolutionSetup, Res1_2X, Res3_4X, Res1X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X,
|
||||
Res8X);
|
||||
|
||||
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, Fsr2, MaxEnum);
|
||||
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Lanczos, Fsr, Fsr2, MaxEnum);
|
||||
|
||||
ENUM(AntiAliasing, None, Fxaa, Smaa, Taa, MaxEnum);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
|
||||
@@ -42,6 +43,7 @@ set(SHADER_FILES
|
||||
opengl_smaa.glsl
|
||||
pitch_unswizzle.comp
|
||||
present_bicubic.frag
|
||||
present_lanczos.frag
|
||||
present_gaussian.frag
|
||||
queries_prefix_scan_sum.comp
|
||||
queries_prefix_scan_sum_nosubgroups.comp
|
||||
|
||||
86
src/video_core/host_shaders/present_lanczos.frag
Normal file
86
src/video_core/host_shaders/present_lanczos.frag
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#version 460 core
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
|
||||
// This block defines the variable that will hold our setting value.
|
||||
// It uses a preprocessor directive to switch between the OpenGL and Vulkan way of doing things.
|
||||
#ifdef VULKAN
|
||||
layout(push_constant) uniform LanczosPushConstant {
|
||||
layout(offset = 128) int u_lanczos_a;
|
||||
} lanczos_pc;
|
||||
#else // OpenGL
|
||||
layout(location = 0) uniform int u_lanczos_a;
|
||||
#endif
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
|
||||
float sinc(float x) {
|
||||
if (x == 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
return sin(PI * x) / (PI * x);
|
||||
}
|
||||
|
||||
float lanczos_weight(float x, float a) {
|
||||
if (abs(x) < a) {
|
||||
return sinc(x) * sinc(x / a);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
vec4 textureLanczos(sampler2D ts, vec2 tc) {
|
||||
// Get the 'a' value from the correct uniform based on the renderer
|
||||
#ifdef VULKAN
|
||||
const int a_val = lanczos_pc.u_lanczos_a;
|
||||
#else
|
||||
const int a_val = u_lanczos_a;
|
||||
#endif
|
||||
const float a = float(a_val);
|
||||
|
||||
// If 'a' is 0 (which it will be by default since we aren't sending a value yet),
|
||||
// just do a basic sample to avoid errors.
|
||||
if (a_val == 0) {
|
||||
return texture(ts, tc);
|
||||
}
|
||||
|
||||
vec2 tex_size = vec2(textureSize(ts, 0));
|
||||
vec2 inv_tex_size = 1.0 / tex_size;
|
||||
vec2 p = tc * tex_size;
|
||||
vec2 f = fract(p);
|
||||
vec2 p_int = p - f;
|
||||
|
||||
vec4 sum = vec4(0.0);
|
||||
float weight_sum = 0.0;
|
||||
|
||||
const int MAX_A = 4;
|
||||
for (int y = -MAX_A + 1; y <= MAX_A; ++y) {
|
||||
if (abs(y) >= a_val) continue;
|
||||
for (int x = -MAX_A + 1; x <= MAX_A; ++x) {
|
||||
if (abs(x) >= a_val) continue;
|
||||
|
||||
vec2 offset = vec2(float(x), float(y));
|
||||
float w = lanczos_weight(f.x - offset.x, a) * lanczos_weight(f.y - offset.y, a);
|
||||
|
||||
if (w != 0.0) {
|
||||
sum += texture(ts, (p_int + offset) * inv_tex_size) * w;
|
||||
weight_sum += w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (weight_sum == 0.0) {
|
||||
return texture(ts, tc);
|
||||
}
|
||||
|
||||
return sum / weight_sum;
|
||||
}
|
||||
|
||||
void main() {
|
||||
color = textureLanczos(color_texture, frag_tex_coord);
|
||||
}
|
||||
@@ -81,6 +81,9 @@ void BlitScreen::CreateWindowAdapt() {
|
||||
case Settings::ScalingFilter::Bicubic:
|
||||
window_adapt = MakeBicubic(device);
|
||||
break;
|
||||
case Settings::ScalingFilter::Lanczos:
|
||||
window_adapt = MakeLanczos(device);
|
||||
break;
|
||||
case Settings::ScalingFilter::Gaussian:
|
||||
window_adapt = MakeGaussian(device);
|
||||
break;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "video_core/host_shaders/opengl_present_frag.h"
|
||||
#include "video_core/host_shaders/opengl_present_scaleforce_frag.h"
|
||||
#include "video_core/host_shaders/present_bicubic_frag.h"
|
||||
#include "video_core/host_shaders/present_lanczos_frag.h"
|
||||
#include "video_core/host_shaders/present_gaussian_frag.h"
|
||||
#include "video_core/renderer_opengl/present/filters.h"
|
||||
#include "video_core/renderer_opengl/present/util.h"
|
||||
@@ -36,4 +38,9 @@ std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device) {
|
||||
fmt::format("#version 460\n{}", HostShaders::OPENGL_PRESENT_SCALEFORCE_FRAG));
|
||||
}
|
||||
|
||||
std::unique_ptr<WindowAdaptPass> MakeLanczos(const Device& device) {
|
||||
return std::make_unique<WindowAdaptPass>(device, CreateNearestNeighborSampler(),
|
||||
HostShaders::PRESENT_LANCZOS_FRAG);
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -11,6 +12,7 @@ namespace OpenGL {
|
||||
std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device);
|
||||
std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device);
|
||||
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device);
|
||||
std::unique_ptr<WindowAdaptPass> MakeLanczos(const Device& device);
|
||||
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device);
|
||||
std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device);
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width),
|
||||
static_cast<GLfloat>(layout.height));
|
||||
|
||||
glEnableVertexAttribArray(PositionLocation);
|
||||
glEnableVertexAttribArray(TexCoordLocation);
|
||||
glVertexAttribDivisor(PositionLocation, 0);
|
||||
glVertexAttribDivisor(TexCoordLocation, 0);
|
||||
glEnableVertexAttribArray(PositionLocation);
|
||||
glEnableVertexAttribArray(TexCoordLocation);
|
||||
glVertexAttribFormat(PositionLocation, 2, GL_FLOAT, GL_FALSE,
|
||||
offsetof(ScreenRectVertex, position));
|
||||
glVertexAttribFormat(TexCoordLocation, 2, GL_FLOAT, GL_FALSE,
|
||||
@@ -84,7 +84,6 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
|
||||
glBindSampler(0, sampler.handle);
|
||||
|
||||
// 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);
|
||||
@@ -107,6 +106,11 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Lanczos) {
|
||||
glProgramUniform1i(frag.handle, 0, Settings::values.lanczos_quality.GetValue());
|
||||
}
|
||||
|
||||
glBindTextureUnit(0, textures[i]);
|
||||
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
|
||||
matrices[i].data());
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// 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/present_bicubic_frag_spv.h"
|
||||
#include "video_core/host_shaders/present_lanczos_frag_spv.h"
|
||||
#include "video_core/host_shaders/present_gaussian_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"
|
||||
@@ -53,4 +55,9 @@ std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat f
|
||||
SelectScaleForceShader(device));
|
||||
}
|
||||
|
||||
std::unique_ptr<WindowAdaptPass> MakeLanczos(const Device& device, VkFormat frame_format) {
|
||||
return std::make_unique<WindowAdaptPass>(device, frame_format, CreateNearestNeighborSampler(device),
|
||||
BuildShader(device, PRESENT_LANCZOS_FRAG_SPV));
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -12,6 +13,7 @@ class MemoryAllocator;
|
||||
std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device, VkFormat frame_format);
|
||||
std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device, VkFormat frame_format);
|
||||
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format);
|
||||
std::unique_ptr<WindowAdaptPass> MakeLanczos(const Device& device, VkFormat frame_format);
|
||||
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format);
|
||||
std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat frame_format);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -92,8 +94,16 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
|
||||
|
||||
for (size_t i = 0; i < layer_count; i++) {
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipelines[i]);
|
||||
cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
|
||||
push_constants[i]);
|
||||
cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
||||
sizeof(PresentPushConstants), &push_constants[i]);
|
||||
|
||||
|
||||
if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Lanczos) {
|
||||
const s32 lanczos_a = Settings::values.lanczos_quality.GetValue();
|
||||
cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
sizeof(PresentPushConstants), sizeof(s32), &lanczos_a);
|
||||
}
|
||||
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0,
|
||||
descriptor_sets[i], {});
|
||||
cmdbuf.Draw(4, 1, 0, 0);
|
||||
@@ -117,20 +127,31 @@ void WindowAdaptPass::CreateDescriptorSetLayout() {
|
||||
}
|
||||
|
||||
void WindowAdaptPass::CreatePipelineLayout() {
|
||||
const VkPushConstantRange range{
|
||||
|
||||
std::array<VkPushConstantRange, 2> ranges{};
|
||||
|
||||
// Range 0: The existing constants for the Vertex Shader
|
||||
ranges[0] = {
|
||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.offset = 0,
|
||||
.size = sizeof(PresentPushConstants),
|
||||
};
|
||||
|
||||
// Range 1: Our new constant for the Fragment Shader
|
||||
ranges[1] = {
|
||||
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.offset = sizeof(PresentPushConstants),
|
||||
.size = sizeof(s32),
|
||||
};
|
||||
|
||||
pipeline_layout = device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.setLayoutCount = 1,
|
||||
.pSetLayouts = descriptor_set_layout.address(),
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &range,
|
||||
.pushConstantRangeCount = static_cast<u32>(ranges.size()),
|
||||
.pPushConstantRanges = ranges.data(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ void BlitScreen::SetWindowAdaptPass() {
|
||||
case Settings::ScalingFilter::Bicubic:
|
||||
window_adapt = MakeBicubic(device, swapchain_view_format);
|
||||
break;
|
||||
case Settings::ScalingFilter::Lanczos:
|
||||
window_adapt = MakeLanczos(device, swapchain_view_format);
|
||||
break;
|
||||
case Settings::ScalingFilter::Gaussian:
|
||||
window_adapt = MakeGaussian(device, swapchain_view_format);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user