Compare commits

...

11 Commits

Author SHA1 Message Date
refractionpcsx2
094088e038 GS/HW: Add new method of rounding sprites 2026-01-31 00:13:24 +00:00
Mrlinkwii
b4293a40d2 Docs: update Gamedb documentation 2026-01-30 18:06:59 +01:00
Mrlinkwii
9acadb21fe GameDB: remove left over schemea options 2026-01-30 18:06:02 +01:00
TJnotJT
e82fa0bba5 GS/HW: Require 32 bit RT for accumulation blend on max blend. 2026-01-30 11:06:13 +01:00
Ziemas
45490d903a SPU: Slow down DMA 2026-01-29 22:26:28 -05:00
PCSX2 Bot
76dadf792a [ci skip] Qt: Update Base Translation. 2026-01-30 02:03:26 +01:00
wxvu
204829865d GameDB: Add fixes for Puchi Copter 2 2026-01-29 14:21:14 +01:00
lightningterror
84dc2959c5 GSDumpRunner: Use utf-8 encoding for opening files.
Allows the dump compare to work normally with unique characters.
2026-01-29 14:17:02 +01:00
refractionpcsx2
a85b203689 GS/TC: More fixes for dst_matches and old target deletion. 2026-01-29 14:17:02 +01:00
lightningterror
135d40fb7f GS/TC: Remove targets in reverse lookup if the targets are old. 2026-01-29 14:17:02 +01:00
lightningterror
a0bc7a5d0e GS/TC: Update depth lookup when looking up targets. 2026-01-29 14:17:02 +01:00
14 changed files with 1653 additions and 1393 deletions

Binary file not shown.

View File

@@ -24781,6 +24781,11 @@ SLES-53820:
SLES-53821:
name: "Radio Helicopter II"
region: "PAL-E"
patches:
9A695202:
content: |-
comment=Patch that nops a branch instruction causing a freeze.
patch=1,EE,001799AC,word,00000000
SLES-53824:
name: "Trapt"
region: "PAL-E"
@@ -40089,6 +40094,11 @@ SLPM-62624:
name-sort: "ぷちこぷたー2"
name-en: "Petit Copter 2"
region: "NTSC-J"
patches:
9A695202:
content: |-
comment=Patch that nops a branch instruction causing a freeze.
patch=1,EE,001799B8,word,00000000
SLPM-62625:
name: "鬼浜爆走愚連隊 激闘編"
name-sort: "おにはまばくそうぐれんたい げきとうへん"

View File

@@ -184,7 +184,7 @@ if __name__ == "__main__":
args = parser.parse_args()
MAX_DIFF_FRAMES = args.maxframes
outfile = open(args.outfile, "w")
outfile = open(args.outfile, "w", encoding="utf-8")
write(FILE_HEADER)
if not check_regression_tests(os.path.realpath(args.baselinedir), os.path.realpath(args.testdir)):

File diff suppressed because it is too large Load Diff

View File

@@ -139,12 +139,15 @@ The clamp modes are also numerically based.
### GS Hardware Mipmap Fixes
* mipmap [`0` or `1` or `2`] {Off, Basic, Full} Default: Automatic (No value, looks up GameDB)
* mipmap [`0` or `1`] {Off, On} Default: On (looks up GameDB)
* trilinearFiltering [`0` or `1` or `2`] {None, Trilinear, Trilinear Ultra} Default: None (`0`)
### GS Hardware General Fixes
* beforeDraw {`OI` with suffix } {None unless specific game GSC} Default: Automatic (No value, looks up GameDB) with valid variable name (ex. OI_BurnoutGames)
* moveHandler {`MV` with suffix } {None unless specific game GSC} Default: Automatic (No value, looks up GameDB) with valid variable name (ex. MV_Ico)
* afterDraw {`OO` with suffix } {None unless specific game GSC} Default: Automatic (No value, looks up GameDB) with valid variable name
* conservativeFramebuffer [`0` or `1`] {Off or On} Default: On (`1`)
* texturePreloading [`0` or `1` or `2`] {None, Partial or Full Hash Cache} Default: None (`0`)
@@ -153,11 +156,17 @@ The clamp modes are also numerically based.
### GS Hardware Renderer Fixes
* autoFlush [`0` or `1` or `2`] {Disabled, Enabled (Sprites Only), Enabled (All Primitives)} Default: Off (`0`)
* partialTargetInvalidation [`0` or `1`] {Off, On} Default: Off (`0`)
* PCRTCOffsets [`0` or `1`] {Off, On} Default: Off (`0`)
* PCRTCOverscan [`0` or `1`] {Off, On} Default: Off (`0`)
* disableDepthSupport [`0` or `1`] {Off, On} Default: Off (`0`)
* disablePartialInvalidation [`0` or `1`] {Off, On} Default: Off (`0`)
* cpuFramebufferConversion [`0` or `1`] {Off, On} Default: Off (`0`)
* preloadFrameData [`0` or `1`] {Off, On} Default: Off (`0`)
* textureInsideRT [`0` or `1`] {Disabled, Inside Targets, Merge Targets} Default: Off (`0`)
* textureInsideRT [`0` or `1`or `2`] {Disabled, Inside Targets, Merge Targets} Default: Off (`0`)
* PCRTCOverscan [`0` or `1`] {Off, On} Default: Off (`0`)
* PCRTCOverscan [`0` or `1`] {Off, On} Default: Off (`0`)
* cpuCLUTRender [`0` or `1` or `2`] {Disabled, Normal, Aggressive} Default: Disabled (`0`)
* 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`)

View File

@@ -229,11 +229,6 @@
"minimum": 0,
"maximum": 100000
},
"halfBottomOverride": {
"type": "integer",
"minimum": 0,
"maximum": 1
},
"halfPixelOffset": {
"type": "integer",
"minimum": 0,

View File

@@ -3560,7 +3560,7 @@ __forceinline bool AreTrianglesQuad(const GSVertex* RESTRICT vin, const u16* RES
return are_quad;
}
__forceinline bool AreTrianglesQuadNonAA(const GSVertex* RESTRICT vin, const u16* RESTRICT index0, const u16* RESTRICT index1)
bool GSState::AreTrianglesQuadNonAA(const GSVertex* RESTRICT vin, const u16* RESTRICT index0, const u16* RESTRICT index1)
{
u32 v0[3] = {
vin[index0[0]].XYZ.U32[0],

View File

@@ -478,6 +478,7 @@ public:
template<bool shuffle_check>
bool TrianglesAreQuadsImpl();
bool AreTrianglesQuadNonAA(const GSVertex* RESTRICT vin, const u16* RESTRICT index0, const u16* RESTRICT index1);
bool TrianglesAreQuads(bool shuffle_check = false);
template <u32 primclass>
PRIM_OVERLAP GetPrimitiveOverlapDrawlistImpl(bool save_drawlist = false, bool save_bbox = false, float bbox_scale = 1.0f);

View File

@@ -756,7 +756,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
config.ps.channel = ChannelFetch_RGB;
config.colormask.wrgba = 1 | 2 | 4;
r.EndHLEHardwareDraw(false);
src->m_last_draw = r.s_n;
return true;
}
else
@@ -814,6 +814,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
config.ps.channel = ChannelFetch_RED + channel;
config.colormask.wrgba = 8;
r.EndHLEHardwareDraw(false);
dst->m_last_draw = r.s_n;
}
return true;

View File

@@ -4715,31 +4715,207 @@ void GSRendererHW::Draw()
// Note: second hack corrects only the texture coordinate
// Be careful to not correct downscaled targets, this can get messy and break post processing
// but it still needs to adjust native stuff from memory as it's not been compensated for upscaling (Dragon Quest 8 font for example).
if (CanUpscale() && (m_vt.m_primclass == GS_SPRITE_CLASS) && rt && rt->GetScale() > 1.0f)
if (CanUpscale() && ((m_vt.m_primclass == GS_SPRITE_CLASS) || ((m_index.tail % 6 == 0) && m_vt.m_primclass == GS_TRIANGLE_CLASS && m_vt.m_eq.z)) && rt && rt->GetScale() > 1.0f)
{
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
bool valid_format = true;
// Hack to avoid vertical black line in various games (ace combat/tekken)
if (GSConfig.UserHacks_AlignSpriteX)
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
// Note for performance reason I do the check only once on the first
// primitive
const int win_position = v[1].XYZ.X - context->XYOFFSET.OFX;
const bool unaligned_position = ((win_position & 0xF) == 8);
const bool unaligned_texture = ((v[1].U & 0xF) == 0) && PRIM->FST; // I'm not sure this check is useful
const bool hole_in_vertex = (count < 4) || (v[1].XYZ.X != v[2].XYZ.X);
if (hole_in_vertex && unaligned_position && (unaligned_texture || !PRIM->FST))
const GSVertex* RESTRICT v = m_vertex.buff;
const u16* RESTRICT index = m_index.buff;
const size_t count = m_index.tail;
for (int i = 0; i < count; i += 6)
{
// Normaly vertex are aligned on full pixels and texture in half
// pixels. Let's extend the coverage of an half-pixel to avoid
// hole after upscaling
for (u32 i = 0; i < count; i += 2)
// Non-axis aligned check when only two triangles
if (!AreTrianglesQuadNonAA(v, &index[i], &index[i + 3]))
{
v[i + 1].XYZ.X += 8;
// I really don't know if it is a good idea. Neither what to do for !PRIM->FST
if (unaligned_texture)
v[i + 1].U += 8;
valid_format = false;
break;
}
}
}
if (valid_format)
{
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
u16* idx = &m_index.buff[0];
// Hack to avoid vertical black line in various games (ace combat/tekken)
if (GSConfig.UserHacks_AlignSpriteX)
{
// Note for performance reason I do the check only once on the first
// primitive
const int win_position0 = v[idx[0]].XYZ.X - context->XYOFFSET.OFX;
const int win_position1 = v[idx[1]].XYZ.X - context->XYOFFSET.OFX;
const bool unaligned_position = ((win_position0 & 0xf) != 0) || ((win_position1 & 0xF) != 0);
const int first_s = (v[idx[0]].ST.S / v[idx[0]].RGBAQ.Q) * static_cast<int>(1 << m_cached_ctx.TEX0.TW);
const bool unaligned_texture = (PRIM->FST && ((v[1].U & 0xF) == 0)) || (!PRIM->FST && (first_s & 0xF) == 0); // I'm not sure this check is useful
const bool hole_in_vertex = (count < 4) || ((v[1].XYZ.X != v[2].XYZ.X) && std::abs(static_cast<int>(v[1].XYZ.X) - static_cast<int>(v[2].XYZ.X)) < 16);
const bool is_tri = m_vt.m_primclass == GS_TRIANGLE_CLASS;
const bool indexed_texture = src && src->m_scale == 1.0f && GSLocalMemory::m_psm[src->m_TEX0.PSM].pal > 0;
const int skip = is_tri ? 3 : 2;
if (m_lod.y == 0)
{
bool can_disable_linear = true;
const GSVector2 gradient = GSVector2(1.0f, 1.0f);
for (u32 i = 0; i < m_index.tail; i += skip)
{
const int x_offset = (is_tri && v[idx[i]].XYZ.X == v[idx[i + 1]].XYZ.X) ? 2 : 1;
const int y_offset = (is_tri && v[idx[i]].XYZ.Y == v[idx[i + 1]].XYZ.Y) ? 2 : 1;
GSVector2 vert = GSVector2(std::abs(static_cast<float>(v[idx[i]].XYZ.X - v[idx[i + x_offset]].XYZ.X)), std::abs(static_cast<float>(v[idx[i]].XYZ.Y - v[idx[i + y_offset]].XYZ.Y)));
GSVector2 tex;
if (!PRIM->FST)
{
const int s_offset = (is_tri && (v[idx[i]].ST.S / v[idx[i]].RGBAQ.Q) == (v[idx[i + 1]].ST.S / v[idx[i + 1]].RGBAQ.Q)) ? 2 : 1;
const int t_offset = (is_tri && (v[idx[i]].ST.T / v[idx[i]].RGBAQ.Q) == (v[idx[i + 1]].ST.T / v[idx[i + 1]].RGBAQ.Q)) ? 2 : 1;
GSVector2 v0, v1;
float s = std::min((v[idx[i]].ST.S / v[idx[i]].RGBAQ.Q), 1.0f);
float t = std::min((v[idx[i]].ST.T / v[idx[i]].RGBAQ.Q), 1.0f);
v0.x = static_cast<int>((1 << m_cached_ctx.TEX0.TW) * s * 16.0f);
v0.y = static_cast<int>((1 << m_cached_ctx.TEX0.TH) * t * 16.0f);
s = std::min((v[idx[i + s_offset]].ST.S / v[idx[i + s_offset]].RGBAQ.Q), 1.0f);
t = std::min((v[idx[i + t_offset]].ST.T / v[idx[i + t_offset]].RGBAQ.Q), 1.0f);
v1.x = static_cast<int>((1 << m_cached_ctx.TEX0.TW) * s * 16.0f);
v1.y = static_cast<int>((1 << m_cached_ctx.TEX0.TH) * t * 16.0f);
tex = GSVector2(std::abs(v1.x - v0.x), std::abs(v1.y - v0.y));
}
else
{
const int u_offset = (is_tri && v[idx[i]].U == v[idx[i + 1]].U) ? 2 : 1;
const int v_offset = (is_tri && v[idx[i]].V == v[idx[i + 1]].V) ? 2 : 1;
tex = GSVector2(std::abs(static_cast<float>(v[idx[i]].U - v[idx[i + u_offset]].U)), std::abs(static_cast<float>(v[idx[i]].V - v[idx[i + v_offset]].V)));
}
GSVector2 grad = tex / vert;
if (grad.x != gradient.x || grad.y != gradient.y)
{
can_disable_linear = false;
break;
}
}
if (can_disable_linear)
{
m_vt.m_filter.linear = 0;
m_vt.m_filter.opt_linear = 0;
}
}
if ((m_vt.m_primclass == GS_SPRITE_CLASS) && hole_in_vertex && unaligned_position && (unaligned_texture || !PRIM->FST))
{
// Normaly vertex are aligned on full pixels and texture in half
// pixels. Let's extend the coverage of an half-pixel to avoid
// hole after upscaling
for (u32 i = 0; i < count; i += 2)
{
v[i + 1].XYZ.X += 8;
// I really don't know if it is a good idea. Neither what to do for !PRIM->FST
if (unaligned_texture)
v[i + 1].U += 8;
}
}
else if (indexed_texture)
{
const int comparitor = unaligned_position ? 0 : 8;
if (!PRIM->FST)
{
int s_offset = (is_tri && (v[idx[0]].ST.S / v[idx[0]].RGBAQ.Q) == (v[idx[1]].ST.S / v[idx[1]].RGBAQ.Q)) ? 2 : 1;
int t_offset = (is_tri && (v[idx[0]].ST.T / v[idx[0]].RGBAQ.Q) == (v[idx[1]].ST.T / v[idx[1]].RGBAQ.Q)) ? 2 : 1;
GSVector2i v0, v1;
float q = v[idx[0]].RGBAQ.Q == 0 ? FLT_MIN : v[idx[0]].RGBAQ.Q;
float s = v[idx[0]].ST.S / q;
float t = v[idx[0]].ST.T / q;
v0.x = static_cast<int>((1 << m_cached_ctx.TEX0.TW) * s * 16.0f);
v0.y = static_cast<int>((1 << m_cached_ctx.TEX0.TH) * t * 16.0f);
q = v[idx[s_offset]].RGBAQ.Q == 0 ? FLT_MIN : v[idx[s_offset]].RGBAQ.Q;
s = v[idx[s_offset]].ST.S / q;
t = v[idx[t_offset]].ST.T / q;
v1.x = static_cast<int>((1 << m_cached_ctx.TEX0.TW) * s * 16.0f);
v1.y = static_cast<int>((1 << m_cached_ctx.TEX0.TH) * t * 16.0f);
bool small_texture = std::abs(v1.x - v0.x) <= (64 * 16) || std::abs(v1.y - v0.y) <= (64 * 16);
bool offset_texture_x = (m_vt.IsLinear() || ((v0.x & 0x8) && (v1.x & 0x8))) && small_texture; // Keep them relatively small to avoid full screen stuff.
bool offset_texture_y = (m_vt.IsLinear() || ((v0.y & 0x8) && (v1.y & 0x8))) && small_texture;
if (offset_texture_x && offset_texture_y)
{
for (u32 i = m_index.buff[0]; i < count; i += skip)
{
GSVector2 st;
float largest_s = std::max(is_tri ? (v[i + 2].ST.S / v[i + 2].RGBAQ.Q) : static_cast<float>(0), std::max((v[i].ST.S / v[i].RGBAQ.Q), (v[i + 1].ST.S / v[i + 1].RGBAQ.Q)));
float smallest_s = std::min(is_tri ? (v[i + 2].ST.S / v[i + 2].RGBAQ.Q) : static_cast<float>(0), std::min((v[i].ST.S / v[i].RGBAQ.Q), (v[i + 1].ST.S / v[i + 1].RGBAQ.Q)));
for (int j = 0; j < skip; j++)
{
q = v[i + j].RGBAQ.Q == 0 ? FLT_MIN : v[i + j].RGBAQ.Q;
float s = v[i + j].ST.S / q;
st.x = static_cast<float>(1 << m_cached_ctx.TEX0.TW) * s;
if ((v[i + j].ST.S / q) == largest_s)
v[i + j].ST.S = ((st.x - 0.5f) / static_cast<float>(1 << m_cached_ctx.TEX0.TW)) * q;
// Check the minimap in Persona 3.
if ((static_cast<int>(st.x * 16) & 0x8) == comparitor && (v[i + j].ST.S / q) == smallest_s)
v[i + j].ST.S = (std::max(0.0f, (st.x - 0.5f)) / static_cast<float>(1 << m_cached_ctx.TEX0.TW)) * q;
}
float largest_t = std::max(is_tri ? (v[i + 2].ST.T / v[i + 2].RGBAQ.Q) : static_cast<float>(0), std::max((v[i].ST.T / v[i].RGBAQ.Q), (v[i + 1].ST.T / v[i + 1].RGBAQ.Q)));
float smallest_t = std::min(is_tri ? (v[i + 2].ST.T / v[i + 2].RGBAQ.Q) : static_cast<float>(0), std::min((v[i].ST.T / v[i].RGBAQ.Q), (v[i + 1].ST.T / v[i + 1].RGBAQ.Q)));
for (int j = 0; j < skip; j++)
{
q = v[i + j].RGBAQ.Q == 0 ? FLT_MIN : v[i + j].RGBAQ.Q;
float t = v[i + j].ST.T / q;
st.y = static_cast<float>(1 << m_cached_ctx.TEX0.TH) * t;
if ((v[i + j].ST.T / q) == largest_t)
v[i + j].ST.T = ((st.y - 0.5f) / static_cast<float>(1 << m_cached_ctx.TEX0.TH)) * q;
// Check the minimap in Persona 3.
if ((static_cast<int>(st.y * 16) & 0x8) == comparitor && (v[i + j].ST.T / q) == smallest_t)
v[i + j].ST.T = (std::max(0.0f, (st.y - 0.5f)) / static_cast<float>(1 << m_cached_ctx.TEX0.TH)) * q;
}
}
}
}
else
{
int u_offset = (is_tri && (v[0].U == v[1].U)) ? 2 : 1;
int v_offset = (is_tri && (v[0].V == v[1].V)) ? 2 : 1;
bool small_texture = std::abs(static_cast<int>(v[idx[u_offset]].U) - static_cast<int>(v[idx[0]].U)) <= (64 * 16) || std::abs(static_cast<int>(v[idx[v_offset]].V) - static_cast<int>(v[idx[0]].V)) <= (64 * 16);
bool offset_texture_x = (m_vt.IsLinear() || ((v[0].U & 0x8) && (v[idx[u_offset]].U & 0x8))) && small_texture;
bool offset_texture_y = (m_vt.IsLinear() || ((v[0].V & 0x8) && (v[idx[v_offset]].V & 0x8))) && small_texture;
if (offset_texture_x && offset_texture_y)
{
for (u32 i = m_index.buff[0]; i < m_index.tail; i += skip)
{
u16 largest_u = std::max(is_tri ? v[idx[i + 2]].U : static_cast<u16>(0), std::max(v[idx[i]].U, v[idx[i + 1]].U));
if ((v[idx[i]].U & 0x8) == comparitor && v[idx[i]].U == largest_u)
v[idx[i]].U = std::max(static_cast<int>(v[idx[i]].U) - 8, 0);
if ((v[idx[i + 1]].U & 0x8) == comparitor && v[idx[i + 1]].U == largest_u)
v[idx[i + 1]].U = std::max(static_cast<int>(v[idx[i + 1]].U) - 8, 0);
if (is_tri && (v[idx[i + 2]].U & 0x8) == comparitor && v[idx[i + 2]].U == largest_u)
v[idx[i + 2]].U = std::max(static_cast<int>(v[idx[i + 2]].U) - 8, 0);
u16 largest_v = std::max(is_tri ? v[idx[i + 2]].V : static_cast<u16>(0), std::max(v[idx[i]].V, v[idx[i + 1]].V));
if ((v[idx[i]].V & 0x8) == comparitor && v[idx[i]].V == largest_v)
v[idx[i]].V = std::max(static_cast<int>(v[idx[i]].V) - 8, 0);
if ((v[idx[i + 1]].V & 0x8) == comparitor && v[idx[i + 1]].V == largest_v)
v[idx[i + 1]].V = std::max(static_cast<int>(v[idx[i + 1]].V) - 8, 0);
if (is_tri && (v[idx[i + 2]].V & 0x8) == comparitor && v[idx[i + 2]].V == largest_v)
v[idx[i + 2]].V = std::max(static_cast<int>(v[idx[i + 2]].V) - 8, 0);
}
}
}
}
}
}
@@ -5910,6 +6086,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
{
case AccBlendLevel::Maximum:
sw_blending |= true;
accumulation_blend &= (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 32);
[[fallthrough]];
case AccBlendLevel::Full:
sw_blending |= m_conf.ps.blend_a != m_conf.ps.blend_b && alpha_c0_high_max_one;

View File

@@ -2404,250 +2404,289 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
};
Target* dst = nullptr;
auto& list = m_dst[type];
Target* dst_match = nullptr;
auto* list = &m_dst[type];
const GSVector4i min_rect = draw_rect.max_u32(GSVector4i(0, 0, draw_rect.x, draw_rect.y));
// TODO: Move all frame stuff to its own routine too.
if (!is_frame)
{
for (auto i = list.begin(); i != list.end();)
for (int iteration = 0; iteration < 2; iteration++)
{
Target* t = *i;
if (bp == t->m_TEX0.TBP0)
{
bool can_use = true;
if (dst != nullptr)
break;
if (dst && ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw) && dst->m_TEX0.TBP0 <= bp))
auto& new_dst = iteration == 0 ? dst : dst_match;
list = &m_dst[iteration == 0 ? type : (1 - type)];
for (auto i = list->begin(); i != list->end();)
{
Target* t = *i;
if (bp == t->m_TEX0.TBP0)
{
DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, dst->m_TEX0.TBP0);
i++;
continue;
}
// if It's an old target and it's being completely overwritten, kill it.
// Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But,
// it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing
// targets at BW=1 because it breaks other games, we can when the *new* buffer area is completely dirty.
if (((!preserve_rgb && !preserve_alpha) || (t->m_was_dst_matched && fbmask == 0xffffff)) && TEX0.TBW != t->m_TEX0.TBW)
{
// Old targets or shrunk targets where Y draw height goes outside the page.
if (TEX0.TBW > 1 && (t->m_age >= 1 || (type == RenderTarget && draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && TEX0.TBW < t->m_TEX0.TBW)))
bool can_use = true;
if (new_dst && ((GSState::s_n - new_dst->m_last_draw) < (GSState::s_n - t->m_last_draw) && new_dst->m_TEX0.TBP0 <= bp))
{
DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, new_dst->m_TEX0.TBP0);
i++;
continue;
}
// If there's no valid RGB, it can't be interchanged between RT and Depth
if (iteration == 1)
{
const u32 valid_mask = (t->m_valid_rgb ? 0x7 : 0x0) | ((t->m_valid_alpha_low || t->m_valid_alpha_high) ? 0x8 : 0x0);
if ((!(valid_mask & GSUtil::GetChannelMask(TEX0.PSM)) || (!is_shuffle && TEX0.TBW < (t->m_TEX0.TBW / 2))) ||
(!is_shuffle && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != GSLocalMemory::m_psm[TEX0.PSM].bpp))
{
if (!preserve_rgb && !preserve_alpha && (!src || src->m_from_target != t) && (valid_mask & GSUtil::GetChannelMask(TEX0.PSM)))
{
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
}
else
i++;
continue;
}
}
// if It's an old target and it's being completely overwritten, kill it.
// Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But,
// it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing
// targets at BW=1 because it breaks other games, we can when the *new* buffer area is completely dirty.
if (((!preserve_rgb && !preserve_alpha) || (t->m_was_dst_matched && fbmask == 0xffffff)) && TEX0.TBW != t->m_TEX0.TBW)
{
// Old targets or shrunk targets where Y draw height goes outside the page.
if (TEX0.TBW > 1 && (t->m_age >= 1 || (type == RenderTarget && draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && TEX0.TBW < t->m_TEX0.TBW)))
{
can_use = false;
}
else if (!t->m_dirty.empty())
{
const GSVector4i size_rect = GSVector4i::loadh(size);
can_use = !t->m_dirty.GetTotalRect(TEX0, size).rintersect(size_rect).eq(size_rect);
}
}
else if (type == RenderTarget && (fbmask == 0xffffff && !t->m_was_dst_matched && TEX0.TBW != t->m_TEX0.TBW))
{
// When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts).
auto& rev_list = m_dst[1 - type];
for (auto j = rev_list.begin(); j != rev_list.end(); ++j)
{
Target* ds = *j;
if (t->m_TEX0.TBP0 != ds->m_TEX0.TBP0 || !ds->m_valid_rgb || TEX0.TBW != ds->m_TEX0.TBW)
continue;
t->m_was_dst_matched = true;
t->m_valid_rgb = false;
break;
}
}
// TODO: What might be a nicer solution than this, is to rearrange the targets to match the new layout, however this comes with some caviets:
// 1. They can draw wider than the FBW
// 2. The dirty+valid rects will need to also be rearranged
// 3. This could mean larger targets hanging around more
// 4. Sources which reference a target may become invalid and will need to be removed
// 5. Potential performance implications from additional render passes/copying
//
// But the bonuses are:
// 1. Rearranging the page layout will fix quite a few games which do this
// 2. Preserved data will be in the correct place (in most cases)
// 3. Less deleting sources/targets
// 4. We can basically do clears in hardware, if they aren't insane ones
bool dirtied_area = t->m_dirty.size() >= 1;
// Check it covers the whole area of the new draw
if (!is_shuffle && dirtied_area)
{
const u32 draw_start = GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const u32 draw_end = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const u32 dirty_start = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
const u32 dirty_end = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
if (dirty_end < draw_end || dirty_start > draw_start)
dirtied_area = false;
}
if (can_use && ((!is_shuffle && dirtied_area) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16)) && ((preserve_alpha && preserve_rgb) || (draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && !possible_clear)) && TEX0.TBW != t->m_TEX0.TBW)
{
can_use = false;
}
else if (!t->m_dirty.empty())
if (can_use)
{
const GSVector4i size_rect = GSVector4i::loadh(size);
can_use = !t->m_dirty.GetTotalRect(TEX0, size).rintersect(size_rect).eq(size_rect);
}
}
else if (type == RenderTarget && (fbmask == 0xffffff && !t->m_was_dst_matched && TEX0.TBW != t->m_TEX0.TBW))
{
// When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts).
auto& rev_list = m_dst[1 - type];
for (auto j = rev_list.begin(); j != rev_list.end(); ++j)
{
Target* ds = *j;
if (t->m_TEX0.TBP0 != ds->m_TEX0.TBP0 || !ds->m_valid_rgb || TEX0.TBW != ds->m_TEX0.TBW)
continue;
t->m_was_dst_matched = true;
t->m_valid_rgb = false;
break;
}
}
// TODO: What might be a nicer solution than this, is to rearrange the targets to match the new layout, however this comes with some caviets:
// 1. They can draw wider than the FBW
// 2. The dirty+valid rects will need to also be rearranged
// 3. This could mean larger targets hanging around more
// 4. Sources which reference a target may become invalid and will need to be removed
// 5. Potential performance implications from additional render passes/copying
//
// But the bonuses are:
// 1. Rearranging the page layout will fix quite a few games which do this
// 2. Preserved data will be in the correct place (in most cases)
// 3. Less deleting sources/targets
// 4. We can basically do clears in hardware, if they aren't insane ones
bool dirtied_area = t->m_dirty.size() >= 1;
// Check it covers the whole area of the new draw
if (!is_shuffle && dirtied_area)
{
const u32 draw_start = GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const u32 draw_end = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const u32 dirty_start = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
const u32 dirty_end = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
if (dirty_end < draw_end || dirty_start > draw_start)
dirtied_area = false;
}
if (can_use && ((!is_shuffle && dirtied_area) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16)) && ((preserve_alpha && preserve_rgb) || (draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && !possible_clear)) && TEX0.TBW != t->m_TEX0.TBW)
{
can_use = false;
}
if (can_use)
{
if (used)
list.MoveFront(i.Index());
dst = t;
dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
else if (!(src && src->m_from_target == t))
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
}
// Probably pointing to half way through the target
else if (!min_rect.rempty() && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets)
{
// Some games misuse the scissor so it ends up valid 1 pixel over, which causes hell for us. So check if it still overlaps without the extra pixel.
const GSVector4i adjusted_valid = GSVector4i(t->m_valid.x, t->m_valid.y, std::min(t->m_valid.z, static_cast<int>(t->m_TEX0.TBW) * 64), t->m_valid.w - 1);
const u32 adjusted_endblock = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, adjusted_valid);
if (adjusted_endblock <= bp)
{
i++;
continue;
}
const u32 widthpage_offset = (std::abs(static_cast<int>(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U);
const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast<int>((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0);
const bool no_target_or_newer = (!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)));
const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y));
const bool ds_offset = !ds || offset != 0;
const bool is_double_buffer = TEX0.TBP0 == ((((t->m_end_block + 1) - t->m_TEX0.TBP0) / 2) + t->m_TEX0.TBP0);
const bool source_match = src && src->m_TEX0.TBP0 <= bp && src->m_end_block > bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect);
const bool was_used_last_draw = t->m_last_draw == (GSState::s_n - 1);
// if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems.
// This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now.
const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32)));
if (source_match || (no_target_or_newer && is_aligned_ok && width_match && overlaps && (is_shuffle || ds_offset || is_double_buffer || was_used_last_draw)))
{
const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM];
// If it overlaps but the target is huge and the Z isn't offset, we need to split the buffer, so let's shrink this one down.
// 896 is just 448 * 2,just gives the buffer chance to be larger than normal, in case they do something like 640x640, or something ridiculous.
if (!is_shuffle && (ds && offset == 0 && (t->m_valid.w >= 896) && ((((t->m_end_block + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) <= bp))
{
const u32 local_offset = (((bp - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * s_psm.pgs.y;
if ((dst = CreateTarget(TEX0, GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), scale, type, true, fbmask, false, false, preserve_rgb || preserve_alpha, GSVector4i::zero(), src)))
dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
// I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target.
if (is_shuffle && src && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
if (!is_shuffle && (!GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM) ||
((widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW)))
{
const int page_offset = TEX0.TBP0 - t->m_TEX0.TBP0;
const int number_pages = page_offset / 32;
const u32 tbw = std::max(t->m_TEX0.TBW, 1u);
const int row_offset = number_pages / tbw;
const int page_height = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
const int vertical_position = row_offset * page_height;
if (src && src->m_from_target == t && src->m_target_direct && vertical_position >= t->m_valid.w / 2)
{
// Valids and drawn since last read doesn't match, keep the target but resize it.
src->m_valid_rect.w = std::min(vertical_position, src->m_valid_rect.w);
t->m_valid.w = std::min(vertical_position, t->m_valid.w);
t->ResizeValidity(t->m_valid);
t->ResizeDrawn(t->m_valid);
++i;
}
else
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
}
continue;
}
GSVector4i lookup_rect = min_rect;
if (is_shuffle)
lookup_rect = lookup_rect & GSVector4i(~8);
const GSVector4i translated_rect = GSVector4i(0, 0, 0, 0).max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, lookup_rect));
const GSVector4i dirty_rect = t->m_dirty.empty() ? GSVector4i::zero() : t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const bool all_dirty = dirty_rect.eq(t->m_valid);
if (!is_shuffle && !dirty_rect.rempty() && (!preserve_alpha && !preserve_rgb) && (GSState::s_n - 3) > t->m_last_draw)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to dirty areas not preserved (Likely change in target)", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
if (!all_dirty && ((translated_rect.w <= t->m_valid.w) || widthpage_offset == 0 || (GSState::s_n - 3) <= t->m_last_draw))
{
if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63) / 64) > 1)
{
// Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080
// which is a problem, because it then puts the Z buffer at 0x1fc0, then offsets THAT by 1 row of pages, so it starts at, you guessed it, 2080.
// So let's check the *real* start.
u32 real_start_address = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_drawn_since_read);
u32 new_end_address = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect);
// Not really overlapping.
if (real_start_address > new_end_address)
{
i++;
continue;
}
}
//DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width());
dst = t;
dst->m_32_bits_fmt |= (psm_s.bpp != 16);
//Continue just in case there's a newer target
if (used)
list.MoveFront(i.Index());
if (t->m_TEX0.TBP0 <= bp || GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect) >= bp)
break;
else
continue;
list->MoveFront(i.Index());
new_dst = t;
new_dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
else if (!(src && src->m_from_target == t))
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
continue;
}
}
}
// Probably pointing to half way through the target
else if (!min_rect.rempty() && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets)
{
// Some games misuse the scissor so it ends up valid 1 pixel over, which causes hell for us. So check if it still overlaps without the extra pixel.
const GSVector4i adjusted_valid = GSVector4i(t->m_valid.x, t->m_valid.y, std::min(t->m_valid.z, static_cast<int>(t->m_TEX0.TBW) * 64), t->m_valid.w - 1);
const u32 adjusted_endblock = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, adjusted_valid);
if (adjusted_endblock <= bp)
{
i++;
continue;
}
i++;
const u32 widthpage_offset = (std::abs(static_cast<int>(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U);
const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast<int>((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0);
const bool no_target_or_newer = (!new_dst || ((GSState::s_n - new_dst->m_last_draw) < (GSState::s_n - t->m_last_draw)));
const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y));
const bool ds_offset = !ds || offset != 0;
const bool is_double_buffer = TEX0.TBP0 == ((((t->m_end_block + 1) - t->m_TEX0.TBP0) / 2) + t->m_TEX0.TBP0);
const bool source_match = src && src->m_TEX0.TBP0 <= bp && src->m_end_block > bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect);
const bool was_used_last_draw = t->m_last_draw == (GSState::s_n - 1);
// if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems.
// This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now.
const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32)));
if (source_match || (no_target_or_newer && is_aligned_ok && width_match && overlaps && (is_shuffle || ds_offset || is_double_buffer || was_used_last_draw)))
{
const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM];
// If it overlaps but the target is huge and the Z isn't offset, we need to split the buffer, so let's shrink this one down.
// 896 is just 448 * 2,just gives the buffer chance to be larger than normal, in case they do something like 640x640, or something ridiculous.
if (!is_shuffle && (ds && offset == 0 && (t->m_valid.w >= 896) && ((((t->m_end_block + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) <= bp))
{
const u32 local_offset = (((bp - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * s_psm.pgs.y;
if ((new_dst = CreateTarget(TEX0, GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), scale, type, true, fbmask, false, false, preserve_rgb || preserve_alpha, GSVector4i::zero(), src)))
new_dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
// I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target.
if (is_shuffle && src && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t)
{
if (iteration == 0)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
}
else
i++;
continue;
}
if (!is_shuffle && (!GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM) ||
((widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW)))
{
const int page_offset = TEX0.TBP0 - t->m_TEX0.TBP0;
const int number_pages = page_offset / 32;
const u32 tbw = std::max(t->m_TEX0.TBW, 1u);
const int row_offset = number_pages / tbw;
const int page_height = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
const int vertical_position = row_offset * page_height;
if (src && src->m_from_target == t && src->m_target_direct && vertical_position >= t->m_valid.w / 2)
{
// Valids and drawn since last read doesn't match, keep the target but resize it.
src->m_valid_rect.w = std::min(vertical_position, src->m_valid_rect.w);
t->m_valid.w = std::min(vertical_position, t->m_valid.w);
t->ResizeValidity(t->m_valid);
t->ResizeDrawn(t->m_valid);
++i;
}
else
{
if (iteration == 0)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
}
else
i++;
}
continue;
}
GSVector4i lookup_rect = min_rect;
if (is_shuffle)
lookup_rect = lookup_rect & GSVector4i(~8);
const GSVector4i translated_rect = GSVector4i(0, 0, 0, 0).max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, lookup_rect));
const GSVector4i dirty_rect = t->m_dirty.empty() ? GSVector4i::zero() : t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const bool all_dirty = dirty_rect.eq(t->m_valid);
if (!is_shuffle && !dirty_rect.rempty() && (!preserve_alpha && !preserve_rgb) && (GSState::s_n - 3) > t->m_last_draw)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to dirty areas not preserved (Likely change in target)", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
continue;
}
if (!all_dirty && ((translated_rect.w <= t->m_valid.w) || widthpage_offset == 0 || (GSState::s_n - 3) <= t->m_last_draw))
{
if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63) / 64) > 1)
{
// Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080
// which is a problem, because it then puts the Z buffer at 0x1fc0, then offsets THAT by 1 row of pages, so it starts at, you guessed it, 2080.
// So let's check the *real* start.
u32 real_start_address = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_drawn_since_read);
u32 new_end_address = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect);
// Not really overlapping.
if (real_start_address > new_end_address)
{
i++;
continue;
}
}
//DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width());
new_dst = t;
new_dst->m_32_bits_fmt |= (psm_s.bpp != 16);
//Continue just in case there's a newer target
if (used)
list->MoveFront(i.Index());
if (t->m_TEX0.TBP0 <= bp || GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect) >= bp)
break;
else
continue;
}
}
}
i++;
}
}
}
else
{
pxAssert(type == RenderTarget);
// Let's try to find a perfect frame that contains valid data
for (auto i = list.begin(); i != list.end(); ++i)
for (auto i = list->begin(); i != list->end(); ++i)
{
Target* t = *i;
@@ -2673,7 +2712,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
{
DevCon.Warning("Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
i = list->erase(i);
delete t;
continue;
}
@@ -2691,7 +2730,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// 2nd try ! Try to find a frame at the requested bp -> bp + size is inside of (or equal to)
if (!dst)
{
for (auto i = list.begin(); i != list.end(); ++i)
for (auto i = list->begin(); i != list->end(); ++i)
{
Target* t = *i;
const u32 end_block = GSLocalMemory::GetEndBlockAddress(bp, TEX0.TBW, TEX0.PSM, GSVector4i(0, size.y, size.x, size.y + 1));
@@ -2711,7 +2750,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
{
DevCon.Warning("2 Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
i = list->erase(i);
delete t;
continue;
}
@@ -2731,7 +2770,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// 3rd try ! Try to find a frame that doesn't contain valid data (honestly I'm not sure we need to do it)
if (!dst)
{
for (auto i = list.begin(); i != list.end(); ++i)
for (auto i = list->begin(); i != list->end(); ++i)
{
Target* t = *i;
if (bp == t->m_TEX0.TBP0 && TEX0.TBW == t->m_TEX0.TBW)
@@ -2743,7 +2782,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
{
DevCon.Warning("3 Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
i = list->erase(i);
delete t;
continue;
}
@@ -3088,67 +3127,28 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
}
else if (!is_frame && !GSConfig.UserHacks_DisableDepthSupport)
{
const int rev_type = (type == DepthStencil) ? RenderTarget : DepthStencil;
// Depth stencil/RT can be an older RT/DS but only check recent RT/DS to avoid to pick
// some bad data.
auto& rev_list = m_dst[rev_type];
Target* dst_match = nullptr;
for (auto i = rev_list.begin(); i != rev_list.end(); ++i)
if (!dst_match)
{
Target* t = *i;
// Don't pull in targets without valid lower 24 bits unless the Z is 32bits and the alpha is valid, it makes no sense to convert them otherwise.
// FIXME: Technically the difference in size is fine, but if the target gets reinterpreted, the hw renderer doesn't rearrange the target.
// This does cause some extra uploads in some games (like Burnout), but without this, bad data gets displayed in games like Transformers.
if (bp != t->m_TEX0.TBP0 || (!t->m_valid_rgb && (!(GSUtil::GetChannelMask(TEX0.PSM) & 0x8) || !(t->m_valid_alpha_low || t->m_valid_alpha_high))) ||
(!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && (possible_clear || ((~GSLocalMemory::m_psm[t->m_TEX0.PSM].fmsk | fbmask) == 0xffffffff))))
const int rev_type = (type == DepthStencil) ? RenderTarget : DepthStencil;
// Depth stencil/RT can be an older RT/DS but only check recent RT/DS to avoid to pick
// some bad data.
auto& rev_list = m_dst[rev_type];
for (auto i = rev_list.begin(); i != rev_list.end(); ++i)
{
continue;
}
// If the format is completely different, but it's the same location, it's likely just overwriting it, so get rid.
// Make sure it's not currently in use, that could be bad.
if (!is_shuffle && (!ds || (ds != t)) &&
t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y)
{
if (src && src->m_target && src->m_from_target == t && src->m_target_direct)
Target* t = *i;
// Don't pull in targets without valid lower 24 bits unless the Z is 32bits and the alpha is valid, it makes no sense to convert them otherwise.
// FIXME: Technically the difference in size is fine, but if the target gets reinterpreted, the hw renderer doesn't rearrange the target.
// This does cause some extra uploads in some games (like Burnout), but without this, bad data gets displayed in games like Transformers.
if (bp != t->m_TEX0.TBP0 || (!t->m_valid_rgb && (!(GSUtil::GetChannelMask(TEX0.PSM) & 0x8) || !(t->m_valid_alpha_low || t->m_valid_alpha_high))) ||
(!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && (possible_clear || ((~GSLocalMemory::m_psm[t->m_TEX0.PSM].fmsk | fbmask) == 0xffffffff))))
{
src->m_target_direct = false;
src->m_shared_texture = false;
t->m_texture = nullptr;
continue;
}
GL_CACHE("TC: Deleting Z draw %d", GSState::s_n);
InvalidateSourcesFromTarget(t);
i = rev_list.erase(i);
delete t;
continue;
}
const GSLocalMemory::psm_t& t_psm_s = GSLocalMemory::m_psm[t->m_TEX0.PSM];
if (t_psm_s.bpp != psm_s.bpp)
{
bool remove_target = possible_clear || (used && !is_shuffle);
// If we have a BW change, and it's not a multiple of 2 (for a shuffle), the game's going to get a jigsaw
// puzzle of pages and can't be expecting to have legitimate data. Tokimeki Memorial 3 reuses a BW 17
// buffer as BW 10, and if we don't discard the BW 17 buffer, the BW 10 buffer ends up twice the size.
const u32 shuffle_bw = (psm_s.bpp > t_psm_s.bpp) ? (TEX0.TBW / 2u) : (TEX0.TBW * 2u);
if (t->m_TEX0.TBW != TEX0.TBW && (t->m_TEX0.TBW != shuffle_bw && !is_shuffle))
// If the format is completely different, but it's the same location, it's likely just overwriting it, so get rid.
// Make sure it's not currently in use, that could be bad.
if (!is_shuffle && (!ds || (ds != t)) &&
t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y)
{
// But we'll make sure the whole existing target's actually being drawn over to be safe.
const u32 end_block = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
if (end_block >= t->UnwrappedEndBlock())
{
GL_CACHE("TC: Not converting %s at %x TBW %u with end block of %x when we're drawing through %x",
to_string(rev_type), t->m_TEX0.TBP0, t->m_TEX0.TBW, t->UnwrappedEndBlock(), end_block);
remove_target = true;
}
}
// Probably an old target, get rid of it.
if (remove_target)
{
// DT Racer hits this path and causes a crash when RT in RT is disabled,
// so let's make sure source and target texture isn't linked/shared before deleting the target.
if (src && src->m_target && src->m_from_target == t && src->m_target_direct)
{
src->m_target_direct = false;
@@ -3156,26 +3156,68 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
t->m_texture = nullptr;
}
GL_CACHE("TC: Deleting Z draw %d", GSState::s_n);
InvalidateSourcesFromTarget(t);
i = rev_list.erase(i);
delete t;
continue;
}
}
const GSLocalMemory::psm_t& t_psm_s = GSLocalMemory::m_psm[t->m_TEX0.PSM];
if (t_psm_s.bpp != psm_s.bpp)
{
bool remove_target = possible_clear || (used && !is_shuffle);
if (t->m_age == 0)
{
dst_match = t;
break;
}
else if (t->m_age == 1 && (preserve_rgb || (preserve_alpha && (t->m_valid_alpha_low || t->m_valid_alpha_high))))
{
dst_match = t;
// If we have a BW change, and it's not a multiple of 2 (for a shuffle), the game's going to get a jigsaw
// puzzle of pages and can't be expecting to have legitimate data. Tokimeki Memorial 3 reuses a BW 17
// buffer as BW 10, and if we don't discard the BW 17 buffer, the BW 10 buffer ends up twice the size.
const u32 shuffle_bw = (psm_s.bpp > t_psm_s.bpp) ? (TEX0.TBW / 2u) : (TEX0.TBW * 2u);
if (t->m_TEX0.TBW != TEX0.TBW && (t->m_TEX0.TBW != shuffle_bw && !is_shuffle))
{
// But we'll make sure the whole existing target's actually being drawn over to be safe.
const u32 end_block = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
if (end_block >= t->UnwrappedEndBlock())
{
GL_CACHE("TC: Not converting %s at %x TBW %u with end block of %x when we're drawing through %x",
to_string(rev_type), t->m_TEX0.TBP0, t->m_TEX0.TBW, t->UnwrappedEndBlock(), end_block);
remove_target = true;
}
}
// Probably an old target, get rid of it.
if (remove_target)
{
// DT Racer hits this path and causes a crash when RT in RT is disabled,
// so let's make sure source and target texture isn't linked/shared before deleting the target.
if (src && src->m_target && src->m_from_target == t && src->m_target_direct)
{
src->m_target_direct = false;
src->m_shared_texture = false;
t->m_texture = nullptr;
}
InvalidateSourcesFromTarget(t);
i = rev_list.erase(i);
delete t;
continue;
}
}
if (t->m_age == 0)
{
dst_match = t;
break;
}
else if (t->m_age == 1 && (preserve_rgb || (preserve_alpha && (t->m_valid_alpha_low || t->m_valid_alpha_high))))
{
dst_match = t;
}
}
}
// We only want to use a matched target if it's actually being used.
if (dst_match)
{
// dst_match, we only want to use a matched target if it's actually being used.
calcRescale(dst_match);
// If we don't need A, and the existing target doesn't have valid alpha, don't bother converting it.
@@ -3192,7 +3234,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// Clear instead of invalidating if there is anything which isn't touched.
clear |= (!preserve_target && fbmask != 0);
GIFRegTEX0 new_TEX0;
new_TEX0.TBP0 = TEX0.TBP0;
new_TEX0.TBP0 = GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets ? dst_match->m_TEX0.TBP0 : TEX0.TBP0;
new_TEX0.TBW = (!half_width) ? dst_match->m_TEX0.TBW : TEX0.TBW;
new_TEX0.PSM = is_shuffle ? dst_match->m_TEX0.PSM : TEX0.PSM;
@@ -3207,8 +3249,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
dst->m_valid_alpha_low = dst_match->m_valid_alpha_low; //&& psm_s.trbpp != 24;
dst->m_valid_alpha_high = dst_match->m_valid_alpha_high; //&& psm_s.trbpp != 24;
dst->m_valid_rgb = dst_match->m_valid_rgb && (dst->m_TEX0.TBW == TEX0.TBW || min_rect.w <= GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].pgs.y);
dst->m_was_dst_matched = true;
dst_match->m_was_dst_matched = true;
dst->m_was_dst_matched = !dst_match->m_was_dst_matched;
dst_match->m_valid_rgb = preserve_rgb;
if (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].bpp > 16)
@@ -4377,6 +4418,7 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
t->m_valid_alpha_low &= preserve_alpha;
t->m_valid_alpha_high &= preserve_alpha;
t->m_valid_rgb &= (fb_mask & 0x00FFFFFF) != 0;
t->m_was_dst_matched = false;
// Don't keep partial depth buffers around.
if ((!t->m_valid_alpha_low && !t->m_valid_alpha_high && !t->m_valid_rgb) || type == DepthStencil)
@@ -4387,7 +4429,22 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
Target* const rev_t = *j;
if (rev_t->m_TEX0.TBP0 == t->m_TEX0.TBP0 && GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp)
{
rev_t->m_was_dst_matched = false;
if (t->m_last_draw > rev_t->m_last_draw && GSUtil::GetChannelMask(t->m_TEX0.PSM) == GSUtil::GetChannelMask(rev_t->m_TEX0.PSM))
{
if (type == DepthStencil && GSUtil::GetChannelMask(t->m_TEX0.PSM) == 0x7)
{
rev_t->m_valid_rgb = false;
rev_t->m_was_dst_matched = false;
}
else
{
GL_CACHE("TC: InvalidateContainedTargets: Remove Target %s[%x, %s]", to_string(1 - type), rev_t->m_TEX0.TBP0, GSUtil::GetPSMName(rev_t->m_TEX0.PSM));
rev_list.erase(j);
delete rev_t;
}
}
else
rev_t->m_was_dst_matched = false;
break;
}
++j;

View File

@@ -380,7 +380,6 @@ static const char* s_gs_hw_fix_names[] = {
"trilinearFiltering",
"skipDrawStart",
"skipDrawEnd",
"halfBottomOverride",
"halfPixelOffset",
"roundSprite",
"nativeScaling",

View File

@@ -65,7 +65,6 @@ namespace GameDatabaseSchema
TrilinearFiltering,
SkipDrawStart,
SkipDrawEnd,
HalfBottomOverride,
HalfPixelOffset,
RoundSprite,
NativeScaling,

View File

@@ -163,7 +163,7 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
if ((AutoDMACtrl & (Index + 1)) == 0)
{
ActiveTSA = 0x2000 + (Index << 10);
DMAICounter = size * 4;
DMAICounter = size * 48;
LastClock = psxRegs.cycle;
}
else if (size >= 256)
@@ -191,7 +191,7 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
if (SPU2::MsgToConsole())
SPU2::ConLog("ADMA%c Error Size of %x too small\n", GetDmaIndexChar(), size);
InputDataLeft = 0;
DMAICounter = size * 4;
DMAICounter = size * 48;
LastClock = psxRegs.cycle;
}
}
@@ -248,7 +248,7 @@ void V_Core::FinishDMAwrite()
DMA7LogWrite(DMAPtr, ReadSize << 1);
#endif
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 4));
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 48));
u32 buff2end = 0;
if (buff1end > 0x100000)
{
@@ -343,7 +343,7 @@ void V_Core::FinishDMAwrite()
DMAPtr += TDA - ActiveTSA;
ReadSize -= TDA - ActiveTSA;
DMAICounter = (DMAICounter - ReadSize) * 4;
DMAICounter = (DMAICounter - ReadSize) * 48;
CounterUpdate(DMAICounter);
@@ -354,7 +354,7 @@ void V_Core::FinishDMAwrite()
void V_Core::FinishDMAread()
{
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 4));
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 48));
u32 buff2end = 0;
if (buff1end > 0x100000)
@@ -426,9 +426,9 @@ void V_Core::FinishDMAread()
// DMA Reads are done AFTER the delay, so to get the timing right we need to scheule one last DMA to catch IRQ's
if (ReadSize)
DMAICounter = std::min(ReadSize, (u32)0x100) * 4;
DMAICounter = std::min(ReadSize, (u32)0x100) * 48;
else
DMAICounter = 4;
DMAICounter = 48;
CounterUpdate(DMAICounter);
@@ -446,7 +446,7 @@ void V_Core::DoDMAread(u16* pMem, u32 size)
ReadSize = size;
IsDMARead = true;
LastClock = psxRegs.cycle;
DMAICounter = std::min(ReadSize, (u32)0x100) * 4;
DMAICounter = (std::min(ReadSize, (u32)0x100) * 48);
Regs.STATX &= ~0x80;
Regs.STATX |= 0x400;
//Regs.ATTR |= 0x30;
@@ -470,7 +470,7 @@ void V_Core::DoDMAwrite(u16* pMem, u32 size)
{
Regs.STATX &= ~0x80;
//Regs.ATTR |= 0x30;
DMAICounter = 1 * 4;
DMAICounter = 1 * 48;
LastClock = psxRegs.cycle;
return;
}