GPU/HW: Add 'True Color (Full)' dithering mode

This is equivalent to the old 'True Color' mode.

The new 'True Color' mode truncates flat-shaded sprites/polygons
to 16-bit color before drawing. Doing so fixes:

- Menu background in Breath of Fire IV.
- Loading background in JumpStart Wildlife Safari - Field Trip.
- and other similar games.
This commit is contained in:
Stenzek 2025-03-22 18:26:14 +10:00
parent 2d477f80b0
commit 741e971681
No known key found for this signature in database
7 changed files with 46 additions and 8 deletions

View File

@ -80,6 +80,7 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
"ForceSoftwareRendererForReadbacks",
"ForceRoundTextureCoordinates",
"ForceShaderBlending",
"ForceFullTrueColor",
"ForceDeinterlacing",
"ForceFullBoot",
"DisableAutoAnalogMode",
@ -110,6 +111,7 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Software Renderer For Readbacks", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Round Texture Coordinates", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Shader Blending", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Full True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Deinterlacing", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Full Boot", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Automatic Analog Mode", "GameDatabase::Trait"),
@ -520,7 +522,7 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
}
if (HasTrait(Trait::DisableTrueColor) || HasTrait(Trait::DisableScaledDithering) ||
HasTrait(Trait::ForceShaderBlending))
HasTrait(Trait::ForceShaderBlending) || HasTrait(Trait::ForceFullTrueColor))
{
// Note: The order these are applied matters.
const GPUDitheringMode old_mode = settings.gpu_dithering_mode;
@ -539,6 +541,10 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
GPUDitheringMode::ScaledShaderBlend :
GPUDitheringMode::UnscaledShaderBlend;
}
if (HasTrait(Trait::ForceFullTrueColor) && settings.gpu_dithering_mode == GPUDitheringMode::TrueColor)
{
settings.gpu_dithering_mode = GPUDitheringMode::TrueColorFull;
}
if (display_osd_messages && settings.gpu_dithering_mode != old_mode)
{

View File

@ -38,6 +38,7 @@ enum class Trait : u32
ForceSoftwareRendererForReadbacks,
ForceRoundUpscaledTextureCoordinates,
ForceShaderBlending,
ForceFullTrueColor,
ForceDeinterlacing,
ForceFullBoot,
DisableAutoAnalogMode,

View File

@ -154,6 +154,21 @@ ALWAYS_INLINE_RELEASE static GSVector4i GetVRAMTransferBounds(u32 x, u32 y, u32
return ret;
}
/// Returns true if the below function should be applied.
ALWAYS_INLINE static bool ShouldTruncate32To16(const GPUBackendDrawCommand* cmd)
{
return (!cmd->texture_enable && !cmd->shading_enable && !cmd->dither_enable &&
g_gpu_settings.gpu_dithering_mode == GPUDitheringMode::TrueColor);
}
/// Truncates a 32-bit colour to 16-bit.
ALWAYS_INLINE static u32 Truncate32To16(u32 color)
{
return GSVector4i((GSVector4(GSVector4i::zext32(color).u8to32().srl32<3>()) / GSVector4::cxpr(31.0f)) *
GSVector4::cxpr(255.0f))
.rgba32();
}
namespace {
class ShaderCompileProgressTracker
{
@ -2525,7 +2540,7 @@ void GPU_HW::DrawLine(const GPUBackendDrawLineCommand* cmd)
}
AddDrawnRectangle(clamped_rect);
DrawLine(GSVector4(bounds), start_color, end_color, depth);
DrawLine(cmd, GSVector4(bounds), start_color, end_color, depth);
}
if (ShouldDrawWithSoftwareRenderer())
@ -2568,7 +2583,7 @@ void GPU_HW::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd)
}
AddDrawnRectangle(clamped_rect);
DrawLine(bounds, start_color, end_color, depth);
DrawLine(cmd, bounds, start_color, end_color, depth);
}
if (ShouldDrawWithSoftwareRenderer())
@ -2590,10 +2605,16 @@ void GPU_HW::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd)
}
}
void GPU_HW::DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth)
void GPU_HW::DrawLine(const GPUBackendDrawCommand* cmd, const GSVector4 bounds, u32 col0, u32 col1, float depth)
{
DebugAssert(m_batch_vertex_space >= 4 && m_batch_index_space >= 6);
if (ShouldTruncate32To16(cmd))
{
col0 = Truncate32To16(col0);
col1 = Truncate32To16(col1);
}
const float x0 = bounds.x;
const float y0 = bounds.y;
const float x1 = bounds.z;
@ -2717,7 +2738,9 @@ void GPU_HW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
const s32 pos_x = cmd->x;
const s32 pos_y = cmd->y;
const u32 texpage = m_draw_mode.bits;
const u32 color = (cmd->texture_enable && cmd->raw_texture_enable) ? UINT32_C(0x00808080) : cmd->color;
const u32 color = (cmd->texture_enable && cmd->raw_texture_enable) ?
UINT32_C(0x00808080) :
(ShouldTruncate32To16(cmd) ? Truncate32To16(cmd->color) : cmd->color);
const float depth = GetCurrentNormalizedVertexDepth();
const u32 orig_tex_left = ZeroExtend32(Truncate8(cmd->texcoord));
const u32 orig_tex_top = ZeroExtend32(cmd->texcoord) >> 8;
@ -2980,6 +3003,12 @@ ALWAYS_INLINE_RELEASE bool GPU_HW::BeginPolygonDraw(const GPUBackendDrawCommand*
}
}
if (ShouldTruncate32To16(cmd))
{
for (u32 i = 0; i < 4; i++)
vertices[i].color = Truncate32To16(vertices[i].color);
}
PrepareDraw(cmd);
return true;
}

View File

@ -242,7 +242,7 @@ private:
bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
/// Expands a line into two triangles.
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
void DrawLine(const GPUBackendDrawCommand* cmd, const GSVector4 bounds, u32 col0, u32 col1, float depth);
/// Computes partial derivatives and area for the given triangle. Needed for sprite/line detection.
static void ComputeUVPartialDerivatives(const BatchVertex* vertices, float* dudx, float* dudy, float* dvdx,

View File

@ -1557,7 +1557,7 @@ const char* Settings::GetTextureFilterDisplayName(GPUTextureFilter filter)
}
static constexpr const std::array s_gpu_dithering_mode_names = {
"Unscaled", "UnscaledShaderBlend", "Scaled", "ScaledShaderBlend", "TrueColor",
"Unscaled", "UnscaledShaderBlend", "Scaled", "ScaledShaderBlend", "TrueColor", "TrueColorFull",
};
static constexpr const std::array s_gpu_dithering_mode_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "Unscaled", "GPUDitheringMode"),
@ -1565,6 +1565,7 @@ static constexpr const std::array s_gpu_dithering_mode_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "Scaled", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Scaled (Shader Blending)", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "True Color", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "True Color (Full)", "GPUDitheringMode"),
};
static_assert(s_gpu_dithering_mode_names.size() == static_cast<size_t>(GPUDitheringMode::MaxCount));
static_assert(s_gpu_dithering_mode_display_names.size() == static_cast<size_t>(GPUDitheringMode::MaxCount));

View File

@ -212,7 +212,7 @@ struct GPUSettings
void SetPGXPDepthClearThreshold(float value);
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); }
ALWAYS_INLINE bool IsUsingTrueColor() const { return (gpu_dithering_mode == GPUDitheringMode::TrueColor); }
ALWAYS_INLINE bool IsUsingTrueColor() const { return (gpu_dithering_mode >= GPUDitheringMode::TrueColor); }
ALWAYS_INLINE bool IsUsingDithering() const { return (gpu_dithering_mode < GPUDitheringMode::TrueColor); }
ALWAYS_INLINE bool IsUsingShaderBlending() const
{

View File

@ -107,6 +107,7 @@ enum class GPUDitheringMode : u8
Scaled,
ScaledShaderBlend,
TrueColor,
TrueColorFull,
MaxCount,
};