Add debanding option to improve gradient smoothness with truecolor

This option only has an effect if true color is also enabled.
This commit is contained in:
Hugo Locurcio 2024-01-18 23:33:37 +01:00
parent 572ea80e50
commit cc94b76037
No known key found for this signature in database
GPG key ID: 39E8F8BE30B0A49C
11 changed files with 71 additions and 18 deletions

View file

@ -3997,6 +3997,10 @@ void FullscreenUI::DrawDisplaySettingsPage()
FSUI_CSTR("Disables dithering and uses the full 8 bits per channel of color information."), "GPU", FSUI_CSTR("Disables dithering and uses the full 8 bits per channel of color information."), "GPU",
"TrueColor", true, is_hardware); "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"), 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."), FSUI_CSTR("Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."),
"GPU", "WidescreenHack", false, is_hardware); "GPU", "WidescreenHack", false, is_hardware);

View file

@ -212,6 +212,7 @@ bool GPU_HW::Initialize()
m_supports_framebuffer_fetch = features.framebuffer_fetch; m_supports_framebuffer_fetch = features.framebuffer_fetch;
m_per_sample_shading = g_settings.gpu_per_sample_shading && features.per_sample_shading; m_per_sample_shading = g_settings.gpu_per_sample_shading && features.per_sample_shading;
m_true_color = g_settings.gpu_true_color; m_true_color = g_settings.gpu_true_color;
m_debanding = g_settings.gpu_debanding;
m_scaled_dithering = g_settings.gpu_scaled_dithering; m_scaled_dithering = g_settings.gpu_scaled_dithering;
m_texture_filtering = g_settings.gpu_texture_filter; m_texture_filtering = g_settings.gpu_texture_filter;
m_clamp_uvs = ShouldClampUVs(); 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)); g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale));
const bool shaders_changed = const bool shaders_changed =
(m_resolution_scale != resolution_scale || m_multisamples != multisamples || (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_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_clamp_uvs != clamp_uvs || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing ||
m_downsample_mode != downsample_mode || m_downsample_mode != downsample_mode ||
@ -395,6 +396,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
m_multisamples = multisamples; m_multisamples = multisamples;
m_per_sample_shading = per_sample_shading; m_per_sample_shading = per_sample_shading;
m_true_color = g_settings.gpu_true_color; m_true_color = g_settings.gpu_true_color;
m_debanding = g_settings.gpu_debanding;
m_scaled_dithering = g_settings.gpu_scaled_dithering; m_scaled_dithering = g_settings.gpu_scaled_dithering;
m_texture_filtering = g_settings.gpu_texture_filter; m_texture_filtering = g_settings.gpu_texture_filter;
m_clamp_uvs = clamp_uvs; m_clamp_uvs = clamp_uvs;
@ -604,7 +606,7 @@ void GPU_HW::PrintSettingsToLog()
VRAM_HEIGHT * m_resolution_scale, GetMaxResolutionScale()); VRAM_HEIGHT * m_resolution_scale, GetMaxResolutionScale());
Log_InfoFmt("Multisampling: {}x{}", m_multisamples, m_per_sample_shading ? " (per sample shading)" : ""); Log_InfoFmt("Multisampling: {}x{}", m_multisamples, m_per_sample_shading ? " (per sample shading)" : "");
Log_InfoFmt("Dithering: {}{}", m_true_color ? "Disabled" : "Enabled", 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("Texture Filtering: {}", Settings::GetTextureFilterDisplayName(m_texture_filtering));
Log_InfoFmt("Dual-source blending: {}", m_supports_dual_source_blend ? "Supported" : "Not supported"); Log_InfoFmt("Dual-source blending: {}", m_supports_dual_source_blend ? "Supported" : "Not supported");
Log_InfoFmt("Clamping UVs: {}", m_clamp_uvs ? "YES" : "NO"); 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(); 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, 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_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 + 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); 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::TextColored(m_true_color ? active_color : inactive_color, m_true_color ? "Enabled" : "Disabled");
ImGui::NextColumn(); 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::TextUnformatted("Scaled Dithering:");
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::TextColored(m_scaled_dithering ? active_color : inactive_color, m_scaled_dithering ? "Enabled" : "Disabled"); ImGui::TextColored(m_scaled_dithering ? active_color : inactive_color, m_scaled_dithering ? "Enabled" : "Disabled");

View file

@ -252,6 +252,7 @@ private:
GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled; GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled;
GPUWireframeMode m_wireframe_mode = GPUWireframeMode::Disabled; GPUWireframeMode m_wireframe_mode = GPUWireframeMode::Disabled;
bool m_true_color = true; bool m_true_color = true;
bool m_debanding = false;
bool m_clamp_uvs = false; bool m_clamp_uvs = false;
bool m_compute_uv_range = false; bool m_compute_uv_range = false;
bool m_pgxp_depth_buffer = false; bool m_pgxp_depth_buffer = false;

View file

@ -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, bool per_sample_shading, bool true_color, bool scaled_dithering,
GPUTextureFilter texture_filtering, bool uv_limits, bool pgxp_depth, GPUTextureFilter texture_filtering, bool uv_limits, bool pgxp_depth,
bool disable_color_perspective, bool supports_dual_source_blend, 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), : 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_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_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)
{ {
} }
@ -662,6 +662,8 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod
DefineMacro(ss, "RAW_TEXTURE", raw_texture); DefineMacro(ss, "RAW_TEXTURE", raw_texture);
DefineMacro(ss, "DITHERING", dithering); DefineMacro(ss, "DITHERING", dithering);
DefineMacro(ss, "DITHERING_SCALED", m_scaled_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, "INTERLACING", interlacing);
DefineMacro(ss, "TRUE_COLOR", m_true_color); DefineMacro(ss, "TRUE_COLOR", m_true_color);
DefineMacro(ss, "TEXTURE_FILTERING", m_texture_filter != GPUTextureFilter::Nearest); DefineMacro(ss, "TEXTURE_FILTERING", m_texture_filter != GPUTextureFilter::Nearest);
@ -768,6 +770,22 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
} }
#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) if (textured)
@ -797,7 +815,7 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
ss << R"( 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; bool semitransparent;
uint3 icolor; uint3 icolor;
@ -859,7 +877,7 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
#endif #endif
#endif #endif
#else #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 #if !RAW_TEXTURE
icolor = (icolor * vertcol) >> 7; icolor = (icolor * vertcol) >> 7;
#if DITHERING #if DITHERING

View file

@ -11,7 +11,7 @@ public:
GPU_HW_ShaderGen(RenderAPI render_api, u32 resolution_scale, u32 multisamples, bool per_sample_shading, 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 true_color, bool scaled_dithering, GPUTextureFilter texture_filtering, bool uv_limits,
bool pgxp_depth, bool disable_color_perspective, bool supports_dual_source_blend, 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(); ~GPU_HW_ShaderGen();
std::string GenerateBatchVertexShader(bool textured); std::string GenerateBatchVertexShader(bool textured);
@ -51,4 +51,5 @@ private:
bool m_uv_limits; bool m_uv_limits;
bool m_pgxp_depth; bool m_pgxp_depth;
bool m_disable_color_perspective; bool m_disable_color_perspective;
bool m_debanding;
}; };

View file

@ -525,8 +525,13 @@ void ImGuiManager::DrawEnhancementsOverlay()
{ {
text.append_format(" {}x{}", g_settings.gpu_multisamples, g_settings.gpu_per_sample_shading ? "SSAA" : "MSAA"); text.append_format(" {}x{}", g_settings.gpu_multisamples, g_settings.gpu_per_sample_shading ? "SSAA" : "MSAA");
} }
if (g_settings.gpu_true_color) if (g_settings.gpu_true_color) {
if (g_settings.gpu_debanding) {
text.append(" TrueColDeband");
} else {
text.append(" TrueCol"); text.append(" TrueCol");
}
}
if (g_settings.gpu_disable_interlacing) if (g_settings.gpu_disable_interlacing)
text.append(" ForceProg"); text.append(" ForceProg");
if (g_settings.gpu_force_ntsc_timings && System::GetRegion() == ConsoleRegion::PAL) if (g_settings.gpu_force_ntsc_timings && System::GetRegion() == ConsoleRegion::PAL)

View file

@ -190,6 +190,7 @@ void Settings::Load(SettingsInterface& si)
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false); gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
gpu_threaded_presentation = si.GetBoolValue("GPU", "ThreadedPresentation", true); gpu_threaded_presentation = si.GetBoolValue("GPU", "ThreadedPresentation", true);
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", 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_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true);
gpu_texture_filter = gpu_texture_filter =
ParseTextureFilterName( ParseTextureFilterName(
@ -456,6 +457,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("GPU", "ThreadedPresentation", gpu_threaded_presentation); si.SetBoolValue("GPU", "ThreadedPresentation", gpu_threaded_presentation);
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks); si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
si.SetBoolValue("GPU", "TrueColor", gpu_true_color); si.SetBoolValue("GPU", "TrueColor", gpu_true_color);
si.SetBoolValue("GPU", "Debanding", gpu_debanding);
si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering);
si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter));
si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode)); 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_multisamples = 1;
g_settings.gpu_per_sample_shading = false; g_settings.gpu_per_sample_shading = false;
g_settings.gpu_true_color = false; g_settings.gpu_true_color = false;
g_settings.gpu_debanding = false;
g_settings.gpu_scaled_dithering = false; g_settings.gpu_scaled_dithering = false;
g_settings.gpu_texture_filter = GPUTextureFilter::Nearest; g_settings.gpu_texture_filter = GPUTextureFilter::Nearest;
g_settings.gpu_disable_interlacing = false; g_settings.gpu_disable_interlacing = false;

View file

@ -111,6 +111,7 @@ struct Settings
bool gpu_disable_texture_copy_to_self = false; bool gpu_disable_texture_copy_to_self = false;
bool gpu_per_sample_shading = false; bool gpu_per_sample_shading = false;
bool gpu_true_color = true; bool gpu_true_color = true;
bool gpu_debanding = false;
bool gpu_scaled_dithering = true; bool gpu_scaled_dithering = true;
GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE; GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE;

View file

@ -3626,6 +3626,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.gpu_fifo_size != old_settings.gpu_fifo_size || 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_max_run_ahead != old_settings.gpu_max_run_ahead ||
g_settings.gpu_true_color != old_settings.gpu_true_color || 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_scaled_dithering != old_settings.gpu_scaled_dithering ||
g_settings.gpu_texture_filter != old_settings.gpu_texture_filter || g_settings.gpu_texture_filter != old_settings.gpu_texture_filter ||
g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing ||

View file

@ -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 " "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 " "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.")); "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( dialog->registerWidgetHelp(
m_ui.scaledDithering, tr("Scaled Dithering (scale dither pattern to resolution)"), tr("Checked"), 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 " tr("Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less "

View file

@ -49,13 +49,20 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="textureFiltering"/> <widget class="QComboBox" name="textureFiltering"/>
</item> </item>
<item row="4" column="0" colspan="2"> <item row="4" column="0">
<widget class="QCheckBox" name="trueColor"> <widget class="QCheckBox" name="trueColor">
<property name="text"> <property name="text">
<string>True Color Rendering (24-bit, disables dithering)</string> <string>True Color Rendering (24-bit, disables dithering)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QCheckBox" name="debanding">
<property name="text">
<string>True Color Debanding</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="scaledDithering"> <widget class="QCheckBox" name="scaledDithering">
<property name="text"> <property name="text">