diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 7493edf7d6..5f26b33db5 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -51,6 +51,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ClearToRAM", &flags_.ClearToRAM); CheckSetting(iniFile, gameID, "Force04154000Download", &flags_.Force04154000Download); CheckSetting(iniFile, gameID, "DrawSyncEatCycles", &flags_.DrawSyncEatCycles); + CheckSetting(iniFile, gameID, "FakeMipmapChange", &flags_.FakeMipmapChange); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 991b824d73..449c01e3f3 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -51,6 +51,7 @@ struct CompatFlags { bool ClearToRAM; bool Force04154000Download; bool DrawSyncEatCycles; + bool FakeMipmapChange; }; class IniFile; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index c824fc0ef4..d95cb033fd 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -90,7 +90,7 @@ void TextureCacheCommon::GetSamplingParams(int &minFilt, int &magFilt, bool &sCl sClamp = gstate.isTexCoordClampedS(); tClamp = gstate.isTexCoordClampedT(); - bool noMip = (gstate.texlevel & 0xFFFFFF) == 0x000001 || (gstate.texlevel & 0xFFFFFF) == 0x100001 ; // Fix texlevel at 0 + bool noMip = gstate.getTexLevelMode() == GE_TEXLEVEL_MODE_CONST; if (maxLevel == 0) { // Enforce no mip filtering, for safety. diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 56ea4f3fb1..bf56a55114 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -23,6 +23,7 @@ #include "Common/CommonTypes.h" #include "Common/MemoryUtil.h" #include "Core/TextureReplacer.h" +#include "Core/System.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/Common/TextureDecoder.h" @@ -102,6 +103,10 @@ public: return cache.size(); } + bool IsFakeMipmapChange() { + return PSP_CoreParameter().compat.flags().FakeMipmapChange && gstate.getTexLevelMode() == GE_TEXLEVEL_MODE_CONST; + } + // Wow this is starting to grow big. Soon need to start looking at resizing it. // Must stay a POD. struct TexCacheEntry { diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 3f87dc2e0f..230ac35b42 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -100,7 +100,7 @@ static const CommandTableEntry commandTable[] = { { GE_CMD_TEXSIZE6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXSIZE7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, - { GE_CMD_TEXLEVEL, 0, DIRTY_TEXTURE_PARAMS }, // Flushing on this is EXPENSIVE in Gran Turismo and of little use + { GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommon::Execute_TexLevel }, { GE_CMD_TEXADDR0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE | DIRTY_UVSCALEOFFSET }, { GE_CMD_TEXADDR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXADDR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index eccb387ec8..c7e7e438ea 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -604,7 +604,10 @@ void TextureCacheDX9::SetTexture(bool force) { lastBoundTexture = INVALID_TEX; } - u32 texaddr = gstate.getTextureAddress(0); + u8 level = 0; + if (IsFakeMipmapChange()) + level = (gstate.texlevel >> 20) & 0xF; + u32 texaddr = gstate.getTextureAddress(level); if (!Memory::IsValidAddress(texaddr)) { // Bind a null texture and return. pD3Ddevice->SetTexture(0, NULL); @@ -612,9 +615,9 @@ void TextureCacheDX9::SetTexture(bool force) { return; } - const u16 dim = gstate.getTextureDimension(0); - int w = gstate.getTextureWidth(0); - int h = gstate.getTextureHeight(0); + const u16 dim = gstate.getTextureDimension(level); + int w = gstate.getTextureWidth(level); + int h = gstate.getTextureHeight(level); GETextureFormat format = gstate.getTextureFormat(); if (format >= 11) { @@ -1011,7 +1014,11 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry, bool replaceImage maxLevel = 0; } - LoadTextureLevel(*entry, replaced, 0, maxLevel, replaceImages, scaleFactor, dstFmt); + if (IsFakeMipmapChange()) { + u8 level = (gstate.texlevel >> 20) & 0xF; + LoadTextureLevel(*entry, replaced, level, maxLevel, replaceImages, scaleFactor, dstFmt); + } else + LoadTextureLevel(*entry, replaced, 0, maxLevel, replaceImages, scaleFactor, dstFmt); LPDIRECT3DTEXTURE9 &texture = DxTex(entry); if (!texture) { return; @@ -1095,7 +1102,7 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re int h = gstate.getTextureHeight(level); LPDIRECT3DTEXTURE9 &texture = DxTex(&entry); - if (level == 0 && (!replaceImages || texture == nullptr)) { + if ((level == 0 || IsFakeMipmapChange()) && (!replaceImages || texture == nullptr)) { // Create texture D3DPOOL pool = D3DPOOL_MANAGED; int usage = 0; @@ -1115,7 +1122,11 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re tfmt = D3DFMT_A8R8G8B8; } } - HRESULT hr = pD3Ddevice->CreateTexture(tw, th, levels, usage, tfmt, pool, &texture, NULL); + HRESULT hr; + if (IsFakeMipmapChange()) + hr = pD3Ddevice->CreateTexture(tw, th, 1, usage, tfmt, pool, &texture, NULL); + else + hr = pD3Ddevice->CreateTexture(tw, th, levels, usage, tfmt, pool, &texture, NULL); if (FAILED(hr)) { INFO_LOG(G3D, "Failed to create D3D texture"); ReleaseTexture(&entry); @@ -1124,7 +1135,10 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re } D3DLOCKED_RECT rect; - texture->LockRect(level, &rect, NULL, 0); + if (IsFakeMipmapChange()) + texture->LockRect(0, &rect, NULL, 0); + else + texture->LockRect(level, &rect, NULL, 0); gpuStats.numTexturesDecoded++; if (replaced.GetSize(level, w, h)) { @@ -1191,7 +1205,10 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re } } - texture->UnlockRect(level); + if (IsFakeMipmapChange()) + texture->UnlockRect(0); + else + texture->UnlockRect(level); } bool TextureCacheDX9::DecodeTexture(u8 *output, const GPUgstate &state) diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 60ff75517d..f4d5ae585d 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -104,7 +104,7 @@ static const CommandTableEntry commandTable[] = { {GE_CMD_TEXSIZE6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS}, {GE_CMD_TEXSIZE7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS}, {GE_CMD_TEXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE}, - {GE_CMD_TEXLEVEL, 0, DIRTY_TEXTURE_PARAMS}, // Flushing on this is EXPENSIVE in Gran Turismo and of little use + {GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommon::Execute_TexLevel}, {GE_CMD_TEXADDR0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE|DIRTY_UVSCALEOFFSET}, {GE_CMD_TEXADDR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS}, {GE_CMD_TEXADDR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS}, diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 62496b9e27..40a634c7f6 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -686,7 +686,10 @@ void TextureCacheGLES::SetTexture(bool force) { lastBoundTexture = INVALID_TEX; } - u32 texaddr = gstate.getTextureAddress(0); + u8 level = 0; + if (IsFakeMipmapChange()) + level = (gstate.texlevel >> 20) & 0xF; + u32 texaddr = gstate.getTextureAddress(level); if (!Memory::IsValidAddress(texaddr)) { // Bind a null texture and return. glBindTexture(GL_TEXTURE_2D, 0); @@ -694,9 +697,9 @@ void TextureCacheGLES::SetTexture(bool force) { return; } - const u16 dim = gstate.getTextureDimension(0); - int w = gstate.getTextureWidth(0); - int h = gstate.getTextureHeight(0); + const u16 dim = gstate.getTextureDimension(level); + int w = gstate.getTextureWidth(level); + int h = gstate.getTextureHeight(level); GETextureFormat format = gstate.getTextureFormat(); if (format >= 11) { @@ -1055,7 +1058,11 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag // be as good quality as the game's own (might even be better in some cases though). // Always load base level texture here - LoadTextureLevel(*entry, replaced, 0, replaceImages, scaleFactor, dstFmt); + if (IsFakeMipmapChange()) { + u8 level = (gstate.texlevel >> 20) & 0xF; + LoadTextureLevel(*entry, replaced, level, replaceImages, scaleFactor, dstFmt); + } else + LoadTextureLevel(*entry, replaced, 0, replaceImages, scaleFactor, dstFmt); // Mipmapping only enable when texture scaling disable if (maxLevel > 0 && scaleFactor == 1) { @@ -1271,7 +1278,10 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, w, h, components2, dstFmt, pixelData); } else { PROFILE_THIS_SCOPE("loadtex"); - glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components2, dstFmt, pixelData); + if (IsFakeMipmapChange()) + glTexImage2D(GL_TEXTURE_2D, 0, components, w, h, 0, components2, dstFmt, pixelData); + else + glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components2, dstFmt, pixelData); if (!lowMemoryMode_) { GLenum err = glGetError(); if (err == GL_OUT_OF_MEMORY) { diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index e9a0836ba6..15fa493ea6 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1106,6 +1106,17 @@ void GPUCommon::Execute_TexOffsetV(u32 op, u32 diff) { gstate_c.uv.vOff = getFloat24(op); } +void GPUCommon::Execute_TexLevel(u32 op, u32 diff) { + if (diff == 0xFFFFFFFF) return; + + gstate.texlevel ^= diff; + if (gstate.getTexLevelMode() == GE_TEXLEVEL_MODE_CONST && (0x00FF0000 & gstate.texlevel) != 0) { + Flush(); + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); + } + gstate.texlevel ^= diff; +} + void GPUCommon::Execute_Bezier(u32 op, u32 diff) { // This also make skipping drawing very effective. framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index d991bd1a2e..3016434030 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -107,6 +107,7 @@ public: void Execute_TexScaleV(u32 op, u32 diff); void Execute_TexOffsetU(u32 op, u32 diff); void Execute_TexOffsetV(u32 op, u32 diff); + void Execute_TexLevel(u32 op, u32 diff); void Execute_WorldMtxNum(u32 op, u32 diff); void Execute_WorldMtxData(u32 op, u32 diff); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index c487329946..d3f0cf5b77 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -100,7 +100,7 @@ static const CommandTableEntry commandTable[] = { { GE_CMD_TEXSIZE6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXSIZE7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, - { GE_CMD_TEXLEVEL, 0, DIRTY_TEXTURE_PARAMS }, // Flushing on this is EXPENSIVE in Gran Turismo and of little use + { GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommon::Execute_TexLevel }, { GE_CMD_TEXADDR0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE | DIRTY_UVSCALEOFFSET }, { GE_CMD_TEXADDR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXADDR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index d3cf4472c3..4eb9d162c0 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -623,16 +623,19 @@ void TextureCacheVulkan::SetTexture() { } #endif - u32 texaddr = gstate.getTextureAddress(0); + u8 level = 0; + if (IsFakeMipmapChange()) + level = (gstate.texlevel >> 20) & 0xF; + u32 texaddr = gstate.getTextureAddress(level); if (!Memory::IsValidAddress(texaddr)) { // Bind a null texture and return. lastBoundTexture = nullptr; return; } - const u16 dim = gstate.getTextureDimension(0); - int w = gstate.getTextureWidth(0); - int h = gstate.getTextureHeight(0); + const u16 dim = gstate.getTextureDimension(level); + int w = gstate.getTextureWidth(level); + int h = gstate.getTextureHeight(level); if (texaddr == 0x04000000 && w == 2 && h == 2) { // Nonsense bootup texture. Discard. } @@ -1082,6 +1085,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff } if (entry->vkTex) { + u8 level = (gstate.texlevel >> 20) & 0xF; + bool fakeMipmap = IsFakeMipmapChange() && level > 0; // Upload the texture data. for (int i = 0; i <= maxLevel; i++) { int mipWidth = gstate.getTextureWidth(i) * scaleFactor; @@ -1098,7 +1103,12 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff if (replaced.Valid()) { replaced.Load(i, data, stride); } else { - LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt); + if (fakeMipmap) { + LoadTextureLevel(*entry, (uint8_t *)data, stride, level, scaleFactor, dstFmt); + entry->vkTex->texture_->UploadMip(0, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); + break; + } else + LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt); if (replacer.Enabled()) { replacer.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight); } diff --git a/assets/compat.ini b/assets/compat.ini index 7ea055b3d5..00adc21e42 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -152,4 +152,13 @@ ULJS00145 = true ULKS46183 = true ULJS00260 = true ULJS19041 = true -NPJH50843 = true \ No newline at end of file +NPJH50843 = true + +[FakeMipmapChange] +# This hacks separates each mipmap to independent textures to display wrong-size mipmaps. +# For example this requires games like Tactics Ogre(Japanese) to display multi bytes fonts stored in mipmaps. +# See issue #5350. +# Tactics Ogre(Japanese) +ULJM05753 = true +NPJH50348 = true +ULJM06009 = true