diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 4dd0ec3eb..4df84f591 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -3997,6 +3997,10 @@ void FullscreenUI::DrawDisplaySettingsPage() FSUI_CSTR("Disables dithering and uses the full 8 bits per channel of color information."), "GPU", "TrueColor", true, is_hardware); + DrawToggleSetting(bsi, FSUI_CSTR("True Color Debanding"), + FSUI_CSTR("Applies modern dithering techniques to further smooth out gradients when true color is enabled."), "GPU", + "Debanding", false, is_hardware); + DrawToggleSetting(bsi, FSUI_CSTR("Widescreen Hack"), FSUI_CSTR("Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."), "GPU", "WidescreenHack", false, is_hardware); diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 2ffdd333f..f76e611b0 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -212,6 +212,7 @@ bool GPU_HW::Initialize() m_supports_framebuffer_fetch = features.framebuffer_fetch; m_per_sample_shading = g_settings.gpu_per_sample_shading && features.per_sample_shading; m_true_color = g_settings.gpu_true_color; + m_debanding = g_settings.gpu_debanding; m_scaled_dithering = g_settings.gpu_scaled_dithering; m_texture_filtering = g_settings.gpu_texture_filter; m_clamp_uvs = ShouldClampUVs(); @@ -345,7 +346,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) 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_true_color != g_settings.gpu_true_color || m_debanding != g_settings.gpu_debanding || m_per_sample_shading != per_sample_shading || m_scaled_dithering != g_settings.gpu_scaled_dithering || m_texture_filtering != g_settings.gpu_texture_filter || m_clamp_uvs != clamp_uvs || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing || m_downsample_mode != downsample_mode || @@ -395,6 +396,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) m_multisamples = multisamples; m_per_sample_shading = per_sample_shading; m_true_color = g_settings.gpu_true_color; + m_debanding = g_settings.gpu_debanding; m_scaled_dithering = g_settings.gpu_scaled_dithering; m_texture_filtering = g_settings.gpu_texture_filter; m_clamp_uvs = clamp_uvs; @@ -604,7 +606,7 @@ void GPU_HW::PrintSettingsToLog() VRAM_HEIGHT * m_resolution_scale, GetMaxResolutionScale()); Log_InfoFmt("Multisampling: {}x{}", m_multisamples, m_per_sample_shading ? " (per sample shading)" : ""); Log_InfoFmt("Dithering: {}{}", m_true_color ? "Disabled" : "Enabled", - (!m_true_color && m_scaled_dithering) ? " (Scaled)" : ""); + (!m_true_color && m_scaled_dithering) ? " (Scaled)" : ((m_true_color && m_debanding) ? " (Debanding)" : "")); Log_InfoFmt("Texture Filtering: {}", Settings::GetTextureFilterDisplayName(m_texture_filtering)); Log_InfoFmt("Dual-source blending: {}", m_supports_dual_source_blend ? "Supported" : "Not supported"); Log_InfoFmt("Clamping UVs: {}", m_clamp_uvs ? "YES" : "NO"); @@ -698,7 +700,7 @@ bool GPU_HW::CompilePipelines() const GPUDevice::Features features = g_gpu_device->GetFeatures(); GPU_HW_ShaderGen shadergen(g_gpu_device->GetRenderAPI(), m_resolution_scale, m_multisamples, m_per_sample_shading, m_true_color, m_scaled_dithering, m_texture_filtering, m_clamp_uvs, m_pgxp_depth_buffer, - m_disable_color_perspective, m_supports_dual_source_blend, m_supports_framebuffer_fetch); + m_disable_color_perspective, m_supports_dual_source_blend, m_supports_framebuffer_fetch, m_debanding); ShaderCompileProgressTracker progress("Compiling Pipelines", 2 + (4 * 5 * 9 * 2 * 2) + (3 * 4 * 5 * 9 * 2 * 2) + 1 + 2 + (2 * 2) + 2 + 1 + 1 + (2 * 3) + 1); @@ -3126,6 +3128,11 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame) ImGui::TextColored(m_true_color ? active_color : inactive_color, m_true_color ? "Enabled" : "Disabled"); ImGui::NextColumn(); + ImGui::TextUnformatted("Debanding:"); + ImGui::NextColumn(); + ImGui::TextColored(m_debanding ? active_color : inactive_color, m_debanding ? "Enabled" : "Disabled"); + ImGui::NextColumn(); + ImGui::TextUnformatted("Scaled Dithering:"); ImGui::NextColumn(); ImGui::TextColored(m_scaled_dithering ? active_color : inactive_color, m_scaled_dithering ? "Enabled" : "Disabled"); diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 2ffc31f2e..a3a27d3cd 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -252,6 +252,7 @@ private: GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled; GPUWireframeMode m_wireframe_mode = GPUWireframeMode::Disabled; bool m_true_color = true; + bool m_debanding = false; bool m_clamp_uvs = false; bool m_compute_uv_range = false; bool m_pgxp_depth_buffer = false; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index e9c8e4cf0..398592ed7 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -9,11 +9,11 @@ GPU_HW_ShaderGen::GPU_HW_ShaderGen(RenderAPI render_api, u32 resolution_scale, u bool per_sample_shading, bool true_color, bool scaled_dithering, GPUTextureFilter texture_filtering, bool uv_limits, bool pgxp_depth, bool disable_color_perspective, bool supports_dual_source_blend, - bool supports_framebuffer_fetch) + bool supports_framebuffer_fetch, bool debanding) : ShaderGen(render_api, supports_dual_source_blend, supports_framebuffer_fetch), m_resolution_scale(resolution_scale), m_multisamples(multisamples), m_per_sample_shading(per_sample_shading), m_true_color(true_color), m_scaled_dithering(scaled_dithering), m_texture_filter(texture_filtering), m_uv_limits(uv_limits), - m_pgxp_depth(pgxp_depth), m_disable_color_perspective(disable_color_perspective) + m_pgxp_depth(pgxp_depth), m_disable_color_perspective(disable_color_perspective), m_debanding(debanding) { } @@ -615,7 +615,7 @@ void FilteredSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, ialpha = res.w; texcol = float4(res.xyz, resW); - + // Compensate for partially transparent sampling. if (ialpha > 0.0) texcol.rgb /= float3(ialpha, ialpha, ialpha); @@ -662,6 +662,8 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod DefineMacro(ss, "RAW_TEXTURE", raw_texture); DefineMacro(ss, "DITHERING", dithering); DefineMacro(ss, "DITHERING_SCALED", m_scaled_dithering); + // Debanding requires true color to work correctly. + DefineMacro(ss, "DEBANDING", m_true_color && m_debanding); DefineMacro(ss, "INTERLACING", interlacing); DefineMacro(ss, "TRUE_COLOR", m_true_color); DefineMacro(ss, "TEXTURE_FILTERING", m_texture_filter != GPUTextureFilter::Nearest); @@ -761,13 +763,29 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords) return SAMPLE_TEXTURE(samp0, float2(palette_icoord) * RCP_VRAM_SIZE); #else // Direct texturing. Render-to-texture effects. Use upscaled coordinates. - uint2 icoord = ApplyUpscaledTextureWindow(FloatToIntegerCoords(coords)); + uint2 icoord = ApplyUpscaledTextureWindow(FloatToIntegerCoords(coords)); uint2 direct_icoord = texpage.xy + icoord; return SAMPLE_TEXTURE(samp0, float2(direct_icoord) * RCP_VRAM_SIZE); #endif } #endif + +// From https://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf +// and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom) +// NOTE: `frag_coord` is in pixels (i.e. not normalized UV). +float3 ApplyDebanding(float2 frag_coord) { +#if DEBANDING + // Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR. + float3 dither = float3(dot(vec2(171.0, 231.0), frag_coord)); + dither.rgb = fract(dither.rgb / float3(103.0, 71.0, 97.0)); + + // Subtract 0.5 to avoid slightly brightening the whole viewport. + return (dither.rgb - 0.5) / 255.0; +#else + return float3(0.0); +#endif +} )"; if (textured) @@ -797,7 +815,7 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords) ss << R"( { - uint3 vertcol = uint3(v_col0.rgb * float3(255.0, 255.0, 255.0)); + uint3 vertcol = uint3(v_col0.rgb * float3(255.0, 255.0, 255.0) + ApplyDebanding(v_pos.xy)); bool semitransparent; uint3 icolor; @@ -821,7 +839,7 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords) #if UV_LIMITS float4 uv_limits = v_uv_limits; #if !PALETTE - // Extend the UV range to all "upscaled" pixels. This means 1-pixel-high polygon-based + // Extend the UV range to all "upscaled" pixels. This means 1-pixel-high polygon-based // framebuffer effects won't be downsampled. (e.g. Mega Man Legends 2 haze effect) uv_limits *= float(RESOLUTION_SCALE); uv_limits.zw += float(RESOLUTION_SCALE - 1u); @@ -859,7 +877,7 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords) #endif #endif #else - icolor = uint3(texcol.rgb * float3(255.0, 255.0, 255.0)); + icolor = uint3(texcol.rgb * float3(255.0, 255.0, 255.0) + ApplyDebanding(v_pos.xy)); #if !RAW_TEXTURE icolor = (icolor * vertcol) >> 7; #if DITHERING @@ -1051,10 +1069,10 @@ float3 SampleVRAM24(uint2 icoords) uint2 vram_coords = u_vram_offset + uint2((icoords.x * 3u) / 2u, icoords.y); uint s0 = RGBA8ToRGBA5551(LoadVRAM(int2((vram_coords % clamp_size) * RESOLUTION_SCALE))); uint s1 = RGBA8ToRGBA5551(LoadVRAM(int2(((vram_coords + uint2(1, 0)) % clamp_size) * RESOLUTION_SCALE))); - + // select which part of the combined 16-bit texels we are currently shading uint s1s0 = ((s1 << 16) | s0) >> ((icoords.x & 1u) * 8u); - + // extract components and normalize return float3(float(s1s0 & 0xFFu) / 255.0, float((s1s0 >> 8u) & 0xFFu) / 255.0, float((s1s0 >> 16u) & 0xFFu) / 255.0); @@ -1113,7 +1131,7 @@ float3 SampleVRAM24Smoothed(uint2 icoords) o_col0 = float4(SampleVRAM24Smoothed(icoords), 1.0); #else o_col0 = float4(SampleVRAM24(icoords), 1.0); - #endif + #endif #else o_col0 = float4(LoadVRAM(int2((icoords + u_vram_offset) % VRAM_SIZE)).rgb, 1.0); #endif @@ -1333,7 +1351,7 @@ std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, b uint buffer_offset = u_buffer_base_offset + (offset.y * u_size.x) + offset.x; uint value = GET_VALUE(buffer_offset) | u_mask_or_bits; #endif - + o_col0 = RGBA5551ToRGBA8(value); #if !PGXP_DEPTH o_depth = (o_col0.a == 1.0) ? u_depth_value : 0.0; diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h index 576550c72..938e6e142 100644 --- a/src/core/gpu_hw_shadergen.h +++ b/src/core/gpu_hw_shadergen.h @@ -11,7 +11,7 @@ public: GPU_HW_ShaderGen(RenderAPI render_api, u32 resolution_scale, u32 multisamples, bool per_sample_shading, bool true_color, bool scaled_dithering, GPUTextureFilter texture_filtering, bool uv_limits, bool pgxp_depth, bool disable_color_perspective, bool supports_dual_source_blend, - bool supports_framebuffer_fetch); + bool supports_framebuffer_fetch, bool debanding); ~GPU_HW_ShaderGen(); std::string GenerateBatchVertexShader(bool textured); @@ -51,4 +51,5 @@ private: bool m_uv_limits; bool m_pgxp_depth; bool m_disable_color_perspective; + bool m_debanding; }; diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 15dd71717..e2f931b37 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -525,8 +525,13 @@ void ImGuiManager::DrawEnhancementsOverlay() { text.append_format(" {}x{}", g_settings.gpu_multisamples, g_settings.gpu_per_sample_shading ? "SSAA" : "MSAA"); } - if (g_settings.gpu_true_color) - text.append(" TrueCol"); + if (g_settings.gpu_true_color) { + if (g_settings.gpu_debanding) { + text.append(" TrueColDeband"); + } else { + text.append(" TrueCol"); + } + } if (g_settings.gpu_disable_interlacing) text.append(" ForceProg"); if (g_settings.gpu_force_ntsc_timings && System::GetRegion() == ConsoleRegion::PAL) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index bbcfa7155..18a6c4db5 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -190,6 +190,7 @@ void Settings::Load(SettingsInterface& si) gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false); gpu_threaded_presentation = si.GetBoolValue("GPU", "ThreadedPresentation", true); gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true); + gpu_debanding = si.GetBoolValue("GPU", "Debanding", false); gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true); gpu_texture_filter = ParseTextureFilterName( @@ -456,6 +457,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "ThreadedPresentation", gpu_threaded_presentation); si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks); si.SetBoolValue("GPU", "TrueColor", gpu_true_color); + si.SetBoolValue("GPU", "Debanding", gpu_debanding); si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode)); @@ -614,6 +616,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) g_settings.gpu_multisamples = 1; g_settings.gpu_per_sample_shading = false; g_settings.gpu_true_color = false; + g_settings.gpu_debanding = false; g_settings.gpu_scaled_dithering = false; g_settings.gpu_texture_filter = GPUTextureFilter::Nearest; g_settings.gpu_disable_interlacing = false; diff --git a/src/core/settings.h b/src/core/settings.h index aa4445219..7924a0ce6 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -111,6 +111,7 @@ struct Settings bool gpu_disable_texture_copy_to_self = false; bool gpu_per_sample_shading = false; bool gpu_true_color = true; + bool gpu_debanding = false; bool gpu_scaled_dithering = true; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE; diff --git a/src/core/system.cpp b/src/core/system.cpp index 8e4166aff..044566e09 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -3626,6 +3626,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_fifo_size != old_settings.gpu_fifo_size || g_settings.gpu_max_run_ahead != old_settings.gpu_max_run_ahead || g_settings.gpu_true_color != old_settings.gpu_true_color || + g_settings.gpu_debanding != old_settings.gpu_debanding || g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering || g_settings.gpu_texture_filter != old_settings.gpu_texture_filter || g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || diff --git a/src/duckstation-qt/enhancementsettingswidget.cpp b/src/duckstation-qt/enhancementsettingswidget.cpp index ce25bf4e6..0053037bf 100644 --- a/src/duckstation-qt/enhancementsettingswidget.cpp +++ b/src/duckstation-qt/enhancementsettingswidget.cpp @@ -80,6 +80,11 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsWindow* dialog, QWi "Disabling the option also enables dithering, which makes the transition between colours less sharp by applying " "a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't " "and will have broken effects with it enabled. Only applies to the hardware renderers.")); + dialog->registerWidgetHelp( + m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"), + tr("Applies modern dithering techniques to further smooth out gradients when true color is enabled. " + "This debanding is performed during rendering (as opposed to a post-processing step), which allows it to be fast while preserving detail. " + "Debanding increases the file size of screenshots due to the subtle dithering pattern present in screenshots.")); dialog->registerWidgetHelp( m_ui.scaledDithering, tr("Scaled Dithering (scale dither pattern to resolution)"), tr("Checked"), tr("Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less " diff --git a/src/duckstation-qt/enhancementsettingswidget.ui b/src/duckstation-qt/enhancementsettingswidget.ui index d4e873427..8a4cc52a8 100644 --- a/src/duckstation-qt/enhancementsettingswidget.ui +++ b/src/duckstation-qt/enhancementsettingswidget.ui @@ -49,13 +49,20 @@ - + True Color Rendering (24-bit, disables dithering) + + + + True Color Debanding + + +