mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Compare commits
17 Commits
v2.7.44
...
gs_preload
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd20651605 | ||
|
|
edb2b37a92 | ||
|
|
4462b3f91d | ||
|
|
57ded8a022 | ||
|
|
304a7f9d30 | ||
|
|
73a09ffe6c | ||
|
|
4e5d7bd407 | ||
|
|
2a3452a489 | ||
|
|
2e7f951399 | ||
|
|
efb66c1d37 | ||
|
|
6fc88a4499 | ||
|
|
238b29836e | ||
|
|
58cbb61aac | ||
|
|
2c7a168029 | ||
|
|
bb4ee5f0fb | ||
|
|
1940fdb3d3 | ||
|
|
bf269e1295 |
@@ -4764,6 +4764,9 @@ SCES-50034:
|
||||
name: "MotoGP"
|
||||
region: "PAL-M5"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
gpuTargetCLUT: 2 # Fixes some (haze?) shuffle colors.
|
||||
textureInsideRT: 1 # Fixes rendering artifacts on racers.
|
||||
SCES-50105:
|
||||
name: "Sky Odyssey"
|
||||
region: "PAL-M5"
|
||||
@@ -54743,6 +54746,9 @@ SLPS-20040:
|
||||
name-sort: "もとGP"
|
||||
name-en: "MotoGP"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
gpuTargetCLUT: 2 # Fixes some (haze?) shuffle colors.
|
||||
textureInsideRT: 1 # Fixes rendering artifacts on racers.
|
||||
SLPS-20041:
|
||||
name: "麻雀悟空 大聖"
|
||||
name-sort: "まーじゃんごくう たいせい"
|
||||
@@ -60491,7 +60497,6 @@ SLPS-25574:
|
||||
halfPixelOffset: 2 # Aligns effects.
|
||||
nativeScaling: 1 # Helps align effects.
|
||||
textureInsideRT: 1 # Fixes channel shuffles.
|
||||
preloadFrameData: 1 # Fixes menu graphics.
|
||||
autoFlush: 1 # Fixes motion blur.
|
||||
minimumBlendingLevel: 2 # Improves post effects.
|
||||
SLPS-25575:
|
||||
@@ -61216,7 +61221,6 @@ SLPS-25682:
|
||||
halfPixelOffset: 2 # Aligns effects.
|
||||
nativeScaling: 1 # Helps align effects.
|
||||
textureInsideRT: 1 # Fixes channel shuffles.
|
||||
preloadFrameData: 1 # Fixes menu graphics.
|
||||
autoFlush: 1 # Fixes motion blur.
|
||||
minimumBlendingLevel: 2 # Improves post effects.
|
||||
SLPS-25683:
|
||||
@@ -61846,7 +61850,6 @@ SLPS-25787:
|
||||
halfPixelOffset: 2 # Aligns effects.
|
||||
nativeScaling: 1 # Helps align effects.
|
||||
textureInsideRT: 1 # Fixes channel shuffles.
|
||||
preloadFrameData: 1 # Fixes menu graphics.
|
||||
autoFlush: 1 # Fixes motion blur.
|
||||
minimumBlendingLevel: 2 # Improves post effects.
|
||||
SLPS-25788:
|
||||
@@ -62258,7 +62261,6 @@ SLPS-25852:
|
||||
halfPixelOffset: 2 # Aligns effects.
|
||||
nativeScaling: 1 # Helps align effects.
|
||||
textureInsideRT: 1 # Fixes channel shuffles.
|
||||
preloadFrameData: 1 # Fixes menu graphics.
|
||||
autoFlush: 1 # Fixes motion blur.
|
||||
minimumBlendingLevel: 2 # Improves post effects.
|
||||
SLPS-25853:
|
||||
@@ -64233,6 +64235,9 @@ SLUS-20058:
|
||||
name: "MotoGP"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
gpuTargetCLUT: 2 # Fixes some (haze?) shuffle colors.
|
||||
textureInsideRT: 1 # Fixes rendering artifacts on racers.
|
||||
SLUS-20062:
|
||||
name: "Grand Theft Auto III"
|
||||
region: "NTSC-U"
|
||||
|
||||
@@ -168,6 +168,7 @@ cbuffer cb1
|
||||
float4 LODParams;
|
||||
float4 STRange;
|
||||
int4 ChannelShuffle;
|
||||
float2 ChannelShuffleOffset;
|
||||
float2 TC_OffsetHack;
|
||||
float2 STScale;
|
||||
float4x4 DitherMatrix;
|
||||
@@ -740,7 +741,7 @@ float4 fog(float4 c, float f)
|
||||
{
|
||||
if(PS_FOG)
|
||||
{
|
||||
c.rgb = trunc(lerp(FogColor, c.rgb, f));
|
||||
c.rgb = trunc(lerp(FogColor, c.rgb, (f * 255.0f) / 256.0f));
|
||||
}
|
||||
|
||||
return c;
|
||||
@@ -757,17 +758,17 @@ float4 ps_color(PS_INPUT input)
|
||||
#endif
|
||||
|
||||
#if PS_CHANNEL_FETCH == 1
|
||||
float4 T = fetch_red(int2(input.p.xy));
|
||||
float4 T = fetch_red(int2(input.p.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 2
|
||||
float4 T = fetch_green(int2(input.p.xy));
|
||||
float4 T = fetch_green(int2(input.p.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 3
|
||||
float4 T = fetch_blue(int2(input.p.xy));
|
||||
float4 T = fetch_blue(int2(input.p.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 4
|
||||
float4 T = fetch_alpha(int2(input.p.xy));
|
||||
float4 T = fetch_alpha(int2(input.p.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 5
|
||||
float4 T = fetch_rgb(int2(input.p.xy));
|
||||
float4 T = fetch_rgb(int2(input.p.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 6
|
||||
float4 T = fetch_gXbY(int2(input.p.xy));
|
||||
float4 T = fetch_gXbY(int2(input.p.xy + ChannelShuffleOffset));
|
||||
#elif PS_DEPTH_FMT > 0
|
||||
float4 T = sample_depth(st_int, input.p.xy);
|
||||
#else
|
||||
|
||||
@@ -49,6 +49,7 @@ layout(std140, binding = 0) uniform cb21
|
||||
vec4 STRange;
|
||||
|
||||
ivec4 ChannelShuffle;
|
||||
vec2 ChannelShuffleOffset;
|
||||
|
||||
vec2 TC_OffsetHack;
|
||||
vec2 STScale;
|
||||
@@ -315,7 +316,7 @@ int fetch_raw_depth()
|
||||
#if PS_TEX_IS_FB == 1
|
||||
return int(sample_from_rt().r * multiplier);
|
||||
#else
|
||||
return int(texelFetch(TextureSampler, ivec2(gl_FragCoord.xy), 0).r * multiplier);
|
||||
return int(texelFetch(TextureSampler, ivec2(gl_FragCoord.xy + ChannelShuffleOffset), 0).r * multiplier);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -324,7 +325,7 @@ vec4 fetch_raw_color()
|
||||
#if PS_TEX_IS_FB == 1
|
||||
return sample_from_rt();
|
||||
#else
|
||||
return texelFetch(TextureSampler, ivec2(gl_FragCoord.xy), 0);
|
||||
return texelFetch(TextureSampler, ivec2(gl_FragCoord.xy + ChannelShuffleOffset), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -646,7 +647,7 @@ bool atst(vec4 C)
|
||||
void fog(inout vec4 C, float f)
|
||||
{
|
||||
#if PS_FOG != 0
|
||||
C.rgb = trunc(mix(FogColor, C.rgb, f));
|
||||
C.rgb = trunc(mix(FogColor, C.rgb, (f * 255.0f) / 256.0f));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,7 @@ layout(std140, set = 0, binding = 1) uniform cb1
|
||||
vec4 LODParams;
|
||||
vec4 STRange;
|
||||
ivec4 ChannelShuffle;
|
||||
vec2 ChannelShuffleOffset;
|
||||
vec2 TC_OffsetHack;
|
||||
vec2 STScale;
|
||||
mat4 DitherMatrix;
|
||||
@@ -907,7 +908,7 @@ bool atst(vec4 C)
|
||||
vec4 fog(vec4 c, float f)
|
||||
{
|
||||
#if PS_FOG
|
||||
c.rgb = trunc(mix(FogColor, c.rgb, f));
|
||||
c.rgb = trunc(mix(FogColor, c.rgb, (f * 255.0f) / 256.0f));
|
||||
#endif
|
||||
|
||||
return c;
|
||||
@@ -926,17 +927,17 @@ vec4 ps_color()
|
||||
#if !NEEDS_TEX
|
||||
vec4 T = vec4(0.0f);
|
||||
#elif PS_CHANNEL_FETCH == 1
|
||||
vec4 T = fetch_red(ivec2(gl_FragCoord.xy));
|
||||
vec4 T = fetch_red(ivec2(gl_FragCoord.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 2
|
||||
vec4 T = fetch_green(ivec2(gl_FragCoord.xy));
|
||||
vec4 T = fetch_green(ivec2(gl_FragCoord.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 3
|
||||
vec4 T = fetch_blue(ivec2(gl_FragCoord.xy));
|
||||
vec4 T = fetch_blue(ivec2(gl_FragCoord.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 4
|
||||
vec4 T = fetch_alpha(ivec2(gl_FragCoord.xy));
|
||||
vec4 T = fetch_alpha(ivec2(gl_FragCoord.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 5
|
||||
vec4 T = fetch_rgb(ivec2(gl_FragCoord.xy));
|
||||
vec4 T = fetch_rgb(ivec2(gl_FragCoord.xy + ChannelShuffleOffset));
|
||||
#elif PS_CHANNEL_FETCH == 6
|
||||
vec4 T = fetch_gXbY(ivec2(gl_FragCoord.xy));
|
||||
vec4 T = fetch_gXbY(ivec2(gl_FragCoord.xy + ChannelShuffleOffset));
|
||||
#elif PS_DEPTH_FMT > 0
|
||||
vec4 T = sample_depth(st_int, ivec2(gl_FragCoord.xy));
|
||||
#else
|
||||
|
||||
@@ -67,17 +67,6 @@ struct KeyCodeName
|
||||
const char* icon_name;
|
||||
};
|
||||
|
||||
// Extended keys for left/right versions, Non conflicting with Qt::Key enum
|
||||
enum ExtendedKeys
|
||||
{
|
||||
Key_RightShift = 0x01100022,
|
||||
Key_LeftShift = 0x01100023,
|
||||
Key_RightControl = 0x01100024,
|
||||
Key_LeftControl = 0x01100025,
|
||||
Key_RightAlt = 0x01100026,
|
||||
Key_LeftAlt = 0x01100027,
|
||||
};
|
||||
|
||||
static constexpr const KeyCodeName s_qt_key_names[] = {
|
||||
{Qt::Key_Escape, "Escape", ICON_PF_ESC},
|
||||
{Qt::Key_Tab, "Tab", ICON_PF_TAB},
|
||||
@@ -103,13 +92,6 @@ static constexpr const KeyCodeName s_qt_key_names[] = {
|
||||
{Qt::Key_Control, "Control", ICON_PF_CTRL},
|
||||
{Qt::Key_Meta, "Meta", ICON_PF_SUPER},
|
||||
{Qt::Key_Alt, "Alt", ICON_PF_ALT},
|
||||
// patch for left and right versions of the keys
|
||||
{ExtendedKeys::Key_LeftShift, "LShift", ICON_PF_SHIFT},
|
||||
{ExtendedKeys::Key_RightShift, "RShift", ICON_PF_SHIFT},
|
||||
{ExtendedKeys::Key_LeftControl, "LCtrl", ICON_PF_CTRL},
|
||||
{ExtendedKeys::Key_RightControl, "RCtrl", ICON_PF_CTRL},
|
||||
{ExtendedKeys::Key_LeftAlt, "LAlt", ICON_PF_ALT},
|
||||
{ExtendedKeys::Key_RightAlt, "RAlt", ICON_PF_ALT},
|
||||
{Qt::Key_CapsLock, "CapsLock", ICON_PF_CAPS},
|
||||
{Qt::Key_NumLock, "NumLock", ICON_PF_NUMLOCK},
|
||||
{Qt::Key_ScrollLock, "ScrollLock", ICON_PF_SCRLK},
|
||||
@@ -593,41 +575,6 @@ u32 QtUtils::KeyEventToCode(const QKeyEvent* ev)
|
||||
const u8 keycode = set_keycode ? map_text_to_keycode(text) : 0;
|
||||
int key = ev->key();
|
||||
|
||||
if (key == Qt::Key_Shift || key == Qt::Key_Alt || key == Qt::Key_Control)
|
||||
{
|
||||
#if defined(Q_OS_WIN) or defined(Q_OS_LINUX)
|
||||
// Scan codes (Tested it)
|
||||
// 0x2A : Left shift
|
||||
// 0x36 : Right shift
|
||||
// 0x1D : Left ctrl
|
||||
// 0xE01D : Right ctrl
|
||||
// 0x38 : Left alt
|
||||
// right alt can become ctrl + right alt in some keyboard layouts (windows)
|
||||
// but thats fine for our use case
|
||||
switch (ev->nativeScanCode())
|
||||
{
|
||||
case 0x2A:
|
||||
key = ExtendedKeys::Key_LeftShift;
|
||||
break;
|
||||
case 0x36:
|
||||
key = ExtendedKeys::Key_RightShift;
|
||||
break;
|
||||
case 0x1D:
|
||||
key = ExtendedKeys::Key_LeftControl;
|
||||
break;
|
||||
case 0xE01D:
|
||||
key = ExtendedKeys::Key_RightControl;
|
||||
break;
|
||||
case 0x38:
|
||||
key = ExtendedKeys::Key_LeftAlt;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
if (keycode != 0)
|
||||
key = keycode; // Override key if mapped
|
||||
|
||||
|
||||
@@ -1202,7 +1202,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0)
|
||||
// Urban Chaos writes to the memory backing the CLUT in the middle of a shuffle, and
|
||||
// it's unclear whether the CLUT would actually get reloaded in that case.
|
||||
if (TEX0.CBP != m_mem.m_clut.GetCLUTCBP())
|
||||
m_channel_shuffle_abort = true;
|
||||
m_channel_shuffle_finish = true;
|
||||
}
|
||||
|
||||
TEX0.CPSM &= 0xa; // 1010b
|
||||
|
||||
@@ -258,7 +258,7 @@ public:
|
||||
bool m_using_temp_z = false;
|
||||
bool m_temp_z_full_copy = false;
|
||||
bool m_in_target_draw = false;
|
||||
bool m_channel_shuffle_abort = false;
|
||||
bool m_channel_shuffle_finish = false;
|
||||
|
||||
u32 m_target_offset = 0;
|
||||
u8 m_scanmask_used = 0;
|
||||
|
||||
@@ -622,6 +622,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
GSVector4 LODParams;
|
||||
GSVector4 STRange;
|
||||
GSVector4i ChannelShuffle;
|
||||
GSVector2 ChannelShuffleOffset;
|
||||
GSVector2 TCOffsetHack;
|
||||
GSVector2 STScale;
|
||||
|
||||
|
||||
@@ -39,15 +39,77 @@ static bool s_nativeres;
|
||||
|
||||
bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
{
|
||||
static bool first_shuffle = false;
|
||||
|
||||
if (skip > 0)
|
||||
{
|
||||
if (skip == 1 && first_shuffle)
|
||||
{
|
||||
first_shuffle = false;
|
||||
|
||||
GIFRegTEX0 RTLookup = GIFRegTEX0::Create(RTBP0, RFBW, RFPSM);
|
||||
GSTextureCache::Source* src = g_texture_cache->LookupSource(true, RTLookup, r.m_cached_ctx.TEXA, r.m_cached_ctx.CLAMP, GSVector4i(0, 0, 1, 1), nullptr, true, false, r.m_cached_ctx.FRAME, true, true);
|
||||
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i(0, 0, 1, 1), true, false, true, src);
|
||||
|
||||
if (!rt)
|
||||
return false;
|
||||
|
||||
GSLocalMemory::psm_t rt_psm = GSLocalMemory::m_psm[RFPSM];
|
||||
int page_offset = (RTBP0 - rt->m_TEX0.TBP0) >> 5;
|
||||
int vertical_offset = page_offset / std::max(rt->m_TEX0.TBW, 1U) * rt_psm.pgs.y;
|
||||
int horizontal_offset = page_offset % std::max(rt->m_TEX0.TBW, 1U) * rt_psm.pgs.x;
|
||||
|
||||
GSVector4i draw_size = GSVector4i(0, 0, 64, 32) + GSVector4i(horizontal_offset, vertical_offset, horizontal_offset, vertical_offset);
|
||||
rt->UnscaleRTAlpha();
|
||||
|
||||
// We need the original red back now for the next channel shuffle.
|
||||
GSHWDrawConfig& config = r.BeginHLEHardwareDraw(
|
||||
rt->GetTexture(), nullptr, rt->GetScale(), rt->GetTexture(), rt->GetScale(), draw_size);
|
||||
config.ps.shuffle = 1;
|
||||
config.ps.dst_fmt = GSLocalMemory::PSM_FMT_32;
|
||||
config.ps.write_rg = 0;
|
||||
config.ps.shuffle_same = 0;
|
||||
config.ps.real16src = 0;
|
||||
config.ps.shuffle_across = 1;
|
||||
config.ps.process_rg = r.SHUFFLE_READWRITE;
|
||||
config.ps.process_ba = r.SHUFFLE_READWRITE;
|
||||
config.colormask.wrgba = 0;
|
||||
config.colormask.wr = 1;
|
||||
config.colormask.wb = 1;
|
||||
config.ps.rta_correction = 0;
|
||||
config.ps.rta_source_correction = 0;
|
||||
config.ps.tfx = TFX_DECAL;
|
||||
config.ps.tcc = true;
|
||||
r.EndHLEHardwareDraw(true);
|
||||
|
||||
rt->m_alpha_min = 0;
|
||||
rt->m_alpha_max = 255;
|
||||
|
||||
rt = nullptr;
|
||||
src = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
skip--;
|
||||
return !first_shuffle;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip == 0)
|
||||
{
|
||||
const int get_next_ctx = r.m_env.PRIM.CTXT;
|
||||
const GSDrawingContext& next_ctx = r.m_env.CTXT[get_next_ctx];
|
||||
|
||||
// Uses these to do some shuffle tricks, it breaks things for us
|
||||
r.m_env.SCANMSK.MSK = 0;
|
||||
r.m_prev_env.SCANMSK.MSK = 0;
|
||||
|
||||
// Game does alternate line channel shuffles with blending, we can't handle this and the first one does it, so skip the second.
|
||||
if (RTME && RTPSM == PSMT8 && (RTBP0 + 0x20) == next_ctx.TEX0.TBP0 && RFBP == next_ctx.FRAME.Block())
|
||||
{
|
||||
skip = 1;
|
||||
skip = 2;
|
||||
return false;
|
||||
}
|
||||
// Detect the deswizzling shuffle from depth, copying the RG and BA separately on each half of the page (ignore the split).
|
||||
@@ -90,7 +152,7 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
draw_size = draw_size + GSVector4i(horizontal_offset, vertical_offset, horizontal_offset, vertical_offset);
|
||||
rt->UnscaleRTAlpha();
|
||||
|
||||
// Shuffle the blue channel in to red, leave green as-is.
|
||||
// Shuffle the blue channel in to red, but swap them, we'll need the original red later.
|
||||
GSHWDrawConfig& config = r.BeginHLEHardwareDraw(
|
||||
rt->GetTexture(), nullptr, rt->GetScale(), rt->GetTexture(), rt->GetScale(), draw_size);
|
||||
config.ps.shuffle = 1;
|
||||
@@ -99,10 +161,11 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
config.ps.shuffle_same = 0;
|
||||
config.ps.real16src = 0;
|
||||
config.ps.shuffle_across = 1;
|
||||
config.ps.process_rg = r.SHUFFLE_WRITE;
|
||||
config.ps.process_ba = r.SHUFFLE_READ;
|
||||
config.ps.process_rg = r.SHUFFLE_READWRITE;
|
||||
config.ps.process_ba = r.SHUFFLE_READWRITE;
|
||||
config.colormask.wrgba = 0;
|
||||
config.colormask.wr = 1;
|
||||
config.colormask.wb = 1;
|
||||
config.ps.rta_correction = 0;
|
||||
config.ps.rta_source_correction = 0;
|
||||
config.ps.tfx = TFX_DECAL;
|
||||
@@ -114,6 +177,7 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
|
||||
rt = nullptr;
|
||||
src = nullptr;
|
||||
first_shuffle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2345,9 +2345,18 @@ void GSRendererHW::Draw()
|
||||
// Fortunately, it seems to change the FBMSK along the way, so this check alone is sufficient.
|
||||
// Tomb Raider: Underworld does similar, except with R, G, B in separate palettes, therefore
|
||||
// we need to split on those too.
|
||||
m_channel_shuffle = !m_channel_shuffle_abort && IsPossibleChannelShuffle() && m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK &&
|
||||
m_last_channel_shuffle_fbp <= m_context->FRAME.Block() && m_last_channel_shuffle_end_block > m_context->FRAME.Block() &&
|
||||
m_last_channel_shuffle_tbp <= m_context->TEX0.TBP0;
|
||||
const bool is_hle_skip = m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle;
|
||||
const u32 max_skip = ((m_channel_shuffle_finish || !m_channel_shuffle_width) ? std::max(m_context->FRAME.FBW, 1U) : m_channel_shuffle_width) << 5;
|
||||
const bool shuffle_detect = IsPossibleChannelShuffle() && m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK &&
|
||||
m_last_channel_shuffle_fbp <= m_context->FRAME.Block() && (m_last_channel_shuffle_fbp + max_skip) >= m_context->FRAME.Block() &&
|
||||
m_last_channel_shuffle_end_block > m_context->FRAME.Block() && m_last_channel_shuffle_tbp <= m_context->TEX0.TBP0
|
||||
&& (m_last_channel_shuffle_tbp + max_skip) >= m_context->TEX0.TBP0;
|
||||
|
||||
const bool shuffle_detect_loose = IsPossibleChannelShuffle() && m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK &&
|
||||
m_last_channel_shuffle_fbp <= m_context->FRAME.Block() &&
|
||||
m_last_channel_shuffle_end_block > m_context->FRAME.Block() && m_last_channel_shuffle_tbp <= m_context->TEX0.TBP0;
|
||||
|
||||
m_channel_shuffle = !m_channel_shuffle_finish && ((!is_hle_skip && shuffle_detect) || (is_hle_skip && shuffle_detect_loose));
|
||||
|
||||
if (m_channel_shuffle)
|
||||
{
|
||||
@@ -2415,12 +2424,21 @@ void GSRendererHW::Draw()
|
||||
CleanupDraw(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!shuffle_detect)
|
||||
{
|
||||
m_last_channel_shuffle_fbp = 0xffff;
|
||||
m_last_channel_shuffle_tbp = 0xffff;
|
||||
m_last_channel_shuffle_end_block = 0xffff;
|
||||
}
|
||||
#ifdef ENABLE_OGL_DEBUG
|
||||
if (num_skipped_channel_shuffle_draws > 0)
|
||||
GL_CACHE("HW: Skipped %d channel shuffle draws ending at %d", num_skipped_channel_shuffle_draws, s_n);
|
||||
#endif
|
||||
num_skipped_channel_shuffle_draws = 0;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
m_last_channel_shuffle_fbp = 0xffff;
|
||||
m_last_channel_shuffle_tbp = 0xffff;
|
||||
m_last_channel_shuffle_end_block = 0xffff;
|
||||
@@ -2429,7 +2447,7 @@ void GSRendererHW::Draw()
|
||||
m_last_rt = nullptr;
|
||||
m_channel_shuffle_width = 0;
|
||||
m_full_screen_shuffle = false;
|
||||
m_channel_shuffle_abort = false;
|
||||
m_channel_shuffle_finish = false;
|
||||
m_channel_shuffle_src_valid = GSVector4i::zero();
|
||||
|
||||
GL_PUSH("HW: Draw %d (Context %u)", s_n, PRIM->CTXT);
|
||||
@@ -3085,15 +3103,16 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
possible_shuffle = !no_rt && (((shuffle_target /*&& GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16*/) /*|| (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && ((m_cached_ctx.TEX0.PSM & 0x6) || m_cached_ctx.FRAME.PSM != m_cached_ctx.TEX0.PSM))*/) || IsPossibleChannelShuffle());
|
||||
const bool is_possible_channel_shuffle = IsPossibleChannelShuffle();
|
||||
possible_shuffle = !no_rt && (((shuffle_target /*&& GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16*/) /*|| (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && ((m_cached_ctx.TEX0.PSM & 0x6) || m_cached_ctx.FRAME.PSM != m_cached_ctx.TEX0.PSM))*/) || is_possible_channel_shuffle);
|
||||
const u32 channel_shuffle_targets = is_possible_channel_shuffle ? EmulateChannelShuffle(nullptr, true) : ChannelFetch_NONE;
|
||||
const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((NeedsBlending() && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_cached_ctx.TEXA.AEM;
|
||||
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 = (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 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 = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used;
|
||||
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);
|
||||
|
||||
// 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.
|
||||
@@ -3277,7 +3296,7 @@ void GSRendererHW::Draw()
|
||||
ZBUF_TEX0.PSM = m_cached_ctx.ZBUF.PSM;
|
||||
|
||||
ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil,
|
||||
m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block(), false,
|
||||
m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block(), !no_rt,
|
||||
src, nullptr, -1);
|
||||
|
||||
ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW;
|
||||
@@ -3892,7 +3911,7 @@ void GSRendererHW::Draw()
|
||||
{
|
||||
const GSVector2i unscaled_size(ds->m_unscaled_size.x, ds->m_unscaled_size.y);
|
||||
ds->m_scale = 1;
|
||||
ds->ResizeTexture(ds->m_unscaled_size.x * target_scale, ds->m_unscaled_size.y * target_scale, true);
|
||||
ds->ResizeTexture(ds->m_unscaled_size.x * target_scale, ds->m_unscaled_size.y * target_scale, true, true, GSVector4i::loadh(ds->m_unscaled_size * target_scale));
|
||||
// Slightly abusing the texture resize.
|
||||
ds->m_scale = target_scale;
|
||||
ds->m_unscaled_size = unscaled_size;
|
||||
@@ -3904,7 +3923,6 @@ void GSRendererHW::Draw()
|
||||
|
||||
if (m_channel_shuffle)
|
||||
{
|
||||
m_last_channel_shuffle_fbp = rt->m_TEX0.TBP0;
|
||||
m_last_channel_shuffle_tbp = src->m_TEX0.TBP0;
|
||||
|
||||
// If it's a new target, we don't know where the end is as it's starting on a shuffle, so just do every shuffle following.
|
||||
@@ -4041,7 +4059,6 @@ void GSRendererHW::Draw()
|
||||
m_last_channel_shuffle_fbmsk = m_context->FRAME.FBMSK;
|
||||
if (rt)
|
||||
{
|
||||
m_last_channel_shuffle_fbp = rt->m_TEX0.TBP0;
|
||||
m_last_channel_shuffle_tbp = src->m_TEX0.TBP0;
|
||||
// Urban Chaos goes from Z16 to C32, so let's just use the rt's original end block.
|
||||
if (!src->m_from_target || GSLocalMemory::m_psm[src->m_from_target_TEX0.PSM].bpp != GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp)
|
||||
@@ -4268,7 +4285,7 @@ void GSRendererHW::Draw()
|
||||
if (rt)
|
||||
{
|
||||
const bool update_fbw = (FRAME_TEX0.TBW != rt->m_TEX0.TBW || rt->m_TEX0.TBW == 1) && !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!NeedsBlending() || IsOpaque() || m_context->ALPHA.IsBlack());
|
||||
rt->m_TEX0.TBW = update_fbw ? ((src && src->m_from_target && src->m_32_bits_fmt) ? src->m_from_target->m_TEX0.TBW : FRAME_TEX0.TBW) : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW);
|
||||
rt->m_TEX0.TBW = update_fbw ? ((src && src->m_from_target && src->m_from_target->m_32_bits_fmt) ? src->m_from_target->m_TEX0.TBW : FRAME_TEX0.TBW) : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW);
|
||||
rt->m_TEX0.PSM = FRAME_TEX0.PSM;
|
||||
}
|
||||
if (ds)
|
||||
@@ -4740,6 +4757,10 @@ void GSRendererHW::Draw()
|
||||
// Limit to 2x the vertical height of the resolution (for double buffering)
|
||||
rt->UpdateValidity(real_rect, !frame_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle)));
|
||||
|
||||
if (m_channel_shuffle)
|
||||
{
|
||||
m_last_channel_shuffle_fbp = rt->m_TEX0.TBP0;
|
||||
}
|
||||
}
|
||||
|
||||
if (ds)
|
||||
@@ -5341,13 +5362,13 @@ bool GSRendererHW::TestChannelShuffle(GSTextureCache::Target* src)
|
||||
const bool shuffle = m_channel_shuffle || IsPossibleChannelShuffle();
|
||||
|
||||
// This is a little redundant since it'll get called twice, but the only way to stop us wasting time on copies.
|
||||
m_channel_shuffle = (shuffle && EmulateChannelShuffle(src, true));
|
||||
m_channel_shuffle = (shuffle && EmulateChannelShuffle(src, true)) != 0;
|
||||
return m_channel_shuffle;
|
||||
}
|
||||
|
||||
__ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt)
|
||||
__ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt)
|
||||
{
|
||||
if ((src->m_texture->GetType() == GSTexture::Type::DepthStencil) && !src->m_32_bits_fmt)
|
||||
if (src && (src->m_texture->GetType() == GSTexture::Type::DepthStencil) && !src->m_32_bits_fmt)
|
||||
{
|
||||
// So far 2 games hit this code path. Urban Chaos and Tales of Abyss
|
||||
// UC: will copy depth to green channel
|
||||
@@ -5357,7 +5378,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
// Green channel is masked
|
||||
GL_INS("HW: HLE Shuffle Tales Of Abyss");
|
||||
if (test_only)
|
||||
return true;
|
||||
return ChannelFetch_RGB;
|
||||
|
||||
m_conf.ps.tales_of_abyss_hle = 1;
|
||||
}
|
||||
@@ -5365,7 +5386,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
{
|
||||
GL_INS("HW: HLE Shuffle Urban Chaos");
|
||||
if (test_only)
|
||||
return true;
|
||||
return ChannelFetch_RGB;
|
||||
|
||||
m_conf.ps.urban_chaos_hle = 1;
|
||||
}
|
||||
@@ -5380,18 +5401,18 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
// handled above.
|
||||
GL_INS("HW: Might not be channel shuffle");
|
||||
if (test_only)
|
||||
return false;
|
||||
return ChannelFetch_NONE;
|
||||
|
||||
m_channel_shuffle = false;
|
||||
return false;
|
||||
}
|
||||
else if (m_cached_ctx.CLAMP.WMS == 3 && ((m_cached_ctx.CLAMP.MAXU & 0x8) == 8))
|
||||
{
|
||||
const ChannelFetch channel_select = ((m_cached_ctx.CLAMP.WMT != 3 && (m_vertex.buff[m_index.buff[0]].V & 0x20) == 0) || (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 0))) ? ChannelFetch_BLUE : ChannelFetch_ALPHA;
|
||||
|
||||
// MGS3/Kill Zone
|
||||
if (test_only)
|
||||
return true;
|
||||
|
||||
const ChannelFetch channel_select = ((m_cached_ctx.CLAMP.WMT != 3 && (m_vertex.buff[m_index.buff[0]].V & 0x20) == 0) || (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 0))) ? ChannelFetch_BLUE : ChannelFetch_ALPHA;
|
||||
return channel_select;
|
||||
|
||||
GL_INS("HW: %s channel", (channel_select == ChannelFetch_BLUE) ? "blue" : "alpha");
|
||||
|
||||
@@ -5401,7 +5422,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
{
|
||||
// Read either Red or Green. Let's check the V coordinate. 0-1 is likely top so
|
||||
// red. 2-3 is likely bottom so green (actually depends on texture base pointer offset)
|
||||
const bool green = PRIM->FST && (m_vertex.buff[0].V & 32);
|
||||
const bool green = (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 2)) || (PRIM->FST && (m_vertex.buff[0].V & 32));
|
||||
if (green && (m_cached_ctx.FRAME.FBMSK & 0x00FFFFFF) == 0x00FFFFFF)
|
||||
{
|
||||
// Typically used in Terminator 3
|
||||
@@ -5429,7 +5450,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
|
||||
GL_INS("HW: Green/Blue channel (%d, %d)", blue_shift, green_shift);
|
||||
if (test_only)
|
||||
return true;
|
||||
return ChannelFetch_GXBY;
|
||||
|
||||
m_conf.cb_ps.ChannelShuffle = GSVector4i(blue_mask, blue_shift, green_mask, green_shift);
|
||||
m_conf.ps.channel = ChannelFetch_GXBY;
|
||||
@@ -5439,7 +5460,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
{
|
||||
GL_INS("HW: Green channel (wrong mask) (fbmask %x)", blue_mask);
|
||||
if (test_only)
|
||||
return true;
|
||||
return ChannelFetch_GREEN;
|
||||
|
||||
m_conf.ps.channel = ChannelFetch_GREEN;
|
||||
}
|
||||
@@ -5448,7 +5469,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
{
|
||||
GL_INS("HW: Green channel");
|
||||
if (test_only)
|
||||
return true;
|
||||
return ChannelFetch_GREEN;
|
||||
|
||||
m_conf.ps.channel = ChannelFetch_GREEN;
|
||||
}
|
||||
@@ -5457,7 +5478,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
// Pop
|
||||
GL_INS("HW: Red channel");
|
||||
if (test_only)
|
||||
return true;
|
||||
return ChannelFetch_RED;
|
||||
|
||||
m_conf.ps.channel = ChannelFetch_RED;
|
||||
}
|
||||
@@ -5502,7 +5523,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
#endif
|
||||
|
||||
if (test_only)
|
||||
return true;
|
||||
return channel;
|
||||
|
||||
m_conf.ps.channel = channel;
|
||||
}
|
||||
@@ -5512,7 +5533,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
m_r.x, m_r.y, m_r.z, m_r.w, min_uv.x, min_uv.y);
|
||||
|
||||
if (test_only)
|
||||
return false;
|
||||
return ChannelFetch_NONE;
|
||||
|
||||
m_channel_shuffle = false;
|
||||
return false;
|
||||
@@ -5546,7 +5567,6 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
s[1].V = 16384;
|
||||
|
||||
m_r = GSVector4i(0, 0, 1024, 1024);
|
||||
|
||||
// We need to count the pages that get shuffled to, some games (like Hitman Blood Money dialogue blur effects) only do half the screen.
|
||||
if (!m_full_screen_shuffle && !m_conf.ps.urban_chaos_hle && !m_conf.ps.tales_of_abyss_hle && src)
|
||||
{
|
||||
@@ -5556,21 +5576,14 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
|
||||
m_channel_shuffle_width = src->m_TEX0.TBW;
|
||||
}
|
||||
|
||||
m_channel_shuffle_finish = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 frame_page_offset = std::max(static_cast<int>(((m_r.x / frame_psm.pgs.x) + (m_r.y / frame_psm.pgs.y) * rt->m_TEX0.TBW)), 0);
|
||||
m_r = GSVector4i(m_r.x & ~(frame_psm.pgs.x - 1), m_r.y & ~(frame_psm.pgs.y - 1), (m_r.z + (frame_psm.pgs.x - 1)) & ~(frame_psm.pgs.x - 1), (m_r.w + (frame_psm.pgs.y - 1)) & ~(frame_psm.pgs.y - 1));
|
||||
|
||||
// Hitman suffers from this, not sure on the exact scenario at the moment, but we need the barrier.
|
||||
if (NeedsBlending() && m_context->ALPHA.IsCdInBlend())
|
||||
{
|
||||
// Needed to enable IsFeedbackLoop.
|
||||
m_conf.ps.channel_fb = 1;
|
||||
// Assume no overlap when it's a channel shuffle, no need for full barriers.
|
||||
m_conf.require_one_barrier = true;
|
||||
}
|
||||
|
||||
// This is for offsetting the texture, however if the texture has a region clamp, we don't want to move it.
|
||||
// A good two test games for this is Ghost in the Shell (no region clamp) and Tekken 5 (offset clamp on shadows)
|
||||
if (rt && rt->m_TEX0.TBP0 == m_cached_ctx.FRAME.Block())
|
||||
@@ -5594,7 +5607,6 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
s[1].U = m_r.z << 4;
|
||||
s[0].V = m_r.y << 4;
|
||||
s[1].V = m_r.w << 4;
|
||||
m_last_channel_shuffle_fbmsk = 0xFFFFFFFF;
|
||||
|
||||
// If we're doing per page copying, then set the valid 1 frame ahead if we're continuing, as this will save the target lookup making a new target for the new row.
|
||||
const u32 frame_offset = m_cached_ctx.FRAME.Block() + (IsPageCopy() ? 0x20 : 0);
|
||||
@@ -5613,13 +5625,15 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
|
||||
new_valid.w = std::max(new_valid.w, offset_height);
|
||||
rt->UpdateValidity(new_valid, true);
|
||||
|
||||
m_channel_shuffle_finish = true;
|
||||
}
|
||||
|
||||
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
|
||||
m_index.tail = 2;
|
||||
|
||||
m_primitive_covers_without_gaps = NoGapsType::FullCover;
|
||||
m_channel_shuffle_abort = false;
|
||||
m_conf.cb_ps.ChannelShuffleOffset = GSVector2(0, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -6425,7 +6439,10 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
|
||||
{
|
||||
// don't overwrite the texture when using channel shuffle, but keep the palette
|
||||
if (!m_channel_shuffle)
|
||||
{
|
||||
m_conf.cb_ps.ChannelShuffleOffset = GSVector2(0, 0);
|
||||
m_conf.tex = tex->m_texture;
|
||||
}
|
||||
m_conf.pal = tex->m_palette;
|
||||
|
||||
// Hazard handling (i.e. reading from the current RT/DS).
|
||||
@@ -6817,7 +6834,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c
|
||||
const GSTextureCache::Target* src_target = nullptr;
|
||||
if (!m_downscale_source || !tex->m_from_target)
|
||||
{
|
||||
if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && (tex_diff != frame_diff || target_region)))
|
||||
if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && tex_diff != frame_diff))
|
||||
{
|
||||
// Can we read the framebuffer directly? (i.e. sample location matches up).
|
||||
if (CanUseTexIsFB(rt, tex, tmm))
|
||||
@@ -6900,37 +6917,63 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c
|
||||
GSVector4i::storel(©_dst_offset, copy_range);
|
||||
if (m_channel_shuffle && (tex_diff || frame_diff))
|
||||
{
|
||||
const int page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5;
|
||||
const int horizontal_offset = ((page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x);
|
||||
const int vertical_offset = ((page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y);
|
||||
|
||||
const u32 page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5;
|
||||
const u32 horizontal_offset = (page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x;
|
||||
const u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y;
|
||||
|
||||
copy_range.x += horizontal_offset;
|
||||
copy_range.y += vertical_offset;
|
||||
copy_range.z += horizontal_offset;
|
||||
copy_range.w += vertical_offset;
|
||||
|
||||
if (!m_channel_shuffle)
|
||||
if (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy)
|
||||
{
|
||||
copy_size.y -= vertical_offset;
|
||||
copy_size.x -= horizontal_offset;
|
||||
}
|
||||
target_region = false;
|
||||
source_region.bits = 0;
|
||||
//copied_rt = tex->m_from_target != nullptr;
|
||||
if (m_in_target_draw && (page_offset || frame_diff))
|
||||
{
|
||||
copy_range.z = copy_range.x + m_r.width();
|
||||
copy_range.w = copy_range.y + m_r.height();
|
||||
const u32 max_skip = ((m_channel_shuffle_finish || !m_channel_shuffle_width) ? 1 : m_channel_shuffle_width) << 5;
|
||||
const bool new_shuffle = !(m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK &&
|
||||
m_last_channel_shuffle_fbp <= m_context->FRAME.Block() && (m_last_channel_shuffle_fbp + max_skip) >= m_context->FRAME.Block() &&
|
||||
m_last_channel_shuffle_end_block > m_context->FRAME.Block() && m_last_channel_shuffle_tbp <= m_context->TEX0.TBP0 && (m_last_channel_shuffle_tbp + max_skip) >= m_context->TEX0.TBP0);
|
||||
|
||||
if (tex_diff != frame_diff)
|
||||
if (rt == tex->m_from_target && new_shuffle)
|
||||
{
|
||||
GSVector4i::storel(©_dst_offset, m_r);
|
||||
if (m_prim_overlap == PRIM_OVERLAP_NO)
|
||||
m_conf.require_one_barrier = true;
|
||||
else
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
}
|
||||
|
||||
copy_range.z = std::min(copy_range.z, src_target->m_unscaled_size.x);
|
||||
copy_range.w = std::min(copy_range.w, src_target->m_unscaled_size.y);
|
||||
m_conf.cb_ps.ChannelShuffleOffset = GSVector2((horizontal_offset - m_r.x) * tex->GetScale(), (vertical_offset - m_r.y) * tex->GetScale());
|
||||
m_conf.ps.channel_fb = 1;
|
||||
target_region = false;
|
||||
source_region.bits = 0;
|
||||
|
||||
unscaled_size = src_target->GetUnscaledSize();
|
||||
scale = src_target->GetScale();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_range.x += horizontal_offset;
|
||||
copy_range.y += vertical_offset;
|
||||
copy_range.z += horizontal_offset;
|
||||
copy_range.w += vertical_offset;
|
||||
|
||||
if (!m_channel_shuffle)
|
||||
{
|
||||
copy_size.y -= vertical_offset;
|
||||
copy_size.x -= horizontal_offset;
|
||||
}
|
||||
target_region = false;
|
||||
source_region.bits = 0;
|
||||
//copied_rt = tex->m_from_target != nullptr;
|
||||
if (m_in_target_draw && (page_offset || frame_diff))
|
||||
{
|
||||
copy_range.z = copy_range.x + m_r.width();
|
||||
copy_range.w = copy_range.y + m_r.height();
|
||||
|
||||
if (tex_diff != frame_diff)
|
||||
{
|
||||
GSVector4i::storel(©_dst_offset, m_r);
|
||||
}
|
||||
}
|
||||
|
||||
copy_range.z = std::min(copy_range.z, src_target->m_unscaled_size.x);
|
||||
copy_range.w = std::min(copy_range.w, src_target->m_unscaled_size.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -9070,8 +9113,9 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc
|
||||
// Bottom of Texture (half height frame, will be the copy of Top texture after the draw)
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
const int tw = static_cast<int>(1 << m_cached_ctx.TEX0.TW);
|
||||
const int th = static_cast<int>(1 << m_cached_ctx.TEX0.TH);
|
||||
// Do not use tw and th, if it has been optimized to not be the TEX0 size, it will crash.
|
||||
const int tw = tex->m_unscaled_size.x;
|
||||
int th = tex->m_unscaled_size.y;
|
||||
|
||||
// Compute the Bottom of texture rectangle
|
||||
pxAssert(m_cached_ctx.TEX0.TBP0 > m_cached_ctx.FRAME.Block());
|
||||
@@ -9079,30 +9123,48 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc
|
||||
GSVector4i r_texture(r_draw);
|
||||
r_texture.y -= offset;
|
||||
r_texture.w -= offset;
|
||||
const int new_height = std::max(r_texture.w, th);
|
||||
|
||||
if (GSTexture* rt = g_gs_device->CreateRenderTarget(tw, th, GSTexture::Format::Color))
|
||||
GSTexture* temp_tex = g_gs_device->CreateTexture(tw, new_height, 1, tex->m_texture->GetFormat(), true);
|
||||
|
||||
if (temp_tex)
|
||||
{
|
||||
// sRect is the top of texture
|
||||
// Need to half pixel offset the dest tex coordinates as draw pixels are top left instead of centre for texel reads.
|
||||
const GSVector4 sRect(m_vt.m_min.t.x / tw, m_vt.m_min.t.y / th, m_vt.m_max.t.x / tw, m_vt.m_max.t.y / th);
|
||||
const GSVector4 dRect = GSVector4(r_texture) + GSVector4(0.5f);
|
||||
const GSVector4i r_full(0, 0, tw, th);
|
||||
if (GSTexture* rt = g_gs_device->CreateRenderTarget(tw, new_height, GSTexture::Format::Color))
|
||||
{
|
||||
// sRect is the top of texture
|
||||
// Need to half pixel offset the dest tex coordinates as draw pixels are top left instead of centre for texel reads.
|
||||
const GSVector4 dRect = GSVector4(r_texture) + GSVector4(0.5f);
|
||||
const GSVector4i r_full(0, 0, tw, th);
|
||||
|
||||
g_gs_device->CopyRect(tex->m_texture, rt, r_full, 0, 0);
|
||||
g_gs_device->CopyRect(tex->m_texture, rt, r_full, 0, 0);
|
||||
|
||||
g_gs_device->StretchRect(tex->m_texture, sRect, rt, dRect, ShaderConvert::COPY, m_vt.IsRealLinear());
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
g_texture_cache->ReplaceSourceTexture(tex, temp_tex, tex->m_scale, GSVector2i(tw, new_height), nullptr, false);
|
||||
|
||||
g_gs_device->CopyRect(rt, tex->m_texture, r_full, 0, 0);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
if (tex->m_region.HasY())
|
||||
{
|
||||
if (tex->m_region.GetMaxY() == th)
|
||||
{
|
||||
tex->m_region.bits &= ~(0xFFFF0000ULL << 32);
|
||||
tex->m_region.SetY(tex->m_region.GetMinY(), new_height);
|
||||
}
|
||||
}
|
||||
th = new_height;
|
||||
const GSVector4 sRect(m_vt.m_min.t.x / tw, m_vt.m_min.t.y / th, m_vt.m_max.t.x / tw, m_vt.m_max.t.y / th);
|
||||
const GSVector4i r_full_new(0, 0, tw, th);
|
||||
g_gs_device->StretchRect(tex->m_texture, sRect, rt, dRect, ShaderConvert::COPY, m_vt.IsRealLinear());
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
|
||||
g_gs_device->CopyRect(rt, tex->m_texture, r_full_new, 0, 0);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
|
||||
g_gs_device->Recycle(rt);
|
||||
}
|
||||
|
||||
// Copy back the texture into the GS mem. I don't know why but it will be
|
||||
// reuploaded again later
|
||||
g_texture_cache->Read(tex, r_texture.rintersect(tex->m_texture->GetRect()));
|
||||
|
||||
g_gs_device->Recycle(rt);
|
||||
}
|
||||
|
||||
// Copy back the texture into the GS mem. I don't know why but it will be
|
||||
// reuploaded again later
|
||||
g_texture_cache->Read(tex, r_texture.rintersect(tex->m_texture->GetRect()));
|
||||
|
||||
g_texture_cache->InvalidateVideoMemSubTarget(_rt);
|
||||
|
||||
return false; // skip current draw
|
||||
|
||||
@@ -96,7 +96,7 @@ private:
|
||||
void HandleProvokingVertexFirst();
|
||||
void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup);
|
||||
void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex);
|
||||
bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr);
|
||||
u32 EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr);
|
||||
void EmulateBlending(int rt_alpha_min, int rt_alpha_max, const bool DATE, bool& DATE_PRIMID, bool& DATE_BARRIER, GSTextureCache::Target* rt,
|
||||
bool can_scale_rt_alpha, bool& new_rt_alpha_scale);
|
||||
void CleanupDraw(bool invalidate_temp_src);
|
||||
|
||||
@@ -1059,12 +1059,13 @@ void GSTextureCache::DirtyRectByPage(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVe
|
||||
|
||||
__ri static GSTextureCache::Source* FindSourceInMap(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA,
|
||||
const GSLocalMemory::psm_t& psm_s, const u32* clut, const GSTexture* gpu_clut, const GSVector2i& compare_lod,
|
||||
const GSTextureCache::SourceRegion& region, u32 fixed_tex0, FastList<GSTextureCache::Source*>& map)
|
||||
const GSTextureCache::SourceRegion& region, u32 fixed_tex0, FastList<GSTextureCache::Source*>& map, const GIFRegCLAMP& CLAMP, GSVector4i read_area)
|
||||
{
|
||||
GSTextureCache::Source* best_s = nullptr;
|
||||
|
||||
for (auto i = map.begin(); i != map.end(); ++i)
|
||||
{
|
||||
GSTextureCache::Source* s = *i;
|
||||
|
||||
if (((TEX0.U32[0] ^ s->m_TEX0.U32[0]) | ((TEX0.U32[1] ^ s->m_TEX0.U32[1]) & 3)) != 0) // TBP0 TBW PSM TW TH
|
||||
continue;
|
||||
|
||||
@@ -1094,8 +1095,23 @@ __ri static GSTextureCache::Source* FindSourceInMap(const GIFRegTEX0& TEX0, cons
|
||||
// When fixed tex0 is used, we must find a matching region texture. The base likely
|
||||
// doesn't contain to the correct region. Bit cheeky here, avoid a logical or by
|
||||
// adding the invalid tex0 bit in.
|
||||
if (((s->m_region.bits | fixed_tex0) != 0) && s->m_region.bits != region.bits)
|
||||
continue;
|
||||
|
||||
if (((s->m_region.bits | fixed_tex0) != 0))
|
||||
{
|
||||
const bool is_clamped = CLAMP.WMS == CLAMP_CLAMP && CLAMP.WMT == CLAMP_CLAMP;
|
||||
if (is_clamped && s->m_region.GetMinX() == 0 && s->m_region.GetMinY() == 0)
|
||||
{
|
||||
const GSVector4i read_region = GSVector4i(0, 0, read_area.z, read_area.w);
|
||||
const GSVector4i region_area = GSVector4i(s->m_region.GetMinX(), s->m_region.GetMinY(), s->m_region.HasX() ? s->m_region.GetMaxX() : (1 << s->m_TEX0.TW), s->m_region.HasY() ? s->m_region.GetMaxY() : (1 << s->m_TEX0.TW));
|
||||
|
||||
if (!region_area.rintersect(read_region).eq(read_region))
|
||||
continue;
|
||||
}
|
||||
else if (((s->m_region.bits | fixed_tex0) != 0) && s->m_region.bits != region.bits)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Same base mip texture, but we need to check that MXL was the same as well.
|
||||
// When mipmapping is off, this will be 0,0 vs 0,0.
|
||||
@@ -1107,7 +1123,10 @@ __ri static GSTextureCache::Source* FindSourceInMap(const GIFRegTEX0& TEX0, cons
|
||||
return s;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
if (best_s != nullptr)
|
||||
return best_s;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, const bool linear, const GIFRegFRAME& frame, bool req_color, bool req_alpha, bool palette)
|
||||
@@ -1127,7 +1146,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c
|
||||
const u32* const clut = g_gs_renderer->m_mem.m_clut;
|
||||
GSTexture* const gpu_clut = (psm_s.pal > 0) ? g_gs_renderer->m_mem.m_clut.GetGPUTexture() : nullptr;
|
||||
Source* src = FindSourceInMap(TEX0, TEXA, psm_s, clut, gpu_clut, GSVector2i(0, 0), region,
|
||||
region.IsFixedTEX0(TEX0), m_src.m_map[TEX0.TBP0 >> 5]);
|
||||
region.IsFixedTEX0(TEX0), m_src.m_map[TEX0.TBP0 >> 5], CLAMP, r);
|
||||
if (src)
|
||||
{
|
||||
GL_CACHE("TC: src hit: (0x%x, %s)", TEX0.TBP0, GSUtil::GetPSMName(TEX0.PSM));
|
||||
@@ -1332,10 +1351,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
const GSOffset offset(psm_s.info, TEX0.TBP0, TEX0.TBW, TEX0.PSM);
|
||||
const u32 region_page = offset.bn(region.GetMinX(), region.GetMinY()) >> 5;
|
||||
if (lookup_page != region_page)
|
||||
src = FindSourceInMap(TEX0, TEXA, psm_s, clut, gpu_clut, compare_lod, region, is_fixed_tex0, m_src.m_map[region_page]);
|
||||
src = FindSourceInMap(TEX0, TEXA, psm_s, clut, gpu_clut, compare_lod, region, is_fixed_tex0, m_src.m_map[region_page], CLAMP, r);
|
||||
}
|
||||
if (!src)
|
||||
src = FindSourceInMap(TEX0, TEXA, psm_s, clut, gpu_clut, compare_lod, region, is_fixed_tex0, m_src.m_map[lookup_page]);
|
||||
src = FindSourceInMap(TEX0, TEXA, psm_s, clut, gpu_clut, compare_lod, region, is_fixed_tex0, m_src.m_map[lookup_page], CLAMP, r);
|
||||
|
||||
if (src && src->m_from_target && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::MergeTargets && GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, r) > src->m_from_target->m_end_block)
|
||||
{
|
||||
@@ -1703,7 +1722,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
if (found_t && (bw != t->m_TEX0.TBW || t->m_TEX0.PSM != psm))
|
||||
match = false;
|
||||
|
||||
//if (!t_clean && can_convert)
|
||||
// Different swizzle, different width, and dirty, so probably not what we want.
|
||||
// DevCon.Warning("Expected %x Got %x shuffle %d draw %d", psm, t_psm, possible_shuffle, GSState::s_n);
|
||||
if (match)
|
||||
{
|
||||
@@ -2186,7 +2205,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
}
|
||||
}
|
||||
|
||||
src = CreateSource(src_TEX0, TEXA, dst, x_offset, y_offset, lod, &rect, gpu_clut, region, target_bp_hit_outside_valid_area && TEX0.PSM != PSMT8);
|
||||
src = CreateSource(src_TEX0, TEXA, CLAMP, dst, x_offset, y_offset, lod, &rect, gpu_clut, region, target_bp_hit_outside_valid_area && TEX0.PSM != PSMT8);
|
||||
if (!src) [[unlikely]]
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2294,10 +2313,10 @@ void GSTextureCache::CombineAlignedInsideTargets(Target* target, GSTextureCache:
|
||||
continue;
|
||||
}
|
||||
// Formats match
|
||||
if (t->m_TEX0.TBW == target->m_TEX0.TBW && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[target->m_TEX0.PSM].bpp)
|
||||
if ((t->m_TEX0.TBW == target->m_TEX0.TBW || t->m_TEX0.TBW == 1) && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[target->m_TEX0.PSM].bpp)
|
||||
{
|
||||
const GSLocalMemory::psm_t& t_psm = GSLocalMemory::m_psm[t->m_TEX0.PSM];
|
||||
const u32 page_offset = ((t->m_TEX0.TBP0 - target->m_TEX0.TBP0) >> 5) % std::max(1U, t->m_TEX0.TBW);
|
||||
const GSLocalMemory::psm_t& t_psm = GSLocalMemory::m_psm[target->m_TEX0.PSM];
|
||||
const u32 page_offset = ((t->m_TEX0.TBP0 - target->m_TEX0.TBP0) >> 5) % std::max(1U, target->m_TEX0.TBW);
|
||||
const u32 page_width = (t->m_valid.z + (t_psm.pgs.x - 1)) / t_psm.pgs.x;
|
||||
|
||||
if ((page_offset + page_width) <= target->m_TEX0.TBW)
|
||||
@@ -2308,7 +2327,7 @@ void GSTextureCache::CombineAlignedInsideTargets(Target* target, GSTextureCache:
|
||||
{
|
||||
t->Update();
|
||||
|
||||
const u32 vertical_offset = (((t->m_TEX0.TBP0 - target->m_TEX0.TBP0) >> 5) / std::max(1U, t->m_TEX0.TBW)) * t_psm.pgs.y;
|
||||
const u32 vertical_offset = (((t->m_TEX0.TBP0 - target->m_TEX0.TBP0) >> 5) / std::max(1U, target->m_TEX0.TBW)) * t_psm.pgs.y;
|
||||
const u32 horizontal_offset = page_offset * t_psm.pgs.x;
|
||||
const GSVector4i target_drect_unscaled = t->m_drawn_since_read + GSVector4i(horizontal_offset, vertical_offset).xyxy();
|
||||
|
||||
@@ -2748,7 +2767,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
|
||||
dst->m_TEX0.TBP0, dst->m_TEX0.TBW, GSUtil::GetPSMName(dst->m_TEX0.PSM));
|
||||
}
|
||||
|
||||
if (dst->m_scale != scale && (!preserve_scale || is_shuffle || !dst->m_downscaled || TEX0.TBW != dst->m_TEX0.TBW))
|
||||
if (dst->m_scale != scale && (!preserve_scale || (type == RenderTarget && (is_shuffle || !dst->m_downscaled || TEX0.TBW != dst->m_TEX0.TBW))))
|
||||
{
|
||||
calcRescale(dst);
|
||||
GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, clear) :
|
||||
@@ -2756,7 +2775,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
|
||||
if (!tex)
|
||||
return nullptr;
|
||||
|
||||
if (scale == 1.0f)
|
||||
if (scale == 1.0f && type == RenderTarget)
|
||||
{
|
||||
// When using native HPO, the top-left column/row of pixels are often not drawn. Clamp these away to avoid sampling black,
|
||||
// causing bleeding into the edges of the downsampled texture.
|
||||
@@ -2770,7 +2789,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
|
||||
g_gs_device->FilteredDownsampleTexture(dst->m_texture, tex, downsample_factor, clamp_min, dRect);
|
||||
}
|
||||
else
|
||||
g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, dst->m_scale < scale);
|
||||
g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, type == RenderTarget && !preserve_scale);
|
||||
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
m_target_memory_usage = (m_target_memory_usage - dst->m_texture->GetMemUsage()) + tex->GetMemUsage();
|
||||
@@ -3757,23 +3776,54 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->Inside(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) &&
|
||||
GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp)
|
||||
{
|
||||
dst->m_TEX0.TBP0 = t->m_TEX0.TBP0;
|
||||
dst->m_valid = t->m_valid;
|
||||
dst->m_drawn_since_read = t->m_drawn_since_read;
|
||||
dst->m_end_block = t->m_end_block;
|
||||
dst->m_valid_rgb = true;
|
||||
t->m_valid_rgb = false;
|
||||
t->m_was_dst_matched = true;
|
||||
// if this part is dirty, then let's break it in two.
|
||||
GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
|
||||
if (GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect) >= dst->m_TEX0.TBP0)
|
||||
{
|
||||
t->m_valid.w = dirty_rect.y;
|
||||
|
||||
dst->ResizeTexture(t->m_unscaled_size.x, t->m_unscaled_size.y);
|
||||
if (t->m_valid.rempty())
|
||||
{
|
||||
if (src && src->m_target && src->m_from_target == t)
|
||||
{
|
||||
src->m_from_target = nullptr;
|
||||
src->m_texture = t->m_texture;
|
||||
src->m_target_direct = false;
|
||||
src->m_shared_texture = false;
|
||||
|
||||
const ShaderConvert shader = (GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 16) ? ShaderConvert::RGB5A1_TO_FLOAT16 :
|
||||
(GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 32) ? ShaderConvert::RGBA8_TO_FLOAT32 :
|
||||
ShaderConvert::RGBA8_TO_FLOAT24;
|
||||
t->m_texture = nullptr;
|
||||
i = list.erase(j);
|
||||
delete t;
|
||||
}
|
||||
else
|
||||
{
|
||||
InvalidateSourcesFromTarget(t);
|
||||
i = list.erase(j);
|
||||
delete t;
|
||||
}
|
||||
}
|
||||
else
|
||||
t->UpdateValidity(t->m_valid, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
dst->m_TEX0.TBP0 = t->m_TEX0.TBP0;
|
||||
dst->m_valid = t->m_valid;
|
||||
dst->m_drawn_since_read = t->m_drawn_since_read;
|
||||
dst->m_end_block = t->m_end_block;
|
||||
dst->m_valid_rgb = true;
|
||||
t->m_valid_rgb = false;
|
||||
t->m_was_dst_matched = true;
|
||||
|
||||
g_gs_device->StretchRect(t->m_texture, GSVector4(0, 0, 1, 1),
|
||||
dst->m_texture, GSVector4(t->GetUnscaledRect()) * GSVector4(dst->GetScale()), shader, false);
|
||||
dst->ResizeTexture(t->m_unscaled_size.x, t->m_unscaled_size.y);
|
||||
|
||||
const ShaderConvert shader = (GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 16) ? ShaderConvert::RGB5A1_TO_FLOAT16 :
|
||||
(GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 32) ? ShaderConvert::RGBA8_TO_FLOAT32 :
|
||||
ShaderConvert::RGBA8_TO_FLOAT24;
|
||||
|
||||
g_gs_device->StretchRect(t->m_texture, GSVector4(0, 0, 1, 1),
|
||||
dst->m_texture, GSVector4(t->GetUnscaledRect()) * GSVector4(dst->GetScale()), shader, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
@@ -3796,34 +3846,51 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
}
|
||||
|
||||
const int height_adjust = ((((dst_end_block + 31) - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
|
||||
bool delete_target = true;
|
||||
|
||||
if (height_adjust < t->m_unscaled_size.y)
|
||||
{
|
||||
t->m_TEX0.TBP0 = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, GSVector4i(0, height_adjust, t->m_valid.z, t->m_valid.w));
|
||||
u32 new_base_tbp = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, GSVector4i(0, height_adjust, t->m_valid.z, t->m_valid.w));
|
||||
if (t->m_dirty.size() > 0)
|
||||
{
|
||||
u32 dirty_end = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size));
|
||||
|
||||
if (new_base_tbp >= dirty_end)
|
||||
t->m_dirty.clear();
|
||||
else
|
||||
t->Update(true);
|
||||
}
|
||||
|
||||
t->m_TEX0.TBP0 = new_base_tbp;
|
||||
t->m_valid.w -= height_adjust;
|
||||
t->ResizeValidity(t->m_valid);
|
||||
|
||||
GSTexture* tex = (t->m_type == RenderTarget) ?
|
||||
g_gs_device->CreateRenderTarget(t->m_texture->GetWidth(), t->m_texture->GetHeight(), GSTexture::Format::Color, true) :
|
||||
g_gs_device->CreateDepthStencil(t->m_texture->GetWidth(), t->m_texture->GetHeight(), GSTexture::Format::DepthStencil, true);
|
||||
if (tex)
|
||||
if (!t->m_valid.rempty())
|
||||
{
|
||||
g_gs_device->CopyRect(t->m_texture, tex, GSVector4i(0, height_adjust * t->m_scale, t->m_texture->GetWidth(), t->m_texture->GetHeight()), 0, 0);
|
||||
if (src && src->m_target && src->m_from_target == t)
|
||||
delete_target = false;
|
||||
GSTexture* tex = (t->m_type == RenderTarget) ?
|
||||
g_gs_device->CreateRenderTarget(t->m_texture->GetWidth(), t->m_texture->GetHeight(), GSTexture::Format::Color, true) :
|
||||
g_gs_device->CreateDepthStencil(t->m_texture->GetWidth(), t->m_texture->GetHeight(), GSTexture::Format::DepthStencil, true);
|
||||
if (tex)
|
||||
{
|
||||
src->m_from_target = t;
|
||||
src->m_texture = t->m_texture;
|
||||
src->m_target_direct = false;
|
||||
src->m_shared_texture = false;
|
||||
g_gs_device->CopyRect(t->m_texture, tex, GSVector4i(0, height_adjust * t->m_scale, t->m_texture->GetWidth(), t->m_texture->GetHeight()), 0, 0);
|
||||
if (src && src->m_target && src->m_from_target == t)
|
||||
{
|
||||
src->m_from_target = t;
|
||||
src->m_texture = t->m_texture;
|
||||
src->m_target_direct = false;
|
||||
src->m_shared_texture = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_gs_device->Recycle(t->m_texture);
|
||||
}
|
||||
t->m_texture = tex;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_gs_device->Recycle(t->m_texture);
|
||||
}
|
||||
t->m_texture = tex;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (delete_target)
|
||||
{
|
||||
if (src && src->m_target && src->m_from_target == t)
|
||||
{
|
||||
@@ -4305,7 +4372,7 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
|
||||
const u32 end_width = write_bw * 64;
|
||||
const u32 end_height = ((end_page_offset / std::max(write_bw, 1U)) * GSLocalMemory::m_psm[write_psm].pgs.y) + GSLocalMemory::m_psm[write_psm].pgs.y;
|
||||
const GSVector4i r = GSVector4i(0, 0, end_width, end_height);
|
||||
const GSVector4i invalidate_r = TranslateAlignedRectByPage(t, start_bp, write_psm, write_bw, r, false).rintersect(t->m_valid); // it is invalidation but we need a real rect.
|
||||
const GSVector4i invalidate_r = TranslateAlignedRectByPage(t, start_bp, write_psm, write_bw, r, true).rintersect(t->m_valid); // it is invalidation but we need a real rect.
|
||||
|
||||
if (offset == 0 || dirty_rect.rempty() || !dirty_rect.rintersect(invalidate_r).rempty())
|
||||
{
|
||||
@@ -4314,7 +4381,10 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
|
||||
|
||||
RGBAMask mask;
|
||||
mask._u32 = GSUtil::GetChannelMask(write_psm);
|
||||
AddDirtyRectTarget(t, invalidate_r, t->m_TEX0.PSM, t->m_TEX0.TBW, mask, false);
|
||||
if (write_bw != t->m_TEX0.TBW)
|
||||
DirtyRectByPage(start_bp, write_psm, write_bw, t, r);
|
||||
else
|
||||
AddDirtyRectTarget(t, invalidate_r, t->m_TEX0.PSM, t->m_TEX0.TBW, mask, false);
|
||||
}
|
||||
|
||||
++i;
|
||||
@@ -4838,7 +4908,10 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
{
|
||||
if (swizzle_match)
|
||||
{
|
||||
targetr = TranslateAlignedRectByPage(t, bp, psm, bw, r, true);
|
||||
if (exact_bp && GSUtil::HasSameSwizzleBits(psm, t->m_TEX0.PSM) && bw == t->m_TEX0.TBW)
|
||||
targetr = r;
|
||||
else
|
||||
targetr = TranslateAlignedRectByPage(t, bp, psm, bw, r, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -5733,10 +5806,7 @@ void GSTextureCache::ReplaceSourceTexture(Source* s, GSTexture* new_texture, flo
|
||||
if (s->m_from_hash_cache)
|
||||
s->m_from_hash_cache->refcount++;
|
||||
else if (!s->m_shared_texture)
|
||||
{
|
||||
DevCon.Warning("replace %d", m_source_memory_usage);
|
||||
m_source_memory_usage += s->m_texture->GetMemUsage();
|
||||
}
|
||||
}
|
||||
|
||||
void GSTextureCache::IncAge()
|
||||
@@ -5816,7 +5886,7 @@ void GSTextureCache::IncAge()
|
||||
}
|
||||
|
||||
//Fixme: Several issues in here. Not handling depth stencil, pitch conversion doesnt work.
|
||||
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region, bool force_temp)
|
||||
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, Target* dst, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region, bool force_temp)
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
Source* src = new Source(TEX0, TEXA);
|
||||
@@ -6296,6 +6366,41 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
|
||||
m_temporary_source = src;
|
||||
}
|
||||
|
||||
// We can check for a recent upload to see if we can find out out what the actual size is.
|
||||
if (!region.HasEither() && m_temporary_source == nullptr && CLAMP.WMS == CLAMP_CLAMP && CLAMP.WMT == CLAMP_CLAMP)
|
||||
{
|
||||
if (src_range->z < tw && src_range->w < th)
|
||||
{
|
||||
auto& transfers = GSRendererHW::GetInstance()->m_draw_transfers;
|
||||
|
||||
if (transfers.size() > 0)
|
||||
{
|
||||
const int last_draw = transfers.back().draw;
|
||||
|
||||
for (auto iter = transfers.rbegin(); iter != transfers.rend(); ++iter)
|
||||
{
|
||||
if (last_draw - iter->draw > 10)
|
||||
break;
|
||||
|
||||
// If the format, and location doesn't overlap
|
||||
if (iter->blit.DBP == TEX0.TBP0 && iter->blit.DBW == TEX0.TBW && iter->blit.DPSM == TEX0.PSM)
|
||||
{
|
||||
const int new_max_x = std::min(tw, std::max(iter->rect.z, Common::AlignUpPow2(src_range->z, psm.pgs.x)));
|
||||
const int new_max_y = std::min(th, std::max(iter->rect.w, Common::AlignUpPow2(src_range->w, psm.pgs.y)));
|
||||
|
||||
if (new_max_x != tw && new_max_y != th)
|
||||
{
|
||||
region.SetX(0, new_max_x);
|
||||
region.SetY(0, new_max_y);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// maintain the clut even when paltex is on for the dump/replacement texture lookup
|
||||
bool paltex = (GSConfig.GPUPaletteConversion && psm.pal > 0) || gpu_clut;
|
||||
const u32* clut = (psm.pal > 0) ? static_cast<const u32*>(g_gs_renderer->m_mem.m_clut) : nullptr;
|
||||
@@ -7923,7 +8028,7 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca
|
||||
// Can't do partial copies in DirectX for depth textures, and it's probably not ideal in other
|
||||
// APIs either. So use a fullscreen quad setting depth instead.
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
g_gs_device->StretchRect(m_texture, tex, GSVector4(rc), ShaderConvert::DEPTH_COPY, false);
|
||||
g_gs_device->StretchRect(m_texture, tex, GSVector4(rc), ShaderConvert::DEPTH_COPY, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -8650,6 +8755,7 @@ GSTextureCache::SourceRegion GSTextureCache::SourceRegion::Create(GIFRegTEX0 TEX
|
||||
GL_CACHE("TC: Region repeat optimization: %d width -> %d", 1 << TEX0.TW, region.GetWidth());
|
||||
}
|
||||
}
|
||||
|
||||
if (CLAMP.WMT == CLAMP_REGION_CLAMP && CLAMP.MAXV >= CLAMP.MINV)
|
||||
{
|
||||
const u32 rh = CLAMP.MAXV - CLAMP.MINV + 1;
|
||||
|
||||
@@ -268,7 +268,7 @@ public:
|
||||
void UpdateValidChannels(u32 psm, u32 fbmsk);
|
||||
|
||||
/// Resizes target texture, DOES NOT RESCALE.
|
||||
bool ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old = true, bool require_offset = false, GSVector4i offset = GSVector4i::zero(), bool keep_old = false);
|
||||
bool ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old = true, bool require_new_rect = false, GSVector4i new_rect = GSVector4i::zero(), bool keep_old = false);
|
||||
|
||||
private:
|
||||
void UpdateTextureDebugName();
|
||||
@@ -445,7 +445,7 @@ protected:
|
||||
std::unique_ptr<GSDownloadTexture> m_uint16_download_texture;
|
||||
std::unique_ptr<GSDownloadTexture> m_uint32_download_texture;
|
||||
|
||||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region, bool force_temporary = false);
|
||||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, Target* t, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region, bool force_temporary = false);
|
||||
|
||||
bool PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, bool is_frame,
|
||||
bool preload, bool preserve_target, const GSVector4i draw_rect, Target* dst, GSTextureCache::Source* src = nullptr);
|
||||
|
||||
@@ -2081,6 +2081,7 @@ static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, HalfTexel) == of
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, MinMax) == offsetof(GSMTLMainPSUniform, uv_min_max));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, STRange) == offsetof(GSMTLMainPSUniform, st_range));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, ChannelShuffle) == offsetof(GSMTLMainPSUniform, channel_shuffle));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, ChannelShuffleOffset) == offsetof(GSMTLMainPSUniform, channel_shuffle_offset));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TCOffsetHack) == offsetof(GSMTLMainPSUniform, tc_offset));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, STScale) == offsetof(GSMTLMainPSUniform, st_scale));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, DitherMatrix) == offsetof(GSMTLMainPSUniform, dither_matrix));
|
||||
|
||||
@@ -129,6 +129,7 @@ struct GSMTLMainPSUniform
|
||||
unsigned int green_mask;
|
||||
unsigned int green_shift;
|
||||
} channel_shuffle;
|
||||
vector_float2 channel_shuffle_offset;
|
||||
vector_float2 tc_offset;
|
||||
vector_float2 st_scale;
|
||||
matrix_float4x4 dither_matrix;
|
||||
|
||||
@@ -509,7 +509,7 @@ struct PSMain
|
||||
|
||||
uint fetch_raw_depth()
|
||||
{
|
||||
return tex_depth.read(ushort2(in.p.xy)) * 0x1p32f;
|
||||
return tex_depth.read(ushort2(in.p.xy + cb.channel_shuffle_offset)) * 0x1p32f;
|
||||
}
|
||||
|
||||
float4 fetch_raw_color()
|
||||
@@ -517,7 +517,7 @@ struct PSMain
|
||||
if (PS_TEX_IS_FB)
|
||||
return current_color;
|
||||
else
|
||||
return tex.read(ushort2(in.p.xy));
|
||||
return tex.read(ushort2(in.p.xy + cb.channel_shuffle_offset));
|
||||
}
|
||||
|
||||
float4 fetch_c(ushort2 uv)
|
||||
@@ -790,7 +790,7 @@ struct PSMain
|
||||
void fog(thread float4& C, float f)
|
||||
{
|
||||
if (PS_FOG)
|
||||
C.rgb = trunc(mix(cb.fog_color, C.rgb, f));
|
||||
C.rgb = trunc(mix(cb.fog_color, C.rgb, (f * 255.0f) / 256.0f));
|
||||
}
|
||||
|
||||
float4 ps_color()
|
||||
|
||||
@@ -218,7 +218,7 @@ void Pad::SetDefaultHotkeyConfig(SettingsInterface& si)
|
||||
// PCSX2 Controller Settings - Hotkeys
|
||||
|
||||
// PCSX2 Controller Settings - Hotkeys - General
|
||||
si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/LAlt & Keyboard/Return");
|
||||
si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/Alt & Keyboard/Return");
|
||||
|
||||
// PCSX2 Controller Settings - Hotkeys - Graphics
|
||||
si.SetStringValue("Hotkeys", "CycleAspectRatio", "Keyboard/F6");
|
||||
@@ -228,24 +228,24 @@ void Pad::SetDefaultHotkeyConfig(SettingsInterface& si)
|
||||
// si.SetStringValue("Hotkeys", "DecreaseUpscaleMultiplier", "Keyboard"); TBD
|
||||
// si.SetStringValue("Hotkeys", "IncreaseUpscaleMultiplier", "Keyboard"); TBD
|
||||
// si.SetStringValue("Hotkeys", "ReloadTextureReplacements", "Keyboard"); TBD
|
||||
si.SetStringValue("Hotkeys", "GSDumpMultiFrame", "Keyboard/RCtrl & Keyboard/RShift & Keyboard/F8");
|
||||
si.SetStringValue("Hotkeys", "GSDumpMultiFrame", "Keyboard/Control & Keyboard/Shift & Keyboard/F8");
|
||||
si.SetStringValue("Hotkeys", "Screenshot", "Keyboard/F8");
|
||||
si.SetStringValue("Hotkeys", "GSDumpSingleFrame", "Keyboard/RShift & Keyboard/F8");
|
||||
si.SetStringValue("Hotkeys", "GSDumpSingleFrame", "Keyboard/Shift & Keyboard/F8");
|
||||
si.SetStringValue("Hotkeys", "ToggleSoftwareRendering", "Keyboard/F9");
|
||||
// si.SetStringValue("Hotkeys", "ToggleTextureDumping", "Keyboard"); TBD
|
||||
// si.SetStringValue("Hotkeys", "ToggleTextureReplacements", "Keyboard"); TBD
|
||||
si.SetStringValue("Hotkeys", "ZoomIn", "Keyboard/LCtrl & Keyboard/Plus");
|
||||
si.SetStringValue("Hotkeys", "ZoomOut", "Keyboard/LCtrl & Keyboard/Minus");
|
||||
// Missing hotkey for resetting zoom back to 100 with Keyboard/LCtrl & Keyboard/Asterisk
|
||||
si.SetStringValue("Hotkeys", "ZoomIn", "Keyboard/Control & Keyboard/Plus");
|
||||
si.SetStringValue("Hotkeys", "ZoomOut", "Keyboard/Control & Keyboard/Minus");
|
||||
// Missing hotkey for resetting zoom back to 100 with Keyboard/Control & Keyboard/Asterisk
|
||||
|
||||
// PCSX2 Controller Settings - Hotkeys - Input Recording
|
||||
si.SetStringValue("Hotkeys", "InputRecToggleMode", "Keyboard/LShift & Keyboard/R");
|
||||
si.SetStringValue("Hotkeys", "InputRecToggleMode", "Keyboard/Shift & Keyboard/R");
|
||||
|
||||
// PCSX2 Controller Settings - Hotkeys - Save States
|
||||
si.SetStringValue("Hotkeys", "LoadStateFromSlot", "Keyboard/F3");
|
||||
si.SetStringValue("Hotkeys", "SaveStateToSlot", "Keyboard/F1");
|
||||
si.SetStringValue("Hotkeys", "NextSaveStateSlot", "Keyboard/F2");
|
||||
si.SetStringValue("Hotkeys", "PreviousSaveStateSlot", "Keyboard/LShift & Keyboard/F2");
|
||||
si.SetStringValue("Hotkeys", "PreviousSaveStateSlot", "Keyboard/Shift & Keyboard/F2");
|
||||
|
||||
// PCSX2 Controller Settings - Hotkeys - System
|
||||
// si.SetStringValue("Hotkeys", "DecreaseSpeed", "Keyboard"); TBD
|
||||
@@ -256,7 +256,7 @@ void Pad::SetDefaultHotkeyConfig(SettingsInterface& si)
|
||||
si.SetStringValue("Hotkeys", "OpenPauseMenu", "Keyboard/Escape");
|
||||
si.SetStringValue("Hotkeys", "ToggleFrameLimit", "Keyboard/F4");
|
||||
si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Space");
|
||||
si.SetStringValue("Hotkeys", "ToggleSlowMotion", "Keyboard/LShift & Keyboard/Backtab");
|
||||
si.SetStringValue("Hotkeys", "ToggleSlowMotion", "Keyboard/Shift & Keyboard/Backtab");
|
||||
si.SetStringValue("Hotkeys", "ToggleTurbo", "Keyboard/Tab");
|
||||
si.SetStringValue("Hotkeys", "HoldTurbo", "Keyboard/Period");
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
/// Version number for GS and other shaders. Increment whenever any of the contents of the
|
||||
/// shaders change, to invalidate the cache.
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 78;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 80;
|
||||
|
||||
Reference in New Issue
Block a user