GPU: Add option to crop vertex colours before modulation

aka "old" GPU.
This commit is contained in:
Stenzek 2025-12-24 00:38:57 +10:00
parent ad0312ec82
commit c0f6ae1424
No known key found for this signature in database
14 changed files with 270 additions and 106 deletions

View File

@ -3957,6 +3957,11 @@ void FullscreenUI::DrawGraphicsSettingsPage()
MenuHeading(FSUI_VSTR("Advanced Rendering Options"));
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BOLT, "Threaded Rendering"),
FSUI_VSTR("Uses a second thread for drawing graphics. Provides a significant speed improvement "
"particularly with the software renderer, and is safe to use."),
"GPU", "UseThread", true);
if (is_hardware)
{
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_GRIP_LINES_VERTICAL, "Line Detection"),
@ -3971,6 +3976,11 @@ void FullscreenUI::DrawGraphicsSettingsPage()
"less noticeable. Usually safe to enable."),
"GPU", "ScaledInterlacing", true, resolution_scale > 1);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SWATCHBOOK, "Texture Modulation Cropping (\"Old/v0\" GPU)"),
FSUI_VSTR("Crops vertex colours to 5:5:5 before modulating with the texture colour, which "
"typically results in more visible banding."),
"GPU", "EnableModulationCrop", false);
DrawToggleSetting(
bsi, FSUI_ICONVSTR(ICON_FA_DOWNLOAD, "Use Software Renderer For Readbacks"),
FSUI_VSTR("Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in "
@ -3978,11 +3988,6 @@ void FullscreenUI::DrawGraphicsSettingsPage()
"GPU", "UseSoftwareRendererForReadbacks", false);
}
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BOLT, "Threaded Rendering"),
FSUI_VSTR("Uses a second thread for drawing graphics. Provides a significant speed improvement "
"particularly with the software renderer, and is safe to use."),
"GPU", "UseThread", true);
if (is_hardware && pgxp_enabled)
{
MenuHeading(FSUI_VSTR("PGXP (Precision Geometry Transform Pipeline)"));

View File

@ -215,6 +215,7 @@ TRANSLATE_NOOP("FullscreenUI", "Create");
TRANSLATE_NOOP("FullscreenUI", "Create New...");
TRANSLATE_NOOP("FullscreenUI", "Create Save State Backups");
TRANSLATE_NOOP("FullscreenUI", "Crop Mode");
TRANSLATE_NOOP("FullscreenUI", "Crops vertex colours to 5:5:5 before modulating with the texture colour, which typically results in more visible banding.");
TRANSLATE_NOOP("FullscreenUI", "Culling Correction");
TRANSLATE_NOOP("FullscreenUI", "Custom");
TRANSLATE_NOOP("FullscreenUI", "Dark");
@ -761,6 +762,7 @@ TRANSLATE_NOOP("FullscreenUI", "Synchronizes presentation of the console's frame
TRANSLATE_NOOP("FullscreenUI", "Temporarily disables all enhancements, useful when testing.");
TRANSLATE_NOOP("FullscreenUI", "Test Unofficial Achievements");
TRANSLATE_NOOP("FullscreenUI", "Texture Filtering");
TRANSLATE_NOOP("FullscreenUI", "Texture Modulation Cropping (\"Old/v0\" GPU)");
TRANSLATE_NOOP("FullscreenUI", "Texture Replacements");
TRANSLATE_NOOP("FullscreenUI", "Textures Directory");
TRANSLATE_NOOP("FullscreenUI", "The SDL input source supports most controllers.");

View File

@ -530,6 +530,7 @@ bool GPU_HW::UpdateSettings(const GPUSettings& old_settings, Error* error)
g_gpu_settings.gpu_scaled_interlacing != old_settings.gpu_scaled_interlacing)) ||
(resolution_scale > 1 && g_gpu_settings.gpu_texture_filter == GPUTextureFilter::Nearest &&
g_gpu_settings.gpu_force_round_texcoords != old_settings.gpu_force_round_texcoords) ||
g_gpu_settings.gpu_modulation_crop != old_settings.gpu_modulation_crop ||
g_gpu_settings.IsUsingShaderBlending() != old_settings.IsUsingShaderBlending() ||
m_texture_filtering != g_gpu_settings.gpu_texture_filter ||
m_sprite_texture_filtering != g_gpu_settings.gpu_sprite_texture_filter || m_clamp_uvs != clamp_uvs ||
@ -1106,6 +1107,7 @@ bool GPU_HW::CompilePipelines(Error* error)
const bool per_sample_shading = (msaa && g_gpu_settings.gpu_per_sample_shading && features.per_sample_shading);
const bool force_round_texcoords =
(upscaled && m_texture_filtering == GPUTextureFilter::Nearest && g_gpu_settings.gpu_force_round_texcoords);
const bool modulation_crop = g_gpu_settings.gpu_modulation_crop;
const bool true_color = g_gpu_settings.IsUsingTrueColor();
const bool scaled_dithering = (!m_true_color && upscaled && g_gpu_settings.IsUsingScaledDithering());
const bool scaled_interlacing = (upscaled && g_gpu_settings.gpu_scaled_interlacing);
@ -1315,10 +1317,10 @@ bool GPU_HW::CompilePipelines(Error* error)
const std::string fs = shadergen.GenerateBatchFragmentShader(
static_cast<BatchRenderMode>(render_mode), static_cast<GPUTransparencyMode>(transparency_mode),
shader_texmode, texture_filter, texture_filter_is_blended, upscaled, msaa, per_sample_shading,
uv_limits, !sprite && force_round_texcoords, true_color, ConvertToBoolUnchecked(dithering),
scaled_dithering, disable_color_perspective, ConvertToBoolUnchecked(interlacing), scaled_interlacing,
ConvertToBoolUnchecked(check_mask), m_write_mask_as_depth, use_rov, needs_rov_depth, rov_depth_test,
rov_depth_write);
uv_limits, !sprite && force_round_texcoords, modulation_crop, true_color,
ConvertToBoolUnchecked(dithering), scaled_dithering, disable_color_perspective,
ConvertToBoolUnchecked(interlacing), scaled_interlacing, ConvertToBoolUnchecked(check_mask),
m_write_mask_as_depth, use_rov, needs_rov_depth, rov_depth_test, rov_depth_write);
if (!(batch_fragment_shaders[depth_test][render_mode][transparency_mode][texture_mode][check_mask]
[dithering][interlacing] = g_gpu_device->CreateShader(
@ -2876,7 +2878,9 @@ void GPU_HW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
if (draw_with_software_renderer)
{
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawRectangleFunction(
cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
GPU_SW_Rasterizer::GetModulationMode(cmd->texture_enable, cmd->raw_texture_enable,
g_gpu_settings.gpu_modulation_crop),
cmd->transparency_enable);
DrawFunction(cmd);
}
}
@ -2907,7 +2911,10 @@ void GPU_HW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
if (m_draw_with_software_renderer)
{
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
cmd->shading_enable,
GPU_SW_Rasterizer::GetModulationMode(cmd->texture_enable, cmd->raw_texture_enable,
g_gpu_settings.gpu_modulation_crop),
cmd->transparency_enable);
DrawFunction(cmd, &cmd->vertices[0], &cmd->vertices[1], &cmd->vertices[2]);
if (cmd->num_vertices > 3)
DrawFunction(cmd, &cmd->vertices[2], &cmd->vertices[1], &cmd->vertices[3]);
@ -2951,7 +2958,10 @@ void GPU_HW::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd)
if (m_draw_with_software_renderer)
{
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
cmd->shading_enable,
GPU_SW_Rasterizer::GetModulationMode(cmd->texture_enable, cmd->raw_texture_enable,
g_gpu_settings.gpu_modulation_crop),
cmd->transparency_enable);
GPUBackendDrawPolygonCommand::Vertex sw_vertices[4];
for (u32 i = 0; i < cmd->num_vertices; i++)
{

View File

@ -727,7 +727,8 @@ void FilteredSampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords, float4 uv_limi
}
else if (texture_filter == GPUTextureFilter::MMPX)
{
ss << "#define src(xoffs, yoffs) packUnorm4x8(SampleFromVRAM(texpage, bcoords + float2((xoffs), (yoffs)), uv_limits))\n";
ss << "#define src(xoffs, yoffs) packUnorm4x8(SampleFromVRAM(texpage, bcoords + float2((xoffs), (yoffs)), "
"uv_limits))\n";
/*
* This part of the shader is from MMPX.glc from https://casual-effects.com/research/McGuire2021PixelArt/index.html
@ -834,7 +835,8 @@ void FilteredSampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords, float4 uv_limi
}
else if (texture_filter == GPUTextureFilter::MMPXEnhanced)
{
ss << "#define src(xoffs, yoffs) packUnorm4x8(SampleFromVRAM(texpage, bcoords + float2((xoffs), (yoffs)), uv_limits))\n";
ss << "#define src(xoffs, yoffs) packUnorm4x8(SampleFromVRAM(texpage, bcoords + float2((xoffs), (yoffs)), "
"uv_limits))\n";
/*
* This part of the shader is from MMPX.glc from https://casual-effects.com/research/McGuire2021PixelArt/index.html
@ -1180,9 +1182,10 @@ void FilteredSampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords, float4 uv_limi
std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency, GPU_HW::BatchTextureMode texture_mode,
GPUTextureFilter texture_filtering, bool is_blended_texture_filtering, bool upscaled, bool msaa,
bool per_sample_shading, bool uv_limits, bool force_round_texcoords, bool true_color, bool dithering,
bool scaled_dithering, bool disable_color_perspective, bool interlacing, bool scaled_interlacing, bool check_mask,
bool write_mask_as_depth, bool use_rov, bool use_rov_depth, bool rov_depth_test, bool rov_depth_write) const
bool per_sample_shading, bool uv_limits, bool force_round_texcoords, bool modulation_crop, bool true_color,
bool dithering, bool scaled_dithering, bool disable_color_perspective, bool interlacing, bool scaled_interlacing,
bool check_mask, bool write_mask_as_depth, bool use_rov, bool use_rov_depth, bool rov_depth_test,
bool rov_depth_write) const
{
DebugAssert(!true_color || !dithering); // Should not be doing dithering+true color.
@ -1216,6 +1219,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
DefineMacro(ss, "DITHERING_SCALED", dithering && scaled_dithering);
DefineMacro(ss, "INTERLACING", interlacing);
DefineMacro(ss, "INTERLACING_SCALED", interlacing && scaled_interlacing);
DefineMacro(ss, "MODULATION_CROP", modulation_crop);
DefineMacro(ss, "TRUE_COLOR", true_color);
DefineMacro(ss, "TEXTURE_FILTERING", texture_filtering != GPUTextureFilter::Nearest);
DefineMacro(ss, "TEXTURE_ALPHA_BLENDING", is_blended_texture_filtering);
@ -1498,7 +1502,11 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, DECLARE_UV_LIMITS(float2 coords, fl
// If not using true color, truncate the framebuffer colors to 5-bit.
#if !TRUE_COLOR
icolor = uint3(texcol.rgb * float3(255.0, 255.0, 255.0)) >> 3;
icolor = (icolor * vertcol) >> 4;
#if MODULATION_CROP
icolor = (icolor * (vertcol >> 3)) >> 1;
#else
icolor = (icolor * vertcol) >> 4;
#endif
#if DITHERING
icolor = ApplyDithering(fragpos, icolor);
#else
@ -1506,7 +1514,11 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, DECLARE_UV_LIMITS(float2 coords, fl
#endif
#else
icolor = uint3(texcol.rgb * float3(255.0, 255.0, 255.0));
icolor = (icolor * vertcol) >> 7;
#if MODULATION_CROP
icolor = (icolor * (vertcol >> 3)) >> 4;
#else
icolor = (icolor * vertcol) >> 7;
#endif
icolor = min(icolor, uint3(255u, 255u, 255u));
#endif

View File

@ -22,7 +22,7 @@ public:
GPU_HW::BatchTextureMode texture_mode, GPUTextureFilter texture_filtering,
bool is_blended_texture_filtering, bool upscaled, bool msaa,
bool per_sample_shading, bool uv_limits, bool force_round_texcoords,
bool true_color, bool dithering, bool scaled_dithering,
bool modulation_crop, bool true_color, bool dithering, bool scaled_dithering,
bool disable_color_perspective, bool interlacing, bool scaled_interlacing,
bool check_mask, bool write_mask_as_depth, bool use_rov, bool use_rov_depth,
bool rov_depth_test, bool rov_depth_write) const;

View File

@ -107,7 +107,10 @@ void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32
void GPU_SW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
cmd->shading_enable,
GPU_SW_Rasterizer::GetModulationMode(cmd->texture_enable, cmd->raw_texture_enable,
g_gpu_settings.gpu_modulation_crop),
cmd->transparency_enable);
DrawFunction(cmd, &cmd->vertices[0], &cmd->vertices[1], &cmd->vertices[2]);
if (cmd->num_vertices > 3)
@ -117,7 +120,10 @@ void GPU_SW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
void GPU_SW::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd)
{
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
cmd->shading_enable,
GPU_SW_Rasterizer::GetModulationMode(cmd->texture_enable, cmd->raw_texture_enable,
g_gpu_settings.gpu_modulation_crop),
cmd->transparency_enable);
// Need to cut out the irrelevant bits.
// TODO: In _theory_ we could use the fixed-point parts here.
@ -148,8 +154,10 @@ void GPU_SW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
return;
}
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawRectangleFunction(cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawRectangleFunction(
GPU_SW_Rasterizer::GetModulationMode(cmd->texture_enable, cmd->raw_texture_enable,
g_gpu_settings.gpu_modulation_crop),
cmd->transparency_enable);
DrawFunction(cmd);
}

View File

@ -14,6 +14,14 @@
namespace GPU_SW_Rasterizer {
enum class TextureModulationMode : u8
{
Disabled, // No texturing
NoModulation, // "Raw Texture"
Modulate8Bit, // Modulate with 8-bit color
Modulate5Bit, // Modulate with 5-bit color
};
// this is actually (31 * 255) >> 4) == 494, but to simplify addressing we use the next power of two (512)
inline constexpr u32 DITHER_LUT_SIZE = 512;
using DitherLUT = std::array<std::array<std::array<u8, DITHER_LUT_SIZE>, DITHER_MATRIX_SIZE>, DITHER_MATRIX_SIZE>;
@ -25,12 +33,12 @@ extern GPUDrawingArea g_drawing_area;
extern void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit);
using DrawRectangleFunction = void (*)(const GPUBackendDrawRectangleCommand* cmd);
typedef const DrawRectangleFunction DrawRectangleFunctionTable[2][2][2];
typedef const DrawRectangleFunction DrawRectangleFunctionTable[4][2];
using DrawTriangleFunction = void (*)(const GPUBackendDrawCommand* cmd, const GPUBackendDrawPolygonCommand::Vertex* v0,
const GPUBackendDrawPolygonCommand::Vertex* v1,
const GPUBackendDrawPolygonCommand::Vertex* v2);
typedef const DrawTriangleFunction DrawTriangleFunctionTable[2][2][2][2];
typedef const DrawTriangleFunction DrawTriangleFunctionTable[2][4][2];
using DrawLineFunction = void (*)(const GPUBackendDrawCommand* cmd, const GPUBackendDrawLineCommand::Vertex* p0,
const GPUBackendDrawLineCommand::Vertex* p1);
@ -52,22 +60,30 @@ extern CopyVRAMFunction CopyVRAM;
extern void SelectImplementation();
ALWAYS_INLINE TextureModulationMode GetModulationMode(bool texture_enable, bool raw_texture_enable,
bool modulation_crop)
{
return (texture_enable ? (raw_texture_enable ? TextureModulationMode::NoModulation :
(modulation_crop ? TextureModulationMode::Modulate5Bit :
TextureModulationMode::Modulate8Bit)) :
TextureModulationMode::Disabled);
}
ALWAYS_INLINE DrawLineFunction GetDrawLineFunction(bool shading_enable, bool transparency_enable)
{
return (*DrawLineFunctions)[u8(shading_enable)][u8(transparency_enable)];
}
ALWAYS_INLINE DrawRectangleFunction GetDrawRectangleFunction(bool texture_enable, bool raw_texture_enable,
ALWAYS_INLINE DrawRectangleFunction GetDrawRectangleFunction(TextureModulationMode modulation_mode,
bool transparency_enable)
{
return (*DrawRectangleFunctions)[u8(texture_enable)][u8(raw_texture_enable)][u8(transparency_enable)];
return (*DrawRectangleFunctions)[u8(modulation_mode)][u8(transparency_enable)];
}
ALWAYS_INLINE DrawTriangleFunction GetDrawTriangleFunction(bool shading_enable, bool texture_enable,
bool raw_texture_enable, bool transparency_enable)
ALWAYS_INLINE DrawTriangleFunction GetDrawTriangleFunction(bool shading_enable, TextureModulationMode modulation_mode,
bool transparency_enable)
{
return (
*DrawTriangleFunctions)[u8(shading_enable)][u8(texture_enable)][u8(raw_texture_enable)][u8(transparency_enable)];
return (*DrawTriangleFunctions)[u8(shading_enable)][u8(modulation_mode)][u8(transparency_enable)];
}
#define DECLARE_ALTERNATIVE_RASTERIZER(isa) \

View File

@ -77,11 +77,13 @@ static u32 s_bad_counter = 0;
return std::make_tuple(static_cast<u8>(rgb24), static_cast<u8>(rgb24 >> 8), static_cast<u8>(rgb24 >> 16));
}
template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<TextureModulationMode modulation_mode, bool transparency_enable>
[[maybe_unused]] ALWAYS_INLINE_RELEASE static void ShadePixel(const GPUBackendDrawCommand* cmd, u32 x, u32 y,
u8 color_r, u8 color_g, u8 color_b, u8 texcoord_x,
u8 texcoord_y)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
u16 color;
if constexpr (texture_enable)
{
@ -123,7 +125,7 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
if (texture_color == 0)
return;
if constexpr (raw_texture_enable)
if constexpr (modulation_mode == TextureModulationMode::NoModulation)
{
color = texture_color;
}
@ -133,12 +135,26 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
color =
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16(texture_color & 0x1Fu) * u16(color_r)) >> 4]) << 0) |
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16((texture_color >> 5) & 0x1Fu) * u16(color_g)) >> 4]) << 5) |
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16((texture_color >> 10) & 0x1Fu) * u16(color_b)) >> 4])
<< 10) |
(texture_color & 0x8000u);
if (modulation_mode == TextureModulationMode::Modulate8Bit)
{
color =
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16(texture_color & 0x1Fu) * u16(color_r)) >> 4]) << 0) |
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16((texture_color >> 5) & 0x1Fu) * u16(color_g)) >> 4])
<< 5) |
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16((texture_color >> 10) & 0x1Fu) * u16(color_b)) >> 4])
<< 10) |
(texture_color & 0x8000u);
}
else
{
color =
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16(texture_color & 0x1Fu) * u16(color_r >> 3)) >> 1]) << 0) |
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16((texture_color >> 5) & 0x1Fu) * u16(color_g >> 3)) >> 1])
<< 5) |
(ZeroExtend16(g_dither_lut[dither_y][dither_x][(u16((texture_color >> 10) & 0x1Fu) * u16(color_b >> 3)) >> 1])
<< 10) |
(texture_color & 0x8000u);
}
}
}
else
@ -225,7 +241,7 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
#ifndef USE_VECTOR
template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<TextureModulationMode modulation_mode, bool transparency_enable>
static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
{
const s32 origin_x = cmd->x;
@ -254,8 +270,8 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
const u8 texcoord_x = Truncate8(ZeroExtend32(origin_texcoord_x) + offset_x);
ShadePixel<texture_enable, raw_texture_enable, transparency_enable>(cmd, static_cast<u32>(x), draw_y, r, g, b,
texcoord_x, texcoord_y);
ShadePixel<modulation_mode, transparency_enable>(cmd, static_cast<u32>(x), draw_y, r, g, b, texcoord_x,
texcoord_y);
}
}
}
@ -505,13 +521,15 @@ struct PixelVectors
};
} // namespace
template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void ShadePixel(const PixelVectors<texture_enable>& RESTRICT pv,
GPUTextureMode texture_mode, GPUTransparencyMode transparency_mode,
bool mask_bit_test, u32 start_x, u32 y, GSVectorNi vertex_color_rg,
GSVectorNi vertex_color_ba, GSVectorNi texcoord_x, GSVectorNi texcoord_y,
GSVectorNi preserve_mask, GSVectorNi dither)
template<TextureModulationMode modulation_mode, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void
ShadePixel(const PixelVectors<modulation_mode != TextureModulationMode::Disabled>& RESTRICT pv,
GPUTextureMode texture_mode, GPUTransparencyMode transparency_mode, bool mask_bit_test, u32 start_x, u32 y,
GSVectorNi vertex_color_rg, GSVectorNi vertex_color_ba, GSVectorNi texcoord_x, GSVectorNi texcoord_y,
GSVectorNi preserve_mask, GSVectorNi dither)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
static constexpr GSVectorNi coord_mask_x = GSVectorNi::cxpr(VRAM_WIDTH_MASK);
static constexpr GSVectorNi coord_mask_y = GSVectorNi::cxpr(VRAM_HEIGHT_MASK);
@ -568,7 +586,7 @@ ALWAYS_INLINE_RELEASE static void ShadePixel(const PixelVectors<texture_enable>&
preserve_mask = preserve_mask | texture_transparent_mask;
if constexpr (raw_texture_enable)
if constexpr (modulation_mode == TextureModulationMode::NoModulation)
{
color = texture_color;
}
@ -577,13 +595,27 @@ ALWAYS_INLINE_RELEASE static void ShadePixel(const PixelVectors<texture_enable>&
GSVectorNi trg, tba;
RGB5A1ToRG_BA(texture_color, trg, tba);
// now we have both the texture and vertex color in RG/GA pairs, for 4 pixels, which we can multiply
GSVectorNi rg = trg.mul16l(vertex_color_rg);
GSVectorNi ba = tba.mul16l(vertex_color_ba);
GSVectorNi rg, ba;
if constexpr (modulation_mode == TextureModulationMode::Modulate5Bit)
{
// now we have both the texture and vertex color in RG/GA pairs, for 4 pixels, which we can multiply
rg = trg.mul16l(vertex_color_rg);
ba = tba.mul16l(vertex_color_ba);
// Convert to 5bit.
rg = rg.sra16<4>().add16(dither).max_s16(GSVectorNi::zero()).sra16<3>();
ba = ba.sra16<4>().add16(dither).max_s16(GSVectorNi::zero()).sra16<3>();
// Convert to 5bit.
rg = rg.sra16<4>().add16(dither).max_s16(GSVectorNi::zero()).sra16<3>();
ba = ba.sra16<4>().add16(dither).max_s16(GSVectorNi::zero()).sra16<3>();
}
else
{
// now we have both the texture and vertex color in RG/GA pairs, for 4 pixels, which we can multiply
rg = trg.mul16l(vertex_color_rg.sra16<3>());
ba = tba.mul16l(vertex_color_ba.sra16<3>());
// Convert to 5bit.
rg = rg.sra16<1>().add16(dither).max_s16(GSVectorNi::zero()).sra16<3>();
ba = ba.sra16<1>().add16(dither).max_s16(GSVectorNi::zero()).sra16<3>();
}
// Bit15 gets passed through as-is.
ba = ba.blend16<0xaa>(tba);
@ -703,9 +735,11 @@ ALWAYS_INLINE_RELEASE static void ShadePixel(const PixelVectors<texture_enable>&
StoreVector(start_x, y, color);
}
template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<TextureModulationMode modulation_mode, bool transparency_enable>
static void DrawRectangle(const GPUBackendDrawRectangleCommand* RESTRICT cmd)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
const s32 origin_x = cmd->x;
const s32 origin_y = cmd->y;
@ -752,9 +786,9 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* RESTRICT cmd)
preserve_mask = preserve_mask | xvec.gt32(pv.clip_right);
if (!preserve_mask.alltrue())
{
ShadePixel<texture_enable, raw_texture_enable, transparency_enable>(
pv, cmd->draw_mode.texture_mode, transparency_mode, mask_bit_test, x, draw_y, rg, ba, row_texcoord_x,
texcoord_y, preserve_mask, GSVectorNi::zero());
ShadePixel<modulation_mode, transparency_enable>(pv, cmd->draw_mode.texture_mode, transparency_mode,
mask_bit_test, x, draw_y, rg, ba, row_texcoord_x, texcoord_y,
preserve_mask, GSVectorNi::zero());
}
xvec = xvec.add32(PIXELS_PER_VEC_VEC);
@ -770,7 +804,7 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* RESTRICT cmd)
}
#ifdef CHECK_VECTOR
CHECK_VRAM(GPU_SW_Rasterizer::DrawRectangleFunctions[texture_enable][raw_texture_enable][transparency_enable](cmd));
CHECK_VRAM(GPU_SW_Rasterizer::DrawRectangleFunctions[u8(modulation_mode)][transparency_enable](cmd));
#endif
}
@ -841,8 +875,8 @@ static void DrawLine(const GPUBackendDrawCommand* RESTRICT cmd, const GPUBackend
const u8 g = shading_enable ? unfp_rgb(curg) : p0->g;
const u8 b = shading_enable ? unfp_rgb(curb) : p0->b;
ShadePixel<false, false, transparency_enable>(cmd, static_cast<u32>(x), static_cast<u32>(y) & VRAM_HEIGHT_MASK, r,
g, b, 0, 0);
ShadePixel<TextureModulationMode::Disabled, transparency_enable>(
cmd, static_cast<u32>(x), static_cast<u32>(y) & VRAM_HEIGHT_MASK, r, g, b, 0, 0);
}
curx += dxdk;
@ -983,10 +1017,12 @@ struct TrianglePart
#ifndef USE_VECTOR
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<bool shading_enable, TextureModulationMode modulation_mode, bool transparency_enable>
static void DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
const UVSteps& RESTRICT uvstep, RGBStepper rgb, const RGBSteps& RESTRICT rgbstep)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
s32 width = x_bound - x_start;
s32 current_x = TruncateGPUVertexPosition(x_start);
@ -1012,8 +1048,8 @@ static void DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_sta
do
{
ShadePixel<texture_enable, raw_texture_enable, transparency_enable>(
cmd, static_cast<u32>(current_x), static_cast<u32>(y), rgb.GetR(), rgb.GetG(), rgb.GetB(), uv.GetU(), uv.GetV());
ShadePixel<modulation_mode, transparency_enable>(cmd, static_cast<u32>(current_x), static_cast<u32>(y), rgb.GetR(),
rgb.GetG(), rgb.GetB(), uv.GetU(), uv.GetV());
current_x++;
if constexpr (texture_enable)
@ -1023,12 +1059,13 @@ static void DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_sta
} while (--width > 0);
}
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<bool shading_enable, TextureModulationMode modulation_mode, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand* RESTRICT cmd,
const TrianglePart& RESTRICT tp, const UVStepper& RESTRICT uv,
const UVSteps& RESTRICT uvstep, const RGBStepper& RESTRICT rgb,
const RGBSteps& RESTRICT rgbstep)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
static constexpr auto unfp_xy = [](s64 xfp) -> s32 { return static_cast<s32>(static_cast<u64>(xfp) >> 32); };
const u64 left_x_step = tp.step_x[0];
@ -1074,8 +1111,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand*
continue;
}
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
DrawSpan<shading_enable, modulation_mode, transparency_enable>(cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x),
unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
} while (current_y > end_y);
}
else
@ -1103,8 +1140,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand*
(!cmd->interlaced_rendering ||
cmd->active_line_lsb != ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
{
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
DrawSpan<shading_enable, modulation_mode, transparency_enable>(cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x),
unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
}
current_y++;
@ -1163,12 +1200,14 @@ struct TriangleVectors : PixelVectors<texture_enable>
};
} // namespace
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_start, s32 x_bound,
UVStepper uv, const UVSteps& RESTRICT uvstep, RGBStepper rgb,
const RGBSteps& RESTRICT rgbstep,
const TriangleVectors<shading_enable, texture_enable>& RESTRICT tv)
template<bool shading_enable, TextureModulationMode modulation_mode, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void
DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
const UVSteps& RESTRICT uvstep, RGBStepper rgb, const RGBSteps& RESTRICT rgbstep,
const TriangleVectors<shading_enable, modulation_mode != TextureModulationMode::Disabled>& RESTRICT tv)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
s32 width = x_bound - x_start;
s32 current_x = TruncateGPUVertexPosition(x_start);
@ -1247,9 +1286,9 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawCommand* RESTRICT
preserve_mask = preserve_mask | xvec.gt32(tv.clip_right);
if (!preserve_mask.alltrue())
{
ShadePixel<texture_enable, raw_texture_enable, transparency_enable>(
tv, cmd->draw_mode.texture_mode, transparency_mode, mask_bit_test, static_cast<u32>(current_x),
static_cast<u32>(y), rg, b, u, v, preserve_mask, dither);
ShadePixel<modulation_mode, transparency_enable>(tv, cmd->draw_mode.texture_mode, transparency_mode,
mask_bit_test, static_cast<u32>(current_x), static_cast<u32>(y),
rg, b, u, v, preserve_mask, dither);
}
current_x += PIXELS_PER_VEC;
@ -1272,12 +1311,13 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawCommand* RESTRICT
}
}
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<bool shading_enable, TextureModulationMode modulation_mode, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand* RESTRICT cmd,
const TrianglePart& RESTRICT tp, const UVStepper& RESTRICT uv,
const UVSteps& RESTRICT uvstep, const RGBStepper& RESTRICT rgb,
const RGBSteps& RESTRICT rgbstep)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
static constexpr auto unfp_xy = [](s64 xfp) -> s32 { return static_cast<s32>(static_cast<u64>(xfp) >> 32); };
const u64 left_x_step = tp.step_x[0];
@ -1325,8 +1365,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand*
continue;
}
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv);
DrawSpan<shading_enable, modulation_mode, transparency_enable>(cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x),
unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv);
} while (current_y > end_y);
}
else
@ -1356,7 +1396,7 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand*
(!cmd->interlaced_rendering ||
cmd->active_line_lsb != ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
{
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
DrawSpan<shading_enable, modulation_mode, transparency_enable>(
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv);
}
@ -1374,12 +1414,14 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand*
#endif // USE_VECTOR
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
template<bool shading_enable, TextureModulationMode modulation_mode, bool transparency_enable>
static void DrawTriangle(const GPUBackendDrawCommand* RESTRICT cmd,
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT v0,
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT v1,
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT v2)
{
static constexpr bool texture_enable = (modulation_mode != TextureModulationMode::Disabled);
#ifdef CHECK_VECTOR
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT orig_v0 = v0;
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT orig_v1 = v1;
@ -1514,35 +1556,85 @@ static void DrawTriangle(const GPUBackendDrawCommand* RESTRICT cmd,
for (u32 i = 0; i < 2; i++)
{
DrawTrianglePart<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(cmd, triparts[i], uv,
uvstep, rgb, rgbstep);
DrawTrianglePart<shading_enable, modulation_mode, transparency_enable>(cmd, triparts[i], uv, uvstep, rgb, rgbstep);
}
#ifdef CHECK_VECTOR
CHECK_VRAM(
GPU_SW_Rasterizer::DrawTriangleFunctions[shading_enable][texture_enable][raw_texture_enable][transparency_enable](
cmd, orig_v0, orig_v1, orig_v2));
CHECK_VRAM(GPU_SW_Rasterizer::DrawTriangleFunctions[shading_enable][u8(modulation_mode)][transparency_enable](
cmd, orig_v0, orig_v1, orig_v2));
#endif
}
// clang-format off
constinit const DrawRectangleFunctionTable DrawRectangleFunctions = {
{{&DrawRectangle<false, false, false>, &DrawRectangle<false, false, true>},
{&DrawRectangle<false, false, false>, &DrawRectangle<false, false, true>}},
{{&DrawRectangle<true, false, false>, &DrawRectangle<true, false, true>},
{&DrawRectangle<true, true, false>, &DrawRectangle<true, true, true>}}};
{
&DrawRectangle<TextureModulationMode::Disabled, false>,
&DrawRectangle<TextureModulationMode::Disabled, true>,
},
{
&DrawRectangle<TextureModulationMode::NoModulation, false>,
&DrawRectangle<TextureModulationMode::NoModulation, true>,
},
{
&DrawRectangle<TextureModulationMode::Modulate8Bit, false>,
&DrawRectangle<TextureModulationMode::Modulate8Bit, true>,
},
{
&DrawRectangle<TextureModulationMode::Modulate5Bit, false>,
&DrawRectangle<TextureModulationMode::Modulate5Bit, true>,
},
};
constinit const DrawLineFunctionTable DrawLineFunctions = {{&DrawLine<false, false>, &DrawLine<false, true>},
{&DrawLine<true, false>, &DrawLine<true, true>}};
constinit const DrawLineFunctionTable DrawLineFunctions = {
{
&DrawLine<false, false>,
&DrawLine<false, true>
},
{
&DrawLine<true, false>,
&DrawLine<true, true>
}
};
constinit const DrawTriangleFunctionTable DrawTriangleFunctions = {
{{{&DrawTriangle<false, false, false, false>, &DrawTriangle<false, false, false, true>},
{&DrawTriangle<false, false, false, false>, &DrawTriangle<false, false, false, true>}},
{{&DrawTriangle<false, true, false, false>, &DrawTriangle<false, true, false, true>},
{&DrawTriangle<false, true, true, false>, &DrawTriangle<false, true, true, true>}}},
{{{&DrawTriangle<true, false, false, false>, &DrawTriangle<true, false, false, true>},
{&DrawTriangle<true, false, false, false>, &DrawTriangle<true, false, false, true>}},
{{&DrawTriangle<true, true, false, false>, &DrawTriangle<true, true, false, true>},
{&DrawTriangle<true, true, true, false>, &DrawTriangle<true, true, true, true>}}}};
{
{
&DrawTriangle<false, TextureModulationMode::Disabled, false>,
&DrawTriangle<false, TextureModulationMode::Disabled, true>
},
{
&DrawTriangle<false, TextureModulationMode::NoModulation, false>,
&DrawTriangle<false, TextureModulationMode::NoModulation, true>
},
{
&DrawTriangle<false, TextureModulationMode::Modulate8Bit, false>,
&DrawTriangle<false, TextureModulationMode::Modulate8Bit, true>
},
{
&DrawTriangle<false, TextureModulationMode::Modulate5Bit, false>,
&DrawTriangle<false, TextureModulationMode::Modulate5Bit, true>
}
},
{
{
&DrawTriangle<true, TextureModulationMode::Disabled, false>,
&DrawTriangle<true, TextureModulationMode::Disabled, true>
},
{
&DrawTriangle<true, TextureModulationMode::NoModulation, false>,
&DrawTriangle<true, TextureModulationMode::NoModulation, true>
},
{
&DrawTriangle<true, TextureModulationMode::Modulate8Bit, false>,
&DrawTriangle<true, TextureModulationMode::Modulate8Bit, true>
},
{
&DrawTriangle<true, TextureModulationMode::Modulate5Bit, false>,
&DrawTriangle<true, TextureModulationMode::Modulate5Bit, true>
}
}
};
// clang-format on
static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced, u8 active_line_lsb)
{

View File

@ -301,6 +301,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
si.GetStringValue("GPU", "ForceVideoTiming", GetForceVideoTimingName(DEFAULT_FORCE_VIDEO_TIMING_MODE)).c_str())
.value_or(DEFAULT_FORCE_VIDEO_TIMING_MODE);
gpu_widescreen_rendering = gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false);
gpu_modulation_crop = si.GetBoolValue("GPU", "EnableModulationCrop", false);
gpu_texture_cache = si.GetBoolValue("GPU", "EnableTextureCache", false);
display_24bit_chroma_smoothing = si.GetBoolValue("GPU", "ChromaSmoothing24Bit", false);
gpu_pgxp_enable = si.GetBoolValue("GPU", "PGXPEnable", false);
@ -680,6 +681,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(gpu_wireframe_mode));
si.SetStringValue("GPU", "ForceVideoTiming", GetForceVideoTimingName(gpu_force_video_timing));
si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_rendering);
si.SetBoolValue("GPU", "EnableModulationCrop", gpu_modulation_crop);
si.SetBoolValue("GPU", "EnableTextureCache", gpu_texture_cache);
si.SetBoolValue("GPU", "ChromaSmoothing24Bit", display_24bit_chroma_smoothing);
si.SetBoolValue("GPU", "PGXPEnable", gpu_pgxp_enable);
@ -1102,6 +1104,7 @@ void Settings::ApplySettingRestrictions()
gpu_force_video_timing = ForceVideoTimingMode::Disabled;
gpu_widescreen_rendering = false;
gpu_widescreen_hack = false;
gpu_modulation_crop = false;
gpu_texture_cache = false;
gpu_pgxp_enable = false;
display_deinterlacing_mode = DisplayDeinterlacingMode::Adaptive;

View File

@ -80,6 +80,7 @@ struct GPUSettings
bool gpu_force_round_texcoords : 1 = false;
bool gpu_widescreen_rendering : 1 = false;
bool gpu_widescreen_hack : 1 = false;
bool gpu_modulation_crop : 1 = false;
bool gpu_texture_cache : 1 = false;
bool gpu_show_vram : 1 = false;
bool gpu_dump_cpu_to_vram_copies : 1 = false;

View File

@ -5,4 +5,4 @@
#include "common/types.h"
inline constexpr u32 SHADER_CACHE_VERSION = 36;
inline constexpr u32 SHADER_CACHE_VERSION = 37;

View File

@ -4600,6 +4600,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
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.gpu_modulation_crop != old_settings.gpu_modulation_crop ||
g_settings.gpu_texture_cache != old_settings.gpu_texture_cache ||
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode ||
g_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing ||
@ -4959,6 +4960,8 @@ void System::WarnAboutUnsafeSettings()
append(TRANSLATE_SV("System", "Widescreen rendering disabled."));
if (g_settings.gpu_pgxp_enable)
append(TRANSLATE_SV("System", "PGXP disabled."));
if (g_settings.gpu_modulation_crop)
append(TRANSLATE_SV("System", "Texture modulation cropping disabled."));
if (g_settings.gpu_texture_cache)
append(TRANSLATE_SV("System", "GPU texture cache disabled."));
if (g_settings.display_24bit_chroma_smoothing)

View File

@ -171,6 +171,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.maxQueuedFrames, "GPU", "MaxQueuedFrames",
Settings::DEFAULT_GPU_MAX_QUEUED_FRAMES);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.modulationCrop, "GPU", "EnableModulationCrop", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.scaledInterlacing, "GPU", "ScaledInterlacing", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useSoftwareRendererForReadbacks, "GPU",
"UseSoftwareRendererForReadbacks", false);
@ -527,6 +528,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
tr("Uses a second thread for drawing graphics. Provides a significant speed improvement "
"particularly with the software renderer, and is safe to use."));
dialog->registerWidgetHelp(
m_ui.modulationCrop, tr("Texture Modulation Cropping (\"Old/v0\" GPU)"), tr("Unchecked"),
tr("Crops vertex colours to 5:5:5 before modulating with the texture colour, which typically results in more "
"visible banding. This is a characteristic of the \"old\" GPUs found in early model consoles."));
dialog->registerWidgetHelp(m_ui.scaledInterlacing, tr("Scaled Interlacing"), tr("Checked"),
tr("Scales line skipping in interlaced rendering to the internal resolution. This makes "
"the combing less obvious at higher resolutions. Usually safe to enable."));

View File

@ -554,9 +554,9 @@
</layout>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="useSoftwareRendererForReadbacks">
<widget class="QCheckBox" name="modulationCrop">
<property name="text">
<string>Software Renderer Readbacks</string>
<string>Texture Modulation Cropping (&quot;Old/v0 GPU&quot;)</string>
</property>
</widget>
</item>
@ -567,6 +567,13 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="useSoftwareRendererForReadbacks">
<property name="text">
<string>Software Renderer Readbacks</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>