diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 51bafc967..70afe3b65 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -3907,6 +3907,17 @@ void FullscreenUI::DrawDisplaySettingsPage() "GPU", "DownsampleMode", Settings::DEFAULT_GPU_DOWNSAMPLE_MODE, &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, &Settings::GetDownsampleModeDisplayName, GPUDownsampleMode::Count, (renderer != GPURenderer::Software)); + if (Settings::ParseDownsampleModeName( + GetEffectiveStringSetting(bsi, "GPU", "DownsampleMode", + Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE)) + .c_str()) + .value_or(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE) == GPUDownsampleMode::Box) + { + DrawIntRangeSetting(bsi, FSUI_CSTR("Downsampling Display Scale"), + FSUI_CSTR("Selects the resolution scale that will be applied to the final image. 1x will " + "downsample to the original console resolution."), + "GPU", "DownsampleScale", 1, 1, GPU::MAX_RESOLUTION_SCALE, "%dx"); + } DrawEnumSetting( bsi, FSUI_CSTR("Scaling"), diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 20385c41a..2801022a0 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -19,6 +19,7 @@ #include "common/scoped_guard.h" #include "common/string_util.h" +#include "IconsFontAwesome5.h" #include "imgui.h" #include @@ -50,6 +51,14 @@ ALWAYS_INLINE static u32 GetMaxResolutionScale() return g_gpu_device->GetMaxTextureSize() / VRAM_WIDTH; } +ALWAYS_INLINE_RELEASE static u32 GetBoxDownsampleScale() +{ + u32 scale = std::min(g_settings.gpu_resolution_scale, g_settings.gpu_downsample_scale); + while ((g_settings.gpu_resolution_scale % scale) != 0) + scale--; + return scale; +} + ALWAYS_INLINE static bool ShouldUseUVLimits() { // We only need UV limits if PGXP is enabled, or texture filtering is enabled. @@ -166,38 +175,7 @@ bool GPU_HW::Initialize() m_wireframe_mode = g_settings.gpu_wireframe_mode; m_disable_color_perspective = features.noperspective_interpolation && ShouldDisableColorPerspective(); - if (m_multisamples != g_settings.gpu_multisamples) - { - Host::AddFormattedOSDMessage(Host::OSD_CRITICAL_ERROR_DURATION, - TRANSLATE("OSDMessage", "%ux MSAA is not supported, using %ux instead."), - g_settings.gpu_multisamples, m_multisamples); - } - if (!m_per_sample_shading && g_settings.gpu_per_sample_shading) - { - Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "SSAA is not supported, using MSAA instead."), 20.0f); - } - if (!features.dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering)) - { - Host::AddFormattedOSDMessage( - Host::OSD_CRITICAL_ERROR_DURATION, - TRANSLATE("OSDMessage", "Texture filter '%s' is not supported with the current renderer."), - Settings::GetTextureFilterDisplayName(m_texture_filtering)); - m_texture_filtering = GPUTextureFilter::Nearest; - } - - if (!features.noperspective_interpolation && !ShouldDisableColorPerspective()) - Log_WarningPrint("Disable color perspective not supported, but should be used."); - - if (!features.geometry_shaders && m_wireframe_mode != GPUWireframeMode::Disabled) - { - Host::AddOSDMessage( - TRANSLATE("OSDMessage", - "Geometry shaders are not supported by your GPU, and are required for wireframe rendering."), - Host::OSD_CRITICAL_ERROR_DURATION); - m_wireframe_mode = GPUWireframeMode::Disabled; - } - - m_pgxp_depth_buffer = g_settings.UsingPGXPDepthBuffer(); + CheckSettings(); UpdateSoftwareRenderer(false); @@ -316,14 +294,18 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) // TODO: Use old_settings const bool framebuffer_changed = - (m_resolution_scale != resolution_scale || m_multisamples != multisamples || m_downsample_mode != downsample_mode); + (m_resolution_scale != resolution_scale || m_multisamples != multisamples || m_downsample_mode != downsample_mode || + (m_downsample_mode == GPUDownsampleMode::Box && + g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale)); const bool shaders_changed = (m_resolution_scale != resolution_scale || m_multisamples != multisamples || m_true_color != g_settings.gpu_true_color || m_per_sample_shading != per_sample_shading || m_scaled_dithering != g_settings.gpu_scaled_dithering || m_texture_filtering != g_settings.gpu_texture_filter || m_using_uv_limits != use_uv_limits || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing || - m_downsample_mode != downsample_mode || m_wireframe_mode != wireframe_mode || - m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() || + m_downsample_mode != downsample_mode || + (m_downsample_mode == GPUDownsampleMode::Box && + g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale) || + m_wireframe_mode != wireframe_mode || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() || m_disable_color_perspective != disable_color_perspective); if (m_resolution_scale != resolution_scale) @@ -369,8 +351,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) m_wireframe_mode = wireframe_mode; m_disable_color_perspective = disable_color_perspective; - if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering)) - m_texture_filtering = GPUTextureFilter::Nearest; + CheckSettings(); if (m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer()) { @@ -404,6 +385,72 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) } } +void GPU_HW::CheckSettings() +{ + const GPUDevice::Features features = g_gpu_device->GetFeatures(); + + if (m_multisamples != g_settings.gpu_multisamples) + { + Host::AddIconOSDMessage("MSAAUnsupported", ICON_FA_PAINT_BRUSH, + fmt::format(TRANSLATE_FS("OSDMessage", "{}x MSAA is not supported, using {}x instead."), + g_settings.gpu_multisamples, m_multisamples), + Host::OSD_CRITICAL_ERROR_DURATION); + } + else + { + Host::RemoveKeyedOSDMessage("MSAAUnsupported"); + } + + if (!m_per_sample_shading && g_settings.gpu_per_sample_shading) + { + Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "SSAA is not supported, using MSAA instead."), 20.0f); + } + if (!features.dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering)) + { + Host::AddFormattedOSDMessage( + Host::OSD_CRITICAL_ERROR_DURATION, + TRANSLATE("OSDMessage", "Texture filter '%s' is not supported with the current renderer."), + Settings::GetTextureFilterDisplayName(m_texture_filtering)); + m_texture_filtering = GPUTextureFilter::Nearest; + } + + if (!features.noperspective_interpolation && !ShouldDisableColorPerspective()) + Log_WarningPrint("Disable color perspective not supported, but should be used."); + + if (!features.geometry_shaders && m_wireframe_mode != GPUWireframeMode::Disabled) + { + Host::AddOSDMessage( + TRANSLATE("OSDMessage", + "Geometry shaders are not supported by your GPU, and are required for wireframe rendering."), + Host::OSD_CRITICAL_ERROR_DURATION); + m_wireframe_mode = GPUWireframeMode::Disabled; + } + + if (m_downsample_mode == GPUDownsampleMode::Box) + { + const u32 scale = GetBoxDownsampleScale(); + if (scale != g_settings.gpu_downsample_scale || scale == g_settings.gpu_resolution_scale) + { + Host::AddIconOSDMessage( + "BoxDownsampleUnsupported", ICON_FA_PAINT_BRUSH, + fmt::format( + TRANSLATE_FS("OSDMessage", + "Resolution scale {0}x is not divisible by downsample scale {1}x, using {2}x instead."), + g_settings.gpu_resolution_scale, g_settings.gpu_downsample_scale, scale), + Host::OSD_ERROR_DURATION); + } + else + { + Host::RemoveKeyedOSDMessage("BoxDownsampleUnsupported"); + } + + if (scale == g_settings.gpu_resolution_scale) + m_downsample_mode = GPUDownsampleMode::Disabled; + } + + m_pgxp_depth_buffer = g_settings.UsingPGXPDepthBuffer(); +} + u32 GPU_HW::CalculateResolutionScale() const { const u32 max_resolution_scale = GetMaxResolutionScale(); @@ -580,8 +627,10 @@ bool GPU_HW::CreateBuffers() } else if (m_downsample_mode == GPUDownsampleMode::Box) { - if (!(m_downsample_render_texture = g_gpu_device->CreateTexture(VRAM_WIDTH, VRAM_HEIGHT, 1, 1, 1, - GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)) || + const u32 downsample_scale = GetBoxDownsampleScale(); + if (!(m_downsample_render_texture = + g_gpu_device->CreateTexture(VRAM_WIDTH * downsample_scale, VRAM_HEIGHT * downsample_scale, 1, 1, 1, + GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)) || !(m_downsample_framebuffer = g_gpu_device->CreateFramebuffer(m_downsample_render_texture.get()))) { return false; @@ -1056,8 +1105,9 @@ bool GPU_HW::CompilePipelines() } else if (m_downsample_mode == GPUDownsampleMode::Box) { - std::unique_ptr fs = - g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateBoxSampleDownsampleFragmentShader()); + std::unique_ptr fs = g_gpu_device->CreateShader( + GPUShaderStage::Fragment, + shadergen.GenerateBoxSampleDownsampleFragmentShader(m_resolution_scale / GetBoxDownsampleScale())); if (!fs) return false; @@ -2642,10 +2692,11 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top void GPU_HW::DownsampleFramebufferBoxFilter(GPUTexture* source, u32 left, u32 top, u32 width, u32 height) { - const u32 ds_left = left / m_resolution_scale; - const u32 ds_top = top / m_resolution_scale; - const u32 ds_width = width / m_resolution_scale; - const u32 ds_height = height / m_resolution_scale; + const u32 factor = m_resolution_scale / GetBoxDownsampleScale(); + const u32 ds_left = left / factor; + const u32 ds_top = top / factor; + const u32 ds_width = width / factor; + const u32 ds_height = height / factor; source->MakeReadyForSampling(); diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index a07095f8e..35a4b5af8 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -347,4 +347,5 @@ private: } void PrintSettingsToLog(); + void CheckSettings(); }; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index ded3bccf0..cb94eb431 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -1558,24 +1558,26 @@ std::string GPU_HW_ShaderGen::GenerateAdaptiveDownsampleCompositeFragmentShader( return ss.str(); } -std::string GPU_HW_ShaderGen::GenerateBoxSampleDownsampleFragmentShader() +std::string GPU_HW_ShaderGen::GenerateBoxSampleDownsampleFragmentShader(u32 factor) { std::stringstream ss; WriteHeader(ss); WriteCommonFunctions(ss); DeclareTexture(ss, "samp0", 0, false); + ss << "#define FACTOR " << factor << "\n"; + DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, false, false, false); ss << R"( { float3 color = float3(0.0, 0.0, 0.0); - uint2 base_coords = uint2(v_pos.xy) * uint2(RESOLUTION_SCALE, RESOLUTION_SCALE); - for (uint offset_x = 0u; offset_x < RESOLUTION_SCALE; offset_x++) + uint2 base_coords = uint2(v_pos.xy) * uint2(FACTOR, FACTOR); + for (uint offset_x = 0u; offset_x < FACTOR; offset_x++) { - for (uint offset_y = 0u; offset_y < RESOLUTION_SCALE; offset_y++) + for (uint offset_y = 0u; offset_y < FACTOR; offset_y++) color += LOAD_TEXTURE(samp0, int2(base_coords + uint2(offset_x, offset_y)), 0).rgb; } - color /= float(RESOLUTION_SCALE * RESOLUTION_SCALE); + color /= float(FACTOR * FACTOR); o_col0 = float4(color, 1.0); } )"; diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h index 82fd5a49d..96274dac3 100644 --- a/src/core/gpu_hw_shadergen.h +++ b/src/core/gpu_hw_shadergen.h @@ -30,7 +30,7 @@ public: std::string GenerateAdaptiveDownsampleMipFragmentShader(bool first_pass); std::string GenerateAdaptiveDownsampleBlurFragmentShader(); std::string GenerateAdaptiveDownsampleCompositeFragmentShader(); - std::string GenerateBoxSampleDownsampleFragmentShader(); + std::string GenerateBoxSampleDownsampleFragmentShader(u32 factor); private: ALWAYS_INLINE bool UsingMSAA() const { return m_multisamples > 1; } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 6e39f6393..1d213e484 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -225,6 +225,7 @@ void Settings::Load(SettingsInterface& si) ParseDownsampleModeName( si.GetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(DEFAULT_GPU_DOWNSAMPLE_MODE)).c_str()) .value_or(DEFAULT_GPU_DOWNSAMPLE_MODE); + gpu_downsample_scale = static_cast(si.GetUIntValue("GPU", "DownsampleScale", 1)); gpu_wireframe_mode = ParseGPUWireframeMode( si.GetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(DEFAULT_GPU_WIREFRAME_MODE)).c_str()) @@ -466,6 +467,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode)); + si.SetUIntValue("GPU", "DownsampleScale", gpu_downsample_scale); si.SetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(gpu_wireframe_mode)); si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing); si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings); diff --git a/src/core/settings.h b/src/core/settings.h index bb20cef8d..d5060a4b4 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -106,6 +106,7 @@ struct Settings bool gpu_scaled_dithering = true; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE; + u8 gpu_downsample_scale = 1; GPUWireframeMode gpu_wireframe_mode = DEFAULT_GPU_WIREFRAME_MODE; bool gpu_disable_interlacing = true; bool gpu_force_ntsc_timings = false; diff --git a/src/core/system.cpp b/src/core/system.cpp index 5efe29b42..51b1d934f 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -3590,6 +3590,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing || g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode || + g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale || g_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode || g_settings.display_crop_mode != old_settings.display_crop_mode || g_settings.display_aspect_ratio != old_settings.display_aspect_ratio || diff --git a/src/duckstation-qt/enhancementsettingswidget.cpp b/src/duckstation-qt/enhancementsettingswidget.cpp index 3a89d2d72..10c9c5143 100644 --- a/src/duckstation-qt/enhancementsettingswidget.cpp +++ b/src/duckstation-qt/enhancementsettingswidget.cpp @@ -20,6 +20,7 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsDialog* dialog, QWi SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.scaledDithering, "GPU", "ScaledDithering", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing", true); @@ -43,7 +44,10 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsDialog* dialog, QWi connect(m_ui.resolutionScale, QOverload::of(&QComboBox::currentIndexChanged), this, &EnhancementSettingsWidget::updateScaledDitheringEnabled); + connect(m_ui.gpuDownsampleMode, QOverload::of(&QComboBox::currentIndexChanged), this, + &EnhancementSettingsWidget::updateDownsampleScaleVisible); connect(m_ui.trueColor, &QCheckBox::stateChanged, this, &EnhancementSettingsWidget::updateScaledDitheringEnabled); + updateDownsampleScaleVisible(); updateScaledDitheringEnabled(); connect(m_ui.pgxpEnable, &QCheckBox::stateChanged, this, &EnhancementSettingsWidget::updatePGXPSettingsEnabled); @@ -55,6 +59,9 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsDialog* dialog, QWi m_ui.gpuDownsampleMode, tr("Downsampling"), tr("Disabled"), tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, " "but should be disabled for pure 3D games. Only applies to the hardware renderers.")); + dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Downsampling Display Scale"), tr("1x"), + tr("Selects the resolution scale that will be applied to the final image. 1x will " + "downsample to the original console resolution.")); dialog->registerWidgetHelp( m_ui.disableInterlacing, tr("Disable Interlacing (force progressive render/scan)"), tr("Unchecked"), tr( @@ -141,6 +148,29 @@ void EnhancementSettingsWidget::updateScaledDitheringEnabled() m_ui.scaledDithering->setEnabled(allow_scaled_dithering); } +void EnhancementSettingsWidget::updateDownsampleScaleVisible() +{ + const GPUDownsampleMode mode = + Settings::ParseDownsampleModeName( + m_dialog + ->getEffectiveStringValue("GPU", "DownsampleMode", + Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE)) + .c_str()) + .value_or(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); + + const bool visible = (mode == GPUDownsampleMode::Box); + if (visible && m_ui.gpuDownsampleLayout->indexOf(m_ui.gpuDownsampleScale) < 0) + { + m_ui.gpuDownsampleScale->setVisible(true); + m_ui.gpuDownsampleLayout->addWidget(m_ui.gpuDownsampleScale, 0); + } + else if (!visible && m_ui.gpuDownsampleLayout->indexOf(m_ui.gpuDownsampleScale) >= 0) + { + m_ui.gpuDownsampleScale->setVisible(false); + m_ui.gpuDownsampleLayout->removeWidget(m_ui.gpuDownsampleScale); + } +} + void EnhancementSettingsWidget::setupAdditionalUi() { QtUtils::FillComboBoxWithResolutionScales(m_ui.resolutionScale); diff --git a/src/duckstation-qt/enhancementsettingswidget.h b/src/duckstation-qt/enhancementsettingswidget.h index 3f328cd85..6b4c64cdf 100644 --- a/src/duckstation-qt/enhancementsettingswidget.h +++ b/src/duckstation-qt/enhancementsettingswidget.h @@ -19,6 +19,7 @@ public: private Q_SLOTS: void updateScaledDitheringEnabled(); + void updateDownsampleScaleVisible(); void updatePGXPSettingsEnabled(); private: diff --git a/src/duckstation-qt/enhancementsettingswidget.ui b/src/duckstation-qt/enhancementsettingswidget.ui index 783dcb113..86369e16a 100644 --- a/src/duckstation-qt/enhancementsettingswidget.ui +++ b/src/duckstation-qt/enhancementsettingswidget.ui @@ -88,7 +88,24 @@ - + + + + + + + + x + + + 1 + + + 16 + + + +