Compare commits

...
2 Commits
Author SHA1 Message Date
TJnotJT 77bec5e0f5 GS/HW: Rewrite vertices if large ST.
Adds vertex rewriting functionality to both HW/SW. Aggressive version is user hack. Adds a GameDB hack for Ridge Racer V and Destruction Derby Arena. For SW, a less aggressive version is used if the hack is not enabled.
2026-09-04 12:05:30 +02:00
refractionpcsx2 f0cd834ed2 GS/HW: Improve source channel requirements for source lookups 2026-09-04 11:44:35 +02:00
16 changed files with 235 additions and 142 deletions
+5
View File
@@ -4706,6 +4706,7 @@ SCES-50000:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SCES-50001:
name: "Tekken Tag Tournament"
region: "PAL-M5"
@@ -35387,6 +35388,7 @@ SLPM-60109:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLPM-60112:
name: "フレースヴェルグ [体験版]"
name-sort: "ふれーすヴぇるぐ [たいけんばん]"
@@ -54809,6 +54811,7 @@ SLPS-20001:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLPS-20002:
name: "撞球 ビリヤードマスター2"
name-sort: "どうきゅう びりやーどますたー2"
@@ -63501,6 +63504,7 @@ SLPS-71502:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLPS-72501:
name: "ファイナルファンタジーⅩ [MEGA HITS!]"
name-sort: "ふぁいなるふぁんたじー10 [MEGA HITS!]"
@@ -64503,6 +64507,7 @@ SLUS-20002:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLUS-20003:
name: "Portal Runner"
region: "NTSC-U"
@@ -271,6 +271,13 @@
</item>
<item row="7" column="0" colspan="2">
<layout class="QGridLayout" name="hwFixesLayout">
<item row="5" column="0">
<widget class="QCheckBox" name="rewriteLargeST">
<property name="text">
<string>Rewrite Large ST</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="drawBuffering">
<property name="text">
@@ -389,6 +396,7 @@
<tabstop>disableRenderFixes</tabstop>
<tabstop>readTCOnClose</tabstop>
<tabstop>estimateTextureRegion</tabstop>
<tabstop>rewriteLargeST</tabstop>
</tabstops>
<resources/>
<connections/>
@@ -155,6 +155,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.readTCOnClose, "EmuCore/GS", "UserHacks_ReadTCOnClose", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.estimateTextureRegion, "EmuCore/GS", "UserHacks_EstimateTextureRegion", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.drawBuffering, "EmuCore/GS", "UserHacks_DrawBuffering", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.rewriteLargeST, "EmuCore/GS", "UserHacks_RewriteLargeST", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.gpuPaletteConversion, "EmuCore/GS", "paltex", false);
connect(m_fixes.cpuSpriteRenderBW, &QComboBox::currentIndexChanged, this,
&GraphicsSettingsWidget::onCPUSpriteRenderBWChanged);
@@ -636,6 +637,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
dialog()->registerWidgetHelp(m_fixes.drawBuffering, tr("Draw Buffering"), tr("Unchecked"),
tr("Attempts to reduce draw calls in games which do heavy context switching for blending purposes."));
dialog()->registerWidgetHelp(m_fixes.rewriteLargeST, tr("Rewrite Large ST"), tr("Unchecked"),
tr("Rewrite large ST coordinates and clamp the values (mainly for Ridge Racer V)."));
}
// Upscaling Fixes tab
+1
View File
@@ -811,6 +811,7 @@ struct Pcsx2Config
UserHacks_NativePaletteDraw : 1,
UserHacks_EstimateTextureRegion : 1,
UserHacks_DrawBuffering : 1,
UserHacks_RewriteLargeST : 1,
FXAA : 1,
ShadeBoost : 1,
DumpGSData : 1,
+2 -1
View File
@@ -174,7 +174,8 @@ The clamp modes are also numerically based.
* cpuSpriteRenderBW [Value between `0` to `10`] {Disabled, 1 (64), 2 (128), 3 (192), 4 (256), 5 (320), 6 (384), 7 (448), 8 (512), 9 (576), 10 (640)} Default: Off (`0`)
* cpuSpriteRenderLevel [`0` or `1` or `2`] {Sprites only, Sprites/Triangles, Blended Sprites/Triangles} Default: Off unless cpuSpriteRenderBW has value other than Off then it is 'Sprites only' (`0`)
* estimateTextureRegion [`0` or `1`] {Off, On} Default: Off (`0`)
* drawBuffering [`0` or `1`] {Off, On} Default: Off (`0`)
* drawBuffering [`0` or `1`] {Off, On} Default: Off (`0`)
* rewriteLargeST [`0` or `1`] {Off, On} Default: Off (`0`)
* getSkipCount {`GSC` with suffix } {None unless specific game GSC} Default: Disabled (`0`) unless valid variable name (ex. GSC_PolyphonyDigitalGames, GSC_UrbanReign, ...)
* gpuPaletteConversion [`0` or `1`] {Off, On} Default: Off (`0`)
* gpuTargetCLUT [`0` or `1` or `2`] {Disabled, Enabled (Exact Match), Enabled (Check Inside Target)} Default: Disabled (`0`)
+6 -1
View File
@@ -204,7 +204,12 @@
"minimum": 0,
"maximum": 1
},
"drawBuffering": {
"drawBuffering": {
"type": "integer",
"minimum": 0,
"maximum": 1
},
"rewriteLargeST": {
"type": "integer",
"minimum": 0,
"maximum": 1
+153
View File
@@ -6838,6 +6838,159 @@ GIFRegTEX0 GSState::GetTex0Layer(u32 lod)
return TEX0;
}
// Fix large ST coorindates that may cause graphical glitches.
template <u32 primclass>
void GSState::RewriteVerticesIfLargeSTImpl(const GSVector4& large_val, bool check_clamp_mode)
{
// Need to be texture mapping with ST and not have so many vertices that rewritten indices will overflow.
if (PRIM->TME && PRIM->FST == 0)
{
const float tw = static_cast<float>(1 << m_context->TEX0.TW);
const float th = static_cast<float>(1 << m_context->TEX0.TH);
const GSVector4 tsize = GSVector4(tw, th, 1.0f, 1.0f);
// Only rewrite big/small S or T when the clamping mode is CLAMP or REGION_CLAMP (if requested).
const GIFRegCLAMP& clamp = m_context->CLAMP;
const GSVector4 clamp_mask =
check_clamp_mode ?
GSVector4::cast(GSVector4i(
(clamp.WMS == CLAMP_CLAMP || clamp.WMS == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
(clamp.WMT == CLAMP_CLAMP || clamp.WMT == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
0,
0)) :
GSVector4::cast(GSVector4i::cxpr(0xFFFFFFFF));
const GSVector4 tex_bbox = m_vt.m_min.t.xyxy(m_vt.m_max.t) / tsize.xyxy();
const bool large_st = ((tex_bbox.abs() >= large_val) & clamp_mask).mask() || m_vt.nan.value;
if (large_st)
{
GL_PUSH("Large ST detected, rewriting vertices.");
if (m_index->tail > UINT16_MAX)
{
GL_INS("Too many vertices to rewrite safely, exiting.");
DevCon.Warning("Large ST detected but too many vertices to rewrite safely.");
return;
}
constexpr int n = GSUtil::GetClassVertexCount(primclass);
// Make sure the copy buffer is large enough.
while (m_max_vertex_count < m_index->tail)
GrowVertexBuffer();
GSVertex* RESTRICT vertex = m_vertex->buff;
GSVertex* RESTRICT vertex_copy = m_vertex->buff_copy;
u16* RESTRICT index = m_index->buff;
const int count = static_cast<int>(m_index->tail);
for (int i = 0; i < count; i += n)
{
GSVector4 stcq[n];
// Load STQ for this primitive.
for (int j = 0; j < n; j++)
stcq[j] = GSVector4::cast(GSVector4i(vertex[index[i + j]].m[0]));
// Perform Q division and see which values need to be rewritten.
GSVector4 uv[n];
GSVector4 small{}, big{}, nan{};
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = primclass == GS_SPRITE_CLASS ? stcq[1].wwww() : stcq[j].wwww();
uv[j] = (stcq[j] / q).xyzw(GSVector4::zero());
small |= (uv[j] <= -large_val);
big |= (uv[j] >= large_val);
nan |= (uv[j] != uv[j]);
}
// Get the new values for fields that will be rewritten.
// The follows rules are used:
// 1. If there are small values but not big or nans, make all vertices small.
// 2. If there are big values but not small or nans, make all vertices big.
// 3. If there are both big and small values, or nans, make all vertices zero.
GSVector4 uv_new = GSVector4::zero();
uv_new = uv_new.blend32(-large_val, small);
uv_new = uv_new.blend32(large_val, big);
uv_new = uv_new.blend32(GSVector4::zero(), (small & big) | nan);
const GSVector4 rewrite = (((small | big) & clamp_mask) | nan).xyxy(GSVector4::zero());
// If both S and T are rewritten, no point in keeping Q. Just set it to 1.0f;
if ((rewrite.mask() & 3) == 3)
{
for (int j = 0; j < n; j++)
stcq[j] = stcq[j].template insert32<0, 3>(GSVector4::m_one);
}
// Rewrite the fields that require it and write to the copy buffer.
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = (primclass == GS_SPRITE_CLASS) ? stcq[1].wwww() : stcq[j].wwww();
stcq[j] = stcq[j].blend32(uv_new * q, rewrite);
vertex_copy[i + j].m[0] = GSVector4i::cast(stcq[j]);
vertex_copy[i + j].m[1] = vertex[index[i + j]].m[1];
index[i + j] = i + j;
}
}
// Swap the buffers and fix the counts.
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
// Recalculate ST min/max/eq in the vertex trace.
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
for (int i = 0; i < count; i += n)
{
for (int j = 0; j < n; j++)
{
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex->buff[i + j].m[0]));
const float q = (primclass == GS_SPRITE_CLASS) ? m_vertex->buff[i + 1].RGBAQ.Q : stcq.w;
stcq = (stcq / q).xyzw(stcq);
tmin = tmin.min(stcq);
tmax = tmax.max(stcq);
}
}
m_vt.m_min.t = tmin.xyww() * tsize;
m_vt.m_max.t = tmax.xyww() * tsize;
m_vt.m_eq.stq = (m_vt.m_min.t == m_vt.m_max.t).mask();
// Dump vertices if needed.
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()) && GSConfig.SaveInfo)
{
DumpVertices(GetDrawDumpPath("%05d_vertices_st_rewrite.txt", s_n));
}
}
}
}
void GSState::RewriteVerticesIfLargeST(const GSVector4& large_val, bool check_clamp_mode)
{
switch (m_vt.m_primclass)
{
case GS_POINT_CLASS:
RewriteVerticesIfLargeSTImpl<GS_POINT_CLASS>(large_val, check_clamp_mode);
break;
case GS_LINE_CLASS:
RewriteVerticesIfLargeSTImpl<GS_LINE_CLASS>(large_val, check_clamp_mode);
break;
case GS_SPRITE_CLASS:
RewriteVerticesIfLargeSTImpl<GS_SPRITE_CLASS>(large_val, check_clamp_mode);
break;
case GS_TRIANGLE_CLASS:
RewriteVerticesIfLargeSTImpl<GS_TRIANGLE_CLASS>(large_val, check_clamp_mode);
break;
default:
pxFail("Invalid primclass"); // Impossible
break;
}
}
// GSTransferBuffer
GSState::GSTransferBuffer::GSTransferBuffer()
+3
View File
@@ -541,6 +541,9 @@ public:
bool SpriteDrawWithoutGaps();
void CalculatePrimitiveCoversWithoutGaps();
GIFRegTEX0 GetTex0Layer(u32 lod);
template <u32 primclass>
void RewriteVerticesIfLargeSTImpl(const GSVector4& large_val, bool check_clamp_mode);
void RewriteVerticesIfLargeST(const GSVector4& large_val, bool check_clamp_mode);
};
// We put this in the header because of Multi-ISA.
+14 -4
View File
@@ -2901,6 +2901,15 @@ void GSRendererHW::Draw()
// First pass texture shuffle detection using context/vertices.
DetectTextureShuffle();
// Ridge Racer V and Destruction Derby Arena have large ST coordinates in reflection map draws that cause
// speckled artifacts on cars. Detect and rewrite such vertices here.
if (GSConfig.UserHacks_RewriteLargeST)
{
// The threshold is chosen to be low enough to fix the artifacts in Ridge Racer V and Destruction Derby Arena
// while not breaking anything.
RewriteVerticesIfLargeST(GSVector4::cxpr(16.0f), false);
}
// When the format is 24bit (Z or C), DATE ceases to function.
// It was believed that in 24bit mode all pixels pass because alpha doesn't exist
@@ -3526,13 +3535,14 @@ void GSRendererHW::Draw()
const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask();
const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000));
const bool texture_function_alpha = m_cached_ctx.TEX0.TFX != TFX_MODULATE || (color_mask & 0xF000);
const bool req_color = (is_possible_channel_shuffle && channel_shuffle_targets != ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && ((texture_function_color && (!PRIM->ABE || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp < 16 || (NeedsBlending() && IsUsingCsInBlend())) && (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color));
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((NeedsBlending() && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_alpha = (is_possible_channel_shuffle && channel_shuffle_targets == ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used);
const bool color_used = (is_possible_channel_shuffle && channel_shuffle_targets != ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && ((texture_function_color && (!PRIM->ABE || (NeedsBlending() && IsUsingCsInBlend())) && ((m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color));
const bool alpha_used = (is_possible_channel_shuffle && channel_shuffle_targets == ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && (m_context->TEX0.TCC && texture_function_alpha) && ((NeedsBlending() && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_color = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x7) && color_used;
const bool req_alpha = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 && (color_used || alpha_used)) || ((GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used);
// TODO: Be able to send an alpha of 1.0 (blended with vertex alpha maybe?) so we can avoid sending the texture, since we don't always need it.
// Example games: Evolution Snowboarding, Final Fantasy Dirge of Cerberus, Red Dead Revolver, Stuntman, Tony Hawk's Underground 2, Ultimate Spider-Man.
if (!req_color && !alpha_used)
if (!req_color && !req_alpha)
{
m_process_texture = false;
possible_shuffle = false;
+18 -136
View File
@@ -287,125 +287,6 @@ void ConvertVertexBuffer(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTR
}
}
// Fix ST coordinates that would overflow the rasterizer fixed point format by rewriting the vertices.
template <u32 primclass>
void GSRendererSW::RewriteVerticesIfSTOverflow()
{
if (PRIM->TME && PRIM->FST == 0)
{
const GSVector4 tsize = GSVector4(
static_cast<float>(1 << m_context->TEX0.TW),
static_cast<float>(1 << m_context->TEX0.TH),
1.0f,
1.0f);
// SW rasterizer stores UV in 1.15.16 format so clamp to +/- (2^15 - 2) (-2 so bilinear doesn't overflow).
// Do the division by texture size here to avoid divisions for each vertex.
const GSVector4 OVERFLOW_VAL = GSVector4::cxpr(static_cast<float>((1 << 15) - 2)) / tsize;
// Only rewrite big/small S or T when the clamping mode is CLAMP or REGION_CLAMP.
const GSVector4i clamp_mode = GSVector4i(
(m_context->CLAMP.WMS == CLAMP_CLAMP || m_context->CLAMP.WMS == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
(m_context->CLAMP.WMT == CLAMP_CLAMP || m_context->CLAMP.WMT == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
0,
0);
const bool st_overflow =
((GSVector4i::cast(m_vt.m_min.t <= -OVERFLOW_VAL * tsize) & clamp_mode).mask() & 3) ||
((GSVector4i::cast(m_vt.m_max.t >= OVERFLOW_VAL * tsize) & clamp_mode).mask() & 3) ||
m_vt.nan.value;
if (st_overflow)
{
constexpr int n = GSUtil::GetClassVertexCount(primclass);
// Make sure the copy buffer is large enough.
while (m_max_vertex_count < m_index->tail)
GrowVertexBuffer();
GSVertex* RESTRICT vertex = m_vertex->buff;
GSVertex* RESTRICT vertex_copy = m_vertex->buff_copy;
u16* RESTRICT index = m_index->buff;
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
{
GSVector4 stcq[n];
// Load STQ for this primitive.
for (int j = 0; j < n; j++)
stcq[j] = GSVector4::cast(GSVector4i(vertex[index[i + j]].m[0]));
// Perform Q division and see which values need to be rewritten.
GSVector4 uv[n];
GSVector4i small{}, big{}, nan{};
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = primclass == GS_SPRITE_CLASS ? stcq[1].wwww() : stcq[j].wwww();
uv[j] = (stcq[j] / q).xyzw(GSVector4::zero());
small |= GSVector4i::cast(uv[j] <= -OVERFLOW_VAL);
big |= GSVector4i::cast(uv[j] >= OVERFLOW_VAL);
nan |= GSVector4i::cast(uv[j] != uv[j]);
}
// Get the new values for fields that will be rewritten.
// The follows rules are used:
// 1. If there are small values but not big or nans, make all vertices small.
// 2. If there are big values but not small or nans, make all vertices big.
// 3. If there are both big and small values, or nans, make all vertices zero.
GSVector4 uv_new = GSVector4::zero();
uv_new = uv_new.blend32(-OVERFLOW_VAL, GSVector4::cast(small));
uv_new = uv_new.blend32(OVERFLOW_VAL, GSVector4::cast(big));
uv_new = uv_new.blend32(GSVector4::zero(), GSVector4::cast((small & big) | nan));
const GSVector4i rewrite = (((small | big) & clamp_mode) | nan).upl64(GSVector4i::zero());
// If both S and T are rewritten, no point in keeping Q. Just set it to 1.0f;
if ((GSVector4::cast(rewrite).mask() & 3) == 3)
{
for (int j = 0; j < n; j++)
stcq[j] = stcq[j].template insert32<0, 3>(GSVector4::m_one);
}
// Rewrite the fields that require it and write to the copy buffer.
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = (primclass == GS_SPRITE_CLASS) ? stcq[1].wwww() : stcq[j].wwww();
stcq[j] = stcq[j].blend32(uv_new * q, GSVector4::cast(rewrite));
vertex_copy[i + j].m[0] = GSVector4i::cast(stcq[j]);
vertex_copy[i + j].m[1] = vertex[index[i + j]].m[1];
index[i + j] = i + j;
}
}
// Swap the buffers and fix the counts.
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
// Recalculate ST min/max/eq in the vertex trace.
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
{
for (int j = 0; j < n; j++)
{
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex->buff[i + j].m[0]));
const float Q = (primclass == GS_SPRITE_CLASS) ? stcq.w : m_vertex->buff[i + 1].RGBAQ.Q;
stcq = (stcq / Q).xyzw(stcq);
tmin = tmin.min(stcq);
tmax = tmax.max(stcq);
}
}
m_vt.m_min.t = tmin.xyww() * tsize;
m_vt.m_max.t = tmax.xyww() * tsize;
m_vt.m_eq.stq = (m_vt.m_min.t == m_vt.m_max.t).mask();
}
}
}
void GSVertexSWInitStatic()
{
#define InitCVB4(P, T, F, Q) GSVertexSW::s_cvb[P][T][F][Q] = ConvertVertexBuffer<P, T, F, Q>;
@@ -427,24 +308,25 @@ MULTI_ISA_UNSHARED_END
void GSRendererSW::Draw()
{
const GSDrawingContext* context = m_context;
switch (m_vt.m_primclass)
if (!GSConfig.UserHacks_RewriteLargeST)
{
case GS_POINT_CLASS:
RewriteVerticesIfSTOverflow<GS_POINT_CLASS>();
break;
case GS_LINE_CLASS:
RewriteVerticesIfSTOverflow<GS_LINE_CLASS>();
break;
case GS_TRIANGLE_CLASS:
RewriteVerticesIfSTOverflow<GS_TRIANGLE_CLASS>();
break;
case GS_SPRITE_CLASS:
RewriteVerticesIfSTOverflow<GS_SPRITE_CLASS>();
break;
default:
pxFailRel("Unknown primitive class.");
break;
// SW rasterizer stores UV in 1.15.16 format so clamp to +/- (2^15 - 2) (-2 so bilinear doesn't overflow).
// Also only do this for CLAMP and CLAMP_REGION modes to reduce the performance impact.
const GSVector4 tsize = GSVector4(
static_cast<float>(1 << m_context->TEX0.TW),
static_cast<float>(1 << m_context->TEX0.TH),
1.0f,
1.0f);
// Large value is given in terms of normalized ST / Q coordinates.
const GSVector4 large_val = GSVector4::cxpr(static_cast<float>((1 << 15) - 2)) / tsize;
RewriteVerticesIfLargeST(large_val, true);
}
else
{
// More sensitive as the rewrite threshold is lower and we do it for all clamp mode.
// The performance cost will also be higher.
RewriteVerticesIfLargeST(GSVector4::cxpr(16.0f), false);
}
auto data = m_vertex_heap.make_shared<SharedData>().cast<GSRasterizerData>();
+8
View File
@@ -376,6 +376,7 @@ static const char* s_gs_hw_fix_names[] = {
"nativePaletteDraw",
"estimateTextureRegion",
"drawBuffering",
"rewriteLargeST",
"PCRTCOffsets",
"PCRTCOverscan",
"trilinearFiltering",
@@ -622,6 +623,9 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti
case GSHWFixId::DrawBuffering:
return (static_cast<int>(config.UserHacks_DrawBuffering) == value);
case GSHWFixId::RewriteLargeST:
return (static_cast<int>(config.UserHacks_RewriteLargeST) == value);
case GSHWFixId::PCRTCOffsets:
return (static_cast<int>(config.PCRTCOffsets) == value);
@@ -793,6 +797,10 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
config.UserHacks_DrawBuffering = (value > 0);
break;
case GSHWFixId::RewriteLargeST:
config.UserHacks_RewriteLargeST = (value > 0);
break;
case GSHWFixId::PCRTCOffsets:
config.PCRTCOffsets = (value > 0);
break;
+1
View File
@@ -61,6 +61,7 @@ namespace GameDatabaseSchema
NativePaletteDraw,
EstimateTextureRegion,
DrawBuffering,
RewriteLargeST,
PCRTCOffsets,
PCRTCOverscan,
+3
View File
@@ -3236,6 +3236,9 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_CROP, "Estimate Texture Region"),
FSUI_CSTR("Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games)."), "EmuCore/GS",
"UserHacks_EstimateTextureRegion", false, manual_hw_fixes);
DrawToggleSetting(bsi, FSUI_CSTR("Rewrite Large ST"),
FSUI_CSTR("Rewrite large ST coordinates and clamp the values."), "EmuCore/GS",
"UserHacks_RewriteLargeST", false, manual_hw_fixes);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_PALETTE, "GPU Palette Conversion"),
FSUI_CSTR("When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU."),
"EmuCore/GS", "paltex", false, manual_hw_fixes);
+2
View File
@@ -1021,6 +1021,8 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
APPEND("ETR ");
if (GSConfig.UserHacks_DrawBuffering)
APPEND("DRWB ");
if (GSConfig.UserHacks_RewriteLargeST)
APPEND("RWST ");
if (GSConfig.HWSpinGPUForReadbacks)
APPEND("RBSG ");
if (GSConfig.HWSpinCPUForReadbacks)
+2
View File
@@ -1005,6 +1005,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapIntEnumEx(UserHacks_Limit24BitDepth, "UserHacks_Limit24BitDepth");
SettingsWrapBitBoolEx(UserHacks_EstimateTextureRegion, "UserHacks_EstimateTextureRegion");
SettingsWrapBitBoolEx(UserHacks_DrawBuffering, "UserHacks_DrawBuffering");
SettingsWrapBitBoolEx(UserHacks_RewriteLargeST, "UserHacks_RewriteLargeST");
SettingsWrapBitBoolEx(FXAA, "fxaa");
SettingsWrapBitBool(ShadeBoost);
SettingsWrapBitBoolEx(DumpGSData, "DumpGSData");
@@ -1145,6 +1146,7 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
UserHacks_Limit24BitDepth = GSLimit24BitDepth::Disabled;
UserHacks_EstimateTextureRegion = false;
UserHacks_DrawBuffering = false;
UserHacks_RewriteLargeST = false;
UserHacks_TCOffsetX = 0;
UserHacks_TCOffsetY = 0;
UserHacks_CPUSpriteRenderBW = 0;
+5
View File
@@ -3279,6 +3279,11 @@ void VMManager::WarnAboutUnsafeSettings()
append(ICON_FA_CIRCLE_EXCLAMATION,
TRANSLATE_SV("VMManager", "Draw Buffering is enabled, this may result in graphical errors."));
}
if (EmuConfig.GS.UserHacks_RewriteLargeST)
{
append(ICON_FA_CIRCLE_EXCLAMATION,
TRANSLATE_SV("VMManager", "Rewrite large ST is enabled, this may reduce performance."));
}
if (EmuConfig.GS.DumpReplaceableTextures)
{
append(ICON_FA_CIRCLE_EXCLAMATION,