diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index deeec14c0e..7fdf580f3c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -220,6 +220,7 @@ class SettingsFragmentPresenter( private fun addGraphicsSettings(sl: ArrayList) { sl.apply { + // TODO(crueter): reorganize this, this is awful add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_VSYNC.key) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index bbea09b1fc..bcd4949e00 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -250,6 +250,7 @@ @string/scaling_filter_gaussian @string/scaling_filter_scale_force @string/scaling_filter_fsr + @string/scaling_filter_area @@ -259,6 +260,7 @@ 3 4 5 + 6 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0e975d7732..a3bb92d27f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -863,6 +863,7 @@ Gaussian ScaleForce AMD FidelityFX™ Super Resolution + Area None diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 28de592966..9068001406 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -158,7 +164,7 @@ ENUM(ResolutionSetup, Res7X, Res8X); -ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, MaxEnum); +ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, Area, MaxEnum); ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index fe3e9e982c..688e10d2e4 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -42,6 +42,7 @@ set(SHADER_FILES opengl_present_scaleforce.frag opengl_smaa.glsl pitch_unswizzle.comp + present_area.frag present_bicubic.frag present_gaussian.frag queries_prefix_scan_sum.comp diff --git a/src/video_core/host_shaders/present_area.frag b/src/video_core/host_shaders/present_area.frag new file mode 100644 index 0000000000..41f5bc52fc --- /dev/null +++ b/src/video_core/host_shaders/present_area.frag @@ -0,0 +1,107 @@ +#version 460 core + +layout(location = 0) in vec2 frag_tex_coord; +layout(location = 0) out vec4 color; +layout(binding = 0) uniform sampler2D color_texture; + +#ifdef VULKAN + +struct ScreenRectVertex { + vec2 position; + vec2 tex_coord; +}; +layout (push_constant) uniform PushConstants { + mat4 modelview_matrix; + ScreenRectVertex vertices[4]; +}; + +#else // OpenGL + +layout(location = 1) uniform uvec2 screen_size; + +#endif + +/***** Area Sampling *****/ + +// By Sam Belliveau and Filippo Tarpini. Public Domain license. +// Effectively a more accurate sharp bilinear filter when upscaling, +// that also works as a mathematically perfect downscale filter. +// https://entropymine.com/imageworsener/pixelmixing/ +// https://github.com/obsproject/obs-studio/pull/1715 +// https://legacy.imagemagick.org/Usage/filter/ +vec4 AreaSampling(sampler2D textureSampler, vec2 texCoords, vec2 source_size, vec2 target_size) { + // Determine the sizes of the source and target images. + vec2 inverted_target_size = vec2(1.0) / target_size; + + // Determine the range of the source image that the target pixel will cover. + vec2 range = source_size * inverted_target_size; + vec2 beg = (texCoords.xy * source_size) - (range * 0.5); + vec2 end = beg + range; + + // Compute the top-left and bottom-right corners of the pixel box. + ivec2 f_beg = ivec2(floor(beg)); + ivec2 f_end = ivec2(floor(end)); + + // Compute how much of the start and end pixels are covered horizontally & vertically. + float area_w = 1.0 - fract(beg.x); + float area_n = 1.0 - fract(beg.y); + float area_e = fract(end.x); + float area_s = fract(end.y); + + // Compute the areas of the corner pixels in the pixel box. + float area_nw = area_n * area_w; + float area_ne = area_n * area_e; + float area_sw = area_s * area_w; + float area_se = area_s * area_e; + + // Initialize the color accumulator. + vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0); + + // Accumulate corner pixels. + avg_color += area_nw * texelFetch(textureSampler, ivec2(f_beg.x, f_beg.y), 0); + avg_color += area_ne * texelFetch(textureSampler, ivec2(f_end.x, f_beg.y), 0); + avg_color += area_sw * texelFetch(textureSampler, ivec2(f_beg.x, f_end.y), 0); + avg_color += area_se * texelFetch(textureSampler, ivec2(f_end.x, f_end.y), 0); + + // Determine the size of the pixel box. + int x_range = int(f_end.x - f_beg.x - 0.5); + int y_range = int(f_end.y - f_beg.y - 0.5); + + // Accumulate top and bottom edge pixels. + for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x) { + avg_color += area_n * texelFetch(textureSampler, ivec2(x, f_beg.y), 0); + avg_color += area_s * texelFetch(textureSampler, ivec2(x, f_end.y), 0); + } + + // Accumulate left and right edge pixels and all the pixels in between. + for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y) { + avg_color += area_w * texelFetch(textureSampler, ivec2(f_beg.x, y), 0); + avg_color += area_e * texelFetch(textureSampler, ivec2(f_end.x, y), 0); + + for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x) { + avg_color += texelFetch(textureSampler, ivec2(x, y), 0); + } + } + + // Compute the area of the pixel box that was sampled. + float area_corners = area_nw + area_ne + area_sw + area_se; + float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e); + float area_center = float(x_range) * float(y_range); + + // Return the normalized average color. + return avg_color / (area_corners + area_edges + area_center); +} + +void main() { + vec2 source_image_size = textureSize(color_texture, 0); + vec2 window_size; + + #ifdef VULKAN + window_size.x = vertices[1].position.x - vertices[0].position.x; + window_size.y = vertices[2].position.y - vertices[0].position.y; + #else // OpenGL + window_size = screen_size; + #endif + + color = AreaSampling(color_texture, frag_tex_coord, source_image_size, window_size); +} diff --git a/src/video_core/renderer_opengl/gl_blit_screen.cpp b/src/video_core/renderer_opengl/gl_blit_screen.cpp index 9260a4dc43..2071fe8d15 100644 --- a/src/video_core/renderer_opengl/gl_blit_screen.cpp +++ b/src/video_core/renderer_opengl/gl_blit_screen.cpp @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -86,6 +92,9 @@ void BlitScreen::CreateWindowAdapt() { case Settings::ScalingFilter::ScaleForce: window_adapt = MakeScaleForce(device); break; + case Settings::ScalingFilter::Area: + window_adapt = MakeArea(device); + break; case Settings::ScalingFilter::Fsr: case Settings::ScalingFilter::Bilinear: default: diff --git a/src/video_core/renderer_opengl/present/filters.cpp b/src/video_core/renderer_opengl/present/filters.cpp index 819e5d77f4..c5ac8e7823 100644 --- a/src/video_core/renderer_opengl/present/filters.cpp +++ b/src/video_core/renderer_opengl/present/filters.cpp @@ -1,8 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu 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_area_frag.h" #include "video_core/host_shaders/present_bicubic_frag.h" #include "video_core/host_shaders/present_gaussian_frag.h" #include "video_core/renderer_opengl/present/filters.h" @@ -36,4 +43,9 @@ std::unique_ptr MakeScaleForce(const Device& device) { fmt::format("#version 460\n{}", HostShaders::OPENGL_PRESENT_SCALEFORCE_FRAG)); } +std::unique_ptr MakeArea(const Device& device) { + return std::make_unique(device, CreateBilinearSampler(), + HostShaders::PRESENT_AREA_FRAG); +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/present/filters.h b/src/video_core/renderer_opengl/present/filters.h index 122ab74365..be2ce24842 100644 --- a/src/video_core/renderer_opengl/present/filters.h +++ b/src/video_core/renderer_opengl/present/filters.h @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,5 +19,6 @@ std::unique_ptr MakeBilinear(const Device& device); std::unique_ptr MakeBicubic(const Device& device); std::unique_ptr MakeGaussian(const Device& device); std::unique_ptr MakeScaleForce(const Device& device); +std::unique_ptr MakeArea(const Device& device); } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/present/present_uniforms.h b/src/video_core/renderer_opengl/present/present_uniforms.h index 3a19f05c72..0e08aca0a9 100644 --- a/src/video_core/renderer_opengl/present/present_uniforms.h +++ b/src/video_core/renderer_opengl/present/present_uniforms.h @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,6 +16,7 @@ namespace OpenGL { constexpr GLint PositionLocation = 0; constexpr GLint TexCoordLocation = 1; constexpr GLint ModelViewMatrixLocation = 0; +constexpr GLint ScreenSizeLocation = 1; struct ScreenRectVertex { constexpr ScreenRectVertex() = default; diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp index d8b6a11cb2..37fb766139 100644 --- a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -110,6 +116,9 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li glBindTextureUnit(0, textures[i]); glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE, matrices[i].data()); + glProgramUniform2ui(frag.handle, ScreenSizeLocation, + static_cast(layout.screen.GetWidth()), + static_cast(layout.screen.GetHeight())); glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i])); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } diff --git a/src/video_core/renderer_vulkan/present/filters.cpp b/src/video_core/renderer_vulkan/present/filters.cpp index b5e08938e7..e5365acde9 100644 --- a/src/video_core/renderer_vulkan/present/filters.cpp +++ b/src/video_core/renderer_vulkan/present/filters.cpp @@ -1,8 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/common_types.h" +#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/vulkan_present_frag_spv.h" @@ -53,4 +60,9 @@ std::unique_ptr MakeScaleForce(const Device& device, VkFormat f SelectScaleForceShader(device)); } +std::unique_ptr MakeArea(const Device& device, VkFormat frame_format) { + return std::make_unique(device, frame_format, CreateBilinearSampler(device), + BuildShader(device, PRESENT_AREA_FRAG_SPV)); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/filters.h b/src/video_core/renderer_vulkan/present/filters.h index 6c83726dd4..c8259487f8 100644 --- a/src/video_core/renderer_vulkan/present/filters.h +++ b/src/video_core/renderer_vulkan/present/filters.h @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,5 +20,6 @@ std::unique_ptr MakeBilinear(const Device& device, VkFormat fra std::unique_ptr MakeBicubic(const Device& device, VkFormat frame_format); std::unique_ptr MakeGaussian(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); } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index b7797f8337..39f07b966d 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -43,6 +49,9 @@ void BlitScreen::SetWindowAdaptPass() { case Settings::ScalingFilter::ScaleForce: window_adapt = MakeScaleForce(device, swapchain_view_format); break; + case Settings::ScalingFilter::Area: + window_adapt = MakeArea(device, swapchain_view_format); + break; case Settings::ScalingFilter::Fsr: case Settings::ScalingFilter::Bilinear: default: diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index 04904e21d5..fe3ec401aa 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -536,6 +542,7 @@ std::unique_ptr ComboboxEnumeration(QWidget* parent) PAIR(ScalingFilter, Gaussian, tr("Gaussian")), PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")), PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")), + PAIR(ScalingFilter, Area, tr("Area")), }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/yuzu/configuration/shared_translation.h b/src/yuzu/configuration/shared_translation.h index d5fc3b8def..574b1e2c78 100644 --- a/src/yuzu/configuration/shared_translation.h +++ b/src/yuzu/configuration/shared_translation.h @@ -1,3 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -40,6 +46,7 @@ static const std::map scaling_filter_texts_map {Settings::ScalingFilter::ScaleForce, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))}, {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, + {Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Area"))}, }; static const std::map use_docked_mode_texts_map = {