diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.cpp b/rpcs3/Emu/RSX/GL/GLOverlays.cpp index 4e9ed84ffa..137b4138d9 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.cpp +++ b/rpcs3/Emu/RSX/GL/GLOverlays.cpp @@ -35,7 +35,7 @@ namespace gl fbo.create(); m_sampler.create(); - m_sampler.apply_defaults(input_filter); + m_sampler.apply_defaults(static_cast(m_input_filter)); m_vertex_data_buffer.create(); @@ -322,7 +322,7 @@ namespace gl "}\n"; // Smooth filtering required for inputs - input_filter = GL_LINEAR; + m_input_filter = gl::filter::linear; } gl::texture_view* ui_overlay_renderer::load_simple_image(rsx::overlays::image_info* desc, bool temp_resource, u32 owner_uid) @@ -603,11 +603,17 @@ namespace gl " ocol = color;\n" "}\n"; - input_filter = GL_LINEAR; + m_input_filter = gl::filter::linear; } - void video_out_calibration_pass::run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, bool _3d) + void video_out_calibration_pass::run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, bool _3d, gl::filter input_filter) { + if (m_input_filter != input_filter) + { + m_input_filter = input_filter; + m_sampler.set_parameteri(GL_TEXTURE_MIN_FILTER, static_cast(m_input_filter)); + m_sampler.set_parameteri(GL_TEXTURE_MAG_FILTER, static_cast(m_input_filter)); + } program_handle.uniforms["gamma"] = gamma; program_handle.uniforms["limit_range"] = limited_rgb + 0; program_handle.uniforms["stereo"] = _3d + 0; diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.h b/rpcs3/Emu/RSX/GL/GLOverlays.h index 3c62b2f783..c4c5e0f1b5 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.h +++ b/rpcs3/Emu/RSX/GL/GLOverlays.h @@ -33,7 +33,7 @@ namespace gl u32 num_drawable_elements = 4; GLenum primitives = GL_TRIANGLE_STRIP; - GLenum input_filter = GL_NEAREST; + gl::filter m_input_filter = gl::filter::nearest; u32 m_write_aspect_mask = gl::image_aspect::color | gl::image_aspect::depth; bool enable_depth_writes = false; @@ -93,7 +93,7 @@ namespace gl { video_out_calibration_pass(); - void run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, bool _3d); + void run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, bool _3d, gl::filter input_filter); }; struct rp_ssbo_to_generic_texture : public overlay_pass diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index efc4df32bb..bec6b4ed96 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -265,6 +265,8 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) const areai screen_area = coordi({}, { static_cast(buffer_width), static_cast(buffer_height) }); const bool use_full_rgb_range_output = g_cfg.video.full_rgb_range_output.get(); + // TODO: Implement FSR for OpenGL and remove this fallback to bilinear + const gl::filter filter = g_cfg.video.output_scaling == output_scaling_mode::nearest ? gl::filter::nearest : gl::filter::linear; if (use_full_rgb_range_output && rsx::fcmp(avconfig.gamma, 1.f) && !avconfig._3d) { @@ -274,7 +276,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) m_flip_fbo.color = image_to_flip; m_flip_fbo.read_buffer(m_flip_fbo.color); m_flip_fbo.draw_buffer(m_flip_fbo.color); - m_flip_fbo.blit(gl::screen, screen_area, aspect_ratio.flipped_vertical(), gl::buffers::color, gl::filter::linear); + m_flip_fbo.blit(gl::screen, screen_area, aspect_ratio.flipped_vertical(), gl::buffers::color, filter); } else { @@ -283,7 +285,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) const rsx::simple_array images{ image_to_flip, image_to_flip2 }; gl::screen.bind(); - m_video_output_pass.run(cmd, areau(aspect_ratio), images, gamma, limited_range, avconfig._3d); + m_video_output_pass.run(cmd, areau(aspect_ratio), images, gamma, limited_range, avconfig._3d, filter); } } diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index 4af1d1d80f..f943ef9288 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -74,7 +74,7 @@ private: std::unique_ptr m_text_writer; std::unique_ptr m_upscaler; - bool m_use_fsr_upscaling{false}; + output_scaling_mode m_output_scaling{output_scaling_mode::bilinear}; std::unique_ptr m_cond_render_buffer; u64 m_cond_render_sync_tag = 0; diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 49515dc200..1a280a87f3 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -6,6 +6,7 @@ #include "upscalers/bilinear_pass.hpp" #include "upscalers/fsr_pass.h" +#include "upscalers/nearest_pass.hpp" #include "util/asm.hpp" #include "util/video_provider.h" @@ -590,13 +591,17 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) target_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; } - const bool use_fsr_upscaling = g_cfg.video.vk.fsr_upscaling.get(); + const output_scaling_mode output_scaling = g_cfg.video.output_scaling.get(); - if (!m_upscaler || m_use_fsr_upscaling != use_fsr_upscaling) + if (!m_upscaler || m_output_scaling != output_scaling) { - m_use_fsr_upscaling = use_fsr_upscaling; + m_output_scaling = output_scaling; - if (m_use_fsr_upscaling) + if (m_output_scaling == output_scaling_mode::nearest) + { + m_upscaler = std::make_unique(); + } + else if (m_output_scaling == output_scaling_mode::fsr) { m_upscaler = std::make_unique(); } @@ -615,7 +620,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) if (image_to_flip) calibration_src.push_back(image_to_flip); if (image_to_flip2) calibration_src.push_back(image_to_flip2); - if (m_use_fsr_upscaling && !avconfig._3d) // 3D will be implemented later + if (m_output_scaling == output_scaling_mode::fsr && !avconfig._3d) // 3D will be implemented later { // Run upscaling pass before the rest of the output effects pipeline // This can be done with all upscalers but we already get bilinear upscaling for free if we just out the filters directly diff --git a/rpcs3/Emu/RSX/VK/upscalers/nearest_pass.hpp b/rpcs3/Emu/RSX/VK/upscalers/nearest_pass.hpp new file mode 100644 index 0000000000..8b878dd9b1 --- /dev/null +++ b/rpcs3/Emu/RSX/VK/upscalers/nearest_pass.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "upscaling.h" + +namespace vk +{ + struct nearest_upscale_pass : public upscaler + { + vk::viewable_image* scale_output( + const vk::command_buffer& cmd, // CB + vk::viewable_image* src, // Source input + VkImage present_surface, // Present target. May be VK_NULL_HANDLE for some passes + VkImageLayout present_surface_layout, // Present surface layout, or VK_IMAGE_LAYOUT_UNDEFINED if no present target is provided + const VkImageBlit& request, // Scaling request information + rsx::flags32_t mode // Mode + ) override + { + if (mode & UPSCALE_AND_COMMIT) + { + ensure(present_surface); + + src->push_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + vkCmdBlitImage(cmd, src->value, src->current_layout, present_surface, present_surface_layout, 1, &request, VK_FILTER_NEAREST); + src->pop_layout(cmd); + return nullptr; + } + + // Upscaling source only is unsupported + return src; + } + }; +} diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index c5df9d57e4..de9e9350a8 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -177,6 +177,7 @@ struct cfg_root : cfg::node cfg::_bool decr_memory_layout{ this, "DECR memory layout", false}; // Force enable increased allowed main memory range as DECR console cfg::_bool host_label_synchronization{ this, "Allow Host GPU Labels", false }; cfg::_bool disable_msl_fast_math{ this, "Disable MSL Fast Math", false }; + cfg::_enum output_scaling{ this, "Output Scaling Mode", output_scaling_mode::bilinear }; struct node_vk : cfg::node { @@ -187,7 +188,6 @@ struct cfg_root : cfg::node cfg::_bool force_primitive_restart{ this, "Force primitive restart flag" }; cfg::_enum exclusive_fullscreen_mode{ this, "Exclusive Fullscreen Mode", vk_exclusive_fs_mode::unspecified}; cfg::_bool asynchronous_texture_streaming{ this, "Asynchronous Texture Streaming 2", false }; - cfg::_bool fsr_upscaling{ this, "Enable FidelityFX Super Resolution Upscaling", false, true }; cfg::uint<0, 100> rcas_sharpening_intensity{ this, "FidelityFX CAS Sharpening Intensity", 50, true }; cfg::_enum asynchronous_scheduler{ this, "Asynchronous Queue Scheduler", vk_gpu_scheduler_mode::safe }; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 4d087a02f9..b096b52f10 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -631,3 +631,19 @@ void fmt_class_string::format(std::string& out, u64 arg) return unknown; }); } + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](output_scaling_mode value) + { + switch (value) + { + case output_scaling_mode::nearest: return "Nearest"; + case output_scaling_mode::bilinear: return "Bilinear"; + case output_scaling_mode::fsr: return "FidelityFX Super Resolution"; + } + + return unknown; + }); +} diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index 0f93970524..5b414f311f 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -305,3 +305,10 @@ enum class gpu_preset_level low, _auto }; + +enum class output_scaling_mode +{ + nearest, + bilinear, + fsr +}; diff --git a/rpcs3/VKGSRender.vcxproj b/rpcs3/VKGSRender.vcxproj index 2565a27193..56a8603dc8 100644 --- a/rpcs3/VKGSRender.vcxproj +++ b/rpcs3/VKGSRender.vcxproj @@ -13,6 +13,7 @@ + diff --git a/rpcs3/VKGSRender.vcxproj.filters b/rpcs3/VKGSRender.vcxproj.filters index 6895155f7b..153b6bc569 100644 --- a/rpcs3/VKGSRender.vcxproj.filters +++ b/rpcs3/VKGSRender.vcxproj.filters @@ -168,6 +168,9 @@ upscalers + + upscalers + diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 7e6beb2416..c5634ed17c 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -977,6 +977,14 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case gpu_preset_level::low: return tr("Low", "Shader Precision"); } break; + case emu_settings_type::OutputScalingMode: + switch (static_cast(index)) + { + case output_scaling_mode::nearest: return tr("Nearest", "Output Scaling Mode"); + case output_scaling_mode::bilinear: return tr("Bilinear", "Output Scaling Mode"); + case output_scaling_mode::fsr: return tr("FidelityFX Super Resolution", "Output Scaling Mode"); + } + break; case emu_settings_type::AudioRenderer: switch (static_cast(index)) { diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index c7012a59d1..19de9131c0 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -83,7 +83,6 @@ enum class emu_settings_type TextureLodBias, ResolutionScale, MinimumScalableDimension, - FsrUpscalingEnable, FsrSharpeningStrength, ExclusiveFullscreenMode, ForceCPUBlitEmulation, @@ -101,6 +100,7 @@ enum class emu_settings_type VulkanAsyncSchedulerDriver, AllowHostGPULabels, DisableMSLFastMath, + OutputScalingMode, // Performance Overlay PerfOverlayEnabled, @@ -272,11 +272,11 @@ inline static const QMap settings_location = { emu_settings_type::DriverWakeUpDelay, { "Video", "Driver Wake-Up Delay"}}, { emu_settings_type::AllowHostGPULabels, { "Video", "Allow Host GPU Labels"}}, { emu_settings_type::DisableMSLFastMath, { "Video", "Disable MSL Fast Math"}}, + { emu_settings_type::OutputScalingMode, { "Video", "Output Scaling Mode"}}, // Vulkan { emu_settings_type::VulkanAsyncTextureUploads, { "Video", "Vulkan", "Asynchronous Texture Streaming 2"}}, { emu_settings_type::VulkanAsyncSchedulerDriver, { "Video", "Vulkan", "Asynchronous Queue Scheduler"}}, - { emu_settings_type::FsrUpscalingEnable, { "Video", "Vulkan", "Enable FidelityFX Super Resolution Upscaling"}}, { emu_settings_type::FsrSharpeningStrength, { "Video", "Vulkan", "FidelityFX CAS Sharpening Intensity"}}, { emu_settings_type::ExclusiveFullscreenMode, { "Video", "Vulkan", "Exclusive Fullscreen Mode"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index d0b99139dd..89bd10b1c5 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -599,6 +599,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std }); SubscribeTooltip(ui->gbZCULL, tooltips.settings.zcull_operation_mode); + m_emu_settings->EnhanceComboBox(ui->outputScalingMode, emu_settings_type::OutputScalingMode); + SubscribeTooltip(ui->outputScalingMode, tooltips.settings.output_scaling_mode); + // Checkboxes: main options m_emu_settings->EnhanceCheckBox(ui->dumpColor, emu_settings_type::WriteColorBuffers); SubscribeTooltip(ui->dumpColor, tooltips.settings.dump_color); @@ -634,9 +637,6 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->asyncTextureStreaming, emu_settings_type::VulkanAsyncTextureUploads); SubscribeTooltip(ui->asyncTextureStreaming, tooltips.settings.async_texture_streaming); - m_emu_settings->EnhanceCheckBox(ui->fsrUpscalingEnable, emu_settings_type::FsrUpscalingEnable); - SubscribeTooltip(ui->fsrUpscalingEnable, tooltips.settings.fsr_upscaling); - // Radio buttons SubscribeTooltip(ui->rb_legacy_recompiler, tooltips.settings.legacy_shader_recompiler); @@ -866,15 +866,25 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std const bool is_vulkan = (text == r_creator->Vulkan.name); ui->asyncTextureStreaming->setEnabled(is_vulkan); ui->vulkansched->setEnabled(is_vulkan); - ui->fsrUpscalingEnable->setEnabled(is_vulkan); - ui->fsrSharpeningStrength->setEnabled(is_vulkan); - ui->fsrSharpeningStrengthReset->setEnabled(is_vulkan); + }; + + const auto apply_fsr_specific_options = [r_creator, this]() + { + const bool is_vulkan = (ui->renderBox->currentText() == r_creator->Vulkan.name); + const QVariantList var_list = ui->outputScalingMode->itemData(ui->outputScalingMode->currentIndex()).toList(); + ensure(var_list.size() == 2 && var_list[1].canConvert()); + const bool fsr_selected = static_cast(var_list[1].toInt()) == output_scaling_mode::fsr; + ui->fsrSharpeningStrength->setEnabled(is_vulkan && fsr_selected); + ui->fsrSharpeningStrengthReset->setEnabled(is_vulkan && fsr_selected); }; // Handle connects to disable specific checkboxes that depend on GUI state. on_strict_rendering_mode(ui->strictModeRendering->isChecked()); apply_renderer_specific_options(ui->renderBox->currentText()); // Init + apply_fsr_specific_options(); connect(ui->renderBox, &QComboBox::currentTextChanged, apply_renderer_specific_options); + connect(ui->renderBox, &QComboBox::currentTextChanged, this, apply_fsr_specific_options); + connect(ui->outputScalingMode, QOverload::of(&QComboBox::currentIndexChanged), this, apply_fsr_specific_options); // _ _ _______ _ // /\ | (_) |__ __| | | diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 96cedfac5a..15258b97dd 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -738,17 +738,13 @@ - + - Upscaling + Output Scaling - - - Enable FSR Upscaling - - + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index c484aceff6..89f1c4c9b5 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -182,8 +182,8 @@ public: const QString async_texture_streaming = tr("Stream textures to GPU in parallel with 3D rendering using asynchronous compute.\nCan improve performance on more powerful GPUs that have spare headroom.\nOnly works with Vulkan renderer and greatly benefits from having MTRSX enabled if you have a capable CPU."); const QString exclusive_fullscreen_mode = tr("Controls which fullscreen mode RPCS3 requests from drivers when using Vulkan renderer.\nAutomatic will let the driver choose an appropriate mode, while the other options will hint the drivers on whether they should use exclusive or borderless fullscreen.\nUsing Prefer borderless fullscreen option can help if you have issues with streaming RPCS3 gameplay or if your system incorrectly enables HDR mode when using fullscreen."); - const QString fsr_upscaling = tr("Enable FidelityFX Super Resolution upscaling filter to improve the look of upscaled images.\nIf the game is rendering at an internal resolution lower than your window resolution, FidelityFX will handle the upscale.\nCan cause visual artifacts.\nDoes not work with stereo 3D output for now."); - const QString fsr_rcas_strength = tr("Control the sharpening strength applied by FidelityFX Super Resolution. Higher values will give sharper output but may introduce artifacts."); + const QString output_scaling_mode = tr("Final image filtering. Nearest applies no filtering, Bilinear smooths the image, and FidelityFX Super Resolution enhances upscaled images.\nIf the game is rendering at an internal resolution lower than your window resolution, FidelityFX will handle the upscale.\nFidelityFX can cause visual artifacts.\nFidelityFX does not work with stereo 3D output for now."); + const QString fsr_rcas_strength = tr("Control the sharpening strength applied by FidelityFX Super Resolution. Higher values will give sharper output but may introduce artifacts."); const QString texture_lod_bias = tr("Changes Texture sampling accuracy. (Small changes have a big effect.)\nAvoid using values outside the range of -12 to +12 if you're unsure.\n-3 to +3 is plenty for most usecases");