diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 69f1590844..49e24d3677 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -253,6 +253,7 @@ @string/scaling_filter_bspline @string/scaling_filter_mitchell @string/scaling_filter_spline1 + @string/scaling_filter_sgsr diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index d35ba4268e..2e77c9555f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1071,6 +1071,7 @@ B-Spline Mitchell MMPX + Snapdragon Game Super Resolution None diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 30d075565b..921924e6d7 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -142,7 +142,7 @@ ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never); ENUM(FullscreenMode, Borderless, Exclusive); ENUM(NvdecEmulation, Off, Cpu, Gpu); ENUM(ResolutionSetup, Res1_4X, Res1_2X, Res3_4X, Res1X, Res5_4X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X, Res8X); -ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, ScaleForce, Fsr, Area, ZeroTangent, BSpline, Mitchell, Spline1, Mmpx, MaxEnum); +ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, ScaleForce, Fsr, Area, ZeroTangent, BSpline, Mitchell, Spline1, Mmpx, Sgsr, MaxEnum); ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); ENUM(ConsoleMode, Handheld, Docked); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 0d15f9065c..7cae03dc51 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -580,6 +580,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(ScalingFilter, BSpline, tr("B-Spline")), PAIR(ScalingFilter, Mitchell, tr("Mitchell")), PAIR(ScalingFilter, Spline1, tr("Spline-1")), + PAIR(ScalingFilter, Sgsr, tr("Snapdragon Game Super Resolution")), }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/qt_common/config/shared_translation.h b/src/qt_common/config/shared_translation.h index afb18ec435..adeda5ba4f 100644 --- a/src/qt_common/config/shared_translation.h +++ b/src/qt_common/config/shared_translation.h @@ -53,6 +53,7 @@ static const std::map scaling_filter_texts_map {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR"))}, {Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Area"))}, {Settings::ScalingFilter::Mmpx, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "MMPX"))}, + {Settings::ScalingFilter::Sgsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SGSR"))}, }; static const std::map use_docked_mode_texts_map = { diff --git a/src/video_core/host_shaders/sgsr1_shader.vert b/src/video_core/host_shaders/sgsr1_shader.vert new file mode 100644 index 0000000000..6a87a7cac0 --- /dev/null +++ b/src/video_core/host_shaders/sgsr1_shader.vert @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(location = 0) out vec2 texcoord; + +void main() { + float x = float((gl_VertexIndex & 1) << 2); + float y = float((gl_VertexIndex & 2) << 1); + gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0); + texcoord = vec2(x, y) / 2.0; +} diff --git a/src/video_core/renderer_opengl/gl_blit_screen.cpp b/src/video_core/renderer_opengl/gl_blit_screen.cpp index 4b75e1b949..b03fc3ecbd 100644 --- a/src/video_core/renderer_opengl/gl_blit_screen.cpp +++ b/src/video_core/renderer_opengl/gl_blit_screen.cpp @@ -115,6 +115,7 @@ void BlitScreen::CreateWindowAdapt() { window_adapt = MakeMmpx(device); break; case Settings::ScalingFilter::Fsr: + case Settings::ScalingFilter::Sgsr: case Settings::ScalingFilter::Bilinear: default: window_adapt = MakeBilinear(device); diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp index b462c672cc..8b61e0ef28 100644 --- a/src/video_core/renderer_vulkan/present/layer.cpp +++ b/src/video_core/renderer_vulkan/present/layer.cpp @@ -64,6 +64,8 @@ Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Schedule CreateDescriptorSets(layout); if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) { fsr.emplace(device, memory_allocator, image_count, output_size); + } else if (filters.get_scaling_filter() == Settings::ScalingFilter::Sgsr) { + sgsr.emplace(device, memory_allocator, image_count, output_size); } } diff --git a/src/video_core/renderer_vulkan/present/layer.h b/src/video_core/renderer_vulkan/present/layer.h index d38b81823e..6de7a66087 100644 --- a/src/video_core/renderer_vulkan/present/layer.h +++ b/src/video_core/renderer_vulkan/present/layer.h @@ -96,6 +96,7 @@ private: Settings::AntiAliasing anti_alias_setting{}; std::variant anti_alias{}; std::optional fsr{}; + std::unique_ptr sgsr{}; std::vector resource_ticks{}; }; diff --git a/src/video_core/renderer_vulkan/present/sgsr.cpp b/src/video_core/renderer_vulkan/present/sgsr.cpp new file mode 100644 index 0000000000..8a1e23d5e7 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/sgsr.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "common/settings.h" + +#include "video_core/fsr.h" +#include "video_core/host_shaders/sgsr1_shader_mobile_frag_spv.h" +#include "video_core/host_shaders/sgsr1_shader_mobile_edge_direction_frag_spv.h" +#include "video_core/host_shaders/sgsr1_shader_vert_spv.h" +#include "video_core/renderer_vulkan/present/fsr.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" + +namespace Vulkan { +using namespace SGSR; + +using PushConstants = std::array; + +SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent) + : m_device{device}, m_memory_allocator{memory_allocator} + , m_image_count{image_count}, m_extent{extent} +{ + m_dynamic_images.resize(m_image_count); + for (auto& images : m_dynamic_images) { + images.images[0] = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.images[1] = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[0] = CreateWrappedImageView(m_device, images.images[0], VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[1] = CreateWrappedImageView(m_device, images.images[1], VK_FORMAT_R16G16B16A16_SFLOAT); + } + + m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + for (auto& images : m_dynamic_images) { + images.framebuffers[0] = CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[0], m_extent); + images.framebuffers[1] = CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[1], m_extent); + } + + m_sampler = CreateBilinearSampler(m_device); + m_vert_shader = BuildShader(m_device, SGSR1_SHADER_VERT_SPV); + m_stage_shader[0] = BuildShader(m_device, SGSR1_SHADER_MOBILE_FRAG_SPV); + m_stage_shader[1] = BuildShader(m_device, SGSR1_SHADER_MOBILE_EDGE_DIRECTION_FRAG_SPV); + // 2 descriptors, 2 descriptor sets per invocation + m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, 2 * m_image_count); + m_descriptor_set_layout = CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + + std::vector layouts(MaxSgsrStage, *m_descriptor_set_layout); + for (auto& images : m_dynamic_images) + images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layouts); + + const VkPushConstantRange range{ + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = sizeof(PushConstants), + }; + VkPipelineLayoutCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = m_descriptor_set_layout.address(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &range, + }; + m_pipeline_layout = m_device.GetLogical().CreatePipelineLayout(ci); + + m_stage_pipeline[0] = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, std::tie(m_vert_shader, m_stage_shader[0])); + m_stage_pipeline[1] = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, std::tie(m_vert_shader, m_stage_shader[1])); +} + +void SGSR::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { + Images& images = m_dynamic_images[image_index]; + std::vector image_infos; + std::vector updates; + image_infos.reserve(2); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, images.descriptor_sets[0], 0)); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[0], images.descriptor_sets[1], 0)); + m_device.GetLogical().UpdateDescriptorSets(updates, {}); +} + +void SGSR::UploadImages(Scheduler& scheduler) { + if (!m_images_ready) { + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + for (auto& image : m_dynamic_images) { + ClearColorImage(cmdbuf, *image.images[0]); + ClearColorImage(cmdbuf, *image.images[1]); + } + }); + scheduler.Finish(); + m_images_ready = true; + } +} + +VkImageView SGSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, VkImageView source_image_view, VkExtent2D input_image_extent, const Common::Rectangle& crop_rect) { + Images& images = m_dynamic_images[image_index]; + VkImage stage0_image = *images.images[0]; + VkImage stage1_image = *images.images[1]; + VkDescriptorSet stage0_descriptor_set = images.descriptor_sets[0]; + VkDescriptorSet stage1_descriptor_set = images.descriptor_sets[1]; + VkFramebuffer stage0_framebuffer = *images.framebuffers[0]; + VkFramebuffer stage1_framebuffer = *images.framebuffers[1]; + VkPipelineLayout pipeline_layout = *m_pipeline_layout; + VkRenderPass renderpass = *m_renderpass; + VkExtent2D extent = m_extent; + + const f32 input_image_width = f32(input_image_extent.width); + const f32 input_image_height = f32(input_image_extent.height); + const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width; + const f32 viewport_x = crop_rect.left * input_image_width; + const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height; + const f32 viewport_y = crop_rect.top * input_image_height; + + // highp vec4 + PushConstants viewport_con{}; + *reinterpret_cast(viewport_con.data() + 0) = viewport_x; + *reinterpret_cast(viewport_con.data() + 1) = viewport_y; + *reinterpret_cast(viewport_con.data() + 2) = viewport_width; + *reinterpret_cast(viewport_con.data() + 3) = viewport_height; + + UploadImages(scheduler); + UpdateDescriptorSets(source_image_view, image_index); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([=](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, stage0_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, stage0_framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, m_stage_pipeline[0]); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, stage0_descriptor_set, {}); + cmdbuf.PushConstants(pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, viewport_con); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + // + TransitionImageLayout(cmdbuf, stage0_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, stage1_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, stage1_framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, m_stage_pipeline[1]); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, stage1_descriptor_set, {}); + cmdbuf.PushConstants(pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, viewport_con); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + TransitionImageLayout(cmdbuf, stage1_image, VK_IMAGE_LAYOUT_GENERAL); + }); + + return *images.image_views[1]; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/sgsr.h b/src/video_core/renderer_vulkan/present/sgsr.h new file mode 100644 index 0000000000..97691c9a90 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/sgsr.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/math_util.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 SGSR { +public: + explicit SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent); + VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, + VkImageView source_image_view, VkExtent2D input_image_extent, + const Common::Rectangle& crop_rect); + +private: + void UploadImages(Scheduler& scheduler); + void UpdateDescriptorSets(VkImageView image_view, size_t image_index); + + const Device& m_device; + MemoryAllocator& m_memory_allocator; + const size_t m_image_count; + const VkExtent2D m_extent; + + vk::DescriptorPool m_descriptor_pool; + vk::DescriptorSetLayout m_descriptor_set_layout; + vk::PipelineLayout m_pipeline_layout; + vk::ShaderModule m_vert_shader; + vk::ShaderModule m_stage_shader[2]; + vk::Pipeline m_stage_pipeline[2]; + vk::RenderPass m_renderpass; + vk::Sampler m_sampler; + + struct Images { + vk::DescriptorSets descriptor_sets; + std::array images; + std::array image_views; + std::array framebuffers; + }; + std::vector m_dynamic_images; + bool m_images_ready{}; +}; + +} // 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 0f54dd5ade..27198dc48f 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -72,6 +72,7 @@ void BlitScreen::SetWindowAdaptPass() { window_adapt = MakeMmpx(device, swapchain_view_format); break; case Settings::ScalingFilter::Fsr: + case Settings::ScalingFilter::Sgsr: case Settings::ScalingFilter::Bilinear: default: window_adapt = MakeBilinear(device, swapchain_view_format); diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 5fee35dc7f..60d209a117 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -4189,8 +4189,7 @@ void MainWindow::UpdateAPIText() { void MainWindow::UpdateFilterText() { const auto filter = Settings::values.scaling_filter.GetValue(); const auto filter_text = ConfigurationShared::scaling_filter_texts_map.find(filter)->second; - filter_status_button->setText(filter == Settings::ScalingFilter::Fsr ? tr("FSR") - : filter_text.toUpper()); + filter_status_button->setText(filter_text.toUpper()); } void MainWindow::UpdateAAText() {