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",
"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);

View file

@ -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");

View file

@ -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;

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,
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;

View file

@ -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;
};

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");
}
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)

View file

@ -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;

View file

@ -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;

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_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 ||

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 "
"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 "

View file

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