From 901a7b804eb372dbd4daf1078571c1eb4a453321 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 12 May 2013 09:04:50 -0700 Subject: [PATCH 1/6] Implement mipmap clut sharing/not sharing. It was kinda already there, probably from JPCSP. Not well tested, but this is what JPCSP does and it makes sense. --- GPU/GLES/TextureCache.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index d9b3923e6..977a3e40a 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -1069,8 +1069,15 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 switch (format) { case GE_TFMT_CLUT4: + { dstFmt = getClutDestFormat((GEPaletteFormat)(clutformat)); + const bool mipmapShareClut = (gstate.texmode & 0x100) == 0; + const int clutSharingOffset = mipmapShareClut ? 0 : level * 16; + if (mipmapShareClut) { + WARN_LOG_REPORT_ONCE(mipMapShareClut4, G3D, "Untested: mipmaps using separate cluts."); + } + switch (clutformat) { case GE_CMODE_16BIT_BGR5650: case GE_CMODE_16BIT_ABGR5551: @@ -1078,8 +1085,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 { tmpTexBuf16.resize(std::max(bufw, w) * h); tmpTexBufRearrange.resize(std::max(bufw, w) * h); - const u16 *clut = GetCurrentClut(); - u32 clutSharingOffset = 0; //(gstate.mipmapShareClut & 1) ? 0 : level * 16; + const u16 *clut = GetCurrentClut() + clutSharingOffset; texByteAlign = 2; // Special optimization: fonts typically draw clut4 with just alpha values in a single color. @@ -1088,14 +1094,14 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 if (gstate.clutformat == (0xC500FF00 | GE_CMODE_16BIT_ABGR4444)) { // TODO: Do this check once per CLUT load? linearClut = true; - linearColor = clut[clutSharingOffset + 15] & 0xFFF0; + linearColor = clut[15] & 0xFFF0; for (int i = 0; i < 16; ++i) { - if ((clut[clutSharingOffset + i] & 0xf) != i) { + if ((clut[i] & 0xf) != i) { linearClut = false; break; } // Alpha 0 doesn't matter. - if (i != 0 && (clut[clutSharingOffset + i] & 0xFFF0) != linearColor) { + if (i != 0 && (clut[i] & 0xFFF0) != linearColor) { linearClut = false; break; } @@ -1106,7 +1112,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 if (linearClut) { DeIndexTexture4Optimal(tmpTexBuf16.data(), texaddr, bufw * h, linearColor); } else { - DeIndexTexture4(tmpTexBuf16.data(), texaddr, bufw * h, clut + clutSharingOffset); + DeIndexTexture4(tmpTexBuf16.data(), texaddr, bufw * h, clut); } } else { tmpTexBuf32.resize(std::max(bufw, w) * h); @@ -1114,7 +1120,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 if (linearClut) { DeIndexTexture4Optimal(tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, linearColor); } else { - DeIndexTexture4(tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, clut + clutSharingOffset); + DeIndexTexture4(tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, clut); } } finalBuf = tmpTexBuf16.data(); @@ -1125,16 +1131,15 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 { tmpTexBuf32.resize(std::max(bufw, w) * h); tmpTexBufRearrange.resize(std::max(bufw, w) * h); - const u32 *clut = GetCurrentClut(); - u32 clutSharingOffset = 0;//gstate.mipmapShareClut ? 0 : level * 16; + const u32 *clut = GetCurrentClut() + clutSharingOffset; if (!(gstate.texmode & 1)) { - DeIndexTexture4(tmpTexBuf32.data(), texaddr, bufw * h, clut + clutSharingOffset); + DeIndexTexture4(tmpTexBuf32.data(), texaddr, bufw * h, clut); finalBuf = tmpTexBuf32.data(); } else { UnswizzleFromMem(texaddr, bufw, 0, level); // Let's reuse tmpTexBuf16, just need double the space. tmpTexBuf16.resize(std::max(bufw, w) * h * 2); - DeIndexTexture4((u32 *)tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, clut + clutSharingOffset); + DeIndexTexture4((u32 *)tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, clut); finalBuf = tmpTexBuf16.data(); } } @@ -1144,6 +1149,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 ERROR_LOG(G3D, "Unknown CLUT4 texture mode %d", (gstate.clutformat & 3)); return NULL; } + } break; case GE_TFMT_CLUT8: From 5223ee3d1b88a86b96fd5c54d77b7ea952de4a32 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 12 May 2013 09:15:31 -0700 Subject: [PATCH 2/6] Move the font clut opt check to clut load. And remove the report for mipmap sharing, seems to work... --- GPU/GLES/TextureCache.cpp | 52 ++++++++++++++++++--------------------- GPU/GLES/TextureCache.h | 3 +++ 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 977a3e40a..9a87f3393 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -782,6 +782,26 @@ void TextureCache::UpdateCurrentClut() { Memory::Memcpy((u8 *)clutBuf_, clutAddr, clutTotalBytes); convertColors((u8 *)clutBuf_, getClutDestFormat(clutFormat), clutTotalBytes / clutColorBytes); clutHash_ = CityHash32((const char *)clutBuf_, clutTotalBytes); + + // Special optimization: fonts typically draw clut4 with just alpha values in a single color. + clutAlphaLinear_ = false; + clutAlphaLinearColor_ = 0; + if (gstate.clutformat == (0xC500FF00 | GE_CMODE_16BIT_ABGR4444)) { + const u16 *clut = GetCurrentClut(); + clutAlphaLinear_ = true; + clutAlphaLinearColor_ = clut[15] & 0xFFF0; + for (int i = 0; i < 16; ++i) { + if ((clut[i] & 0xf) != i) { + clutAlphaLinear_ = false; + break; + } + // Alpha 0 doesn't matter. + if (i != 0 && (clut[i] & 0xFFF0) != clutAlphaLinearColor_) { + clutAlphaLinear_ = false; + break; + } + } + } } else { memset(clutBuf_, 0xFF, clutTotalBytes); clutHash_ = 0; @@ -1074,9 +1094,6 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 const bool mipmapShareClut = (gstate.texmode & 0x100) == 0; const int clutSharingOffset = mipmapShareClut ? 0 : level * 16; - if (mipmapShareClut) { - WARN_LOG_REPORT_ONCE(mipMapShareClut4, G3D, "Untested: mipmaps using separate cluts."); - } switch (clutformat) { case GE_CMODE_16BIT_BGR5650: @@ -1087,38 +1104,17 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 tmpTexBufRearrange.resize(std::max(bufw, w) * h); const u16 *clut = GetCurrentClut() + clutSharingOffset; texByteAlign = 2; - - // Special optimization: fonts typically draw clut4 with just alpha values in a single color. - bool linearClut = false; - u16 linearColor = 0; - if (gstate.clutformat == (0xC500FF00 | GE_CMODE_16BIT_ABGR4444)) { - // TODO: Do this check once per CLUT load? - linearClut = true; - linearColor = clut[15] & 0xFFF0; - for (int i = 0; i < 16; ++i) { - if ((clut[i] & 0xf) != i) { - linearClut = false; - break; - } - // Alpha 0 doesn't matter. - if (i != 0 && (clut[i] & 0xFFF0) != linearColor) { - linearClut = false; - break; - } - } - } - if (!(gstate.texmode & 1)) { - if (linearClut) { - DeIndexTexture4Optimal(tmpTexBuf16.data(), texaddr, bufw * h, linearColor); + if (clutAlphaLinear_ && mipmapShareClut) { + DeIndexTexture4Optimal(tmpTexBuf16.data(), texaddr, bufw * h, clutAlphaLinearColor_); } else { DeIndexTexture4(tmpTexBuf16.data(), texaddr, bufw * h, clut); } } else { tmpTexBuf32.resize(std::max(bufw, w) * h); UnswizzleFromMem(texaddr, bufw, 0, level); - if (linearClut) { - DeIndexTexture4Optimal(tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, linearColor); + if (clutAlphaLinear_ && mipmapShareClut) { + DeIndexTexture4Optimal(tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, clutAlphaLinearColor_); } else { DeIndexTexture4(tmpTexBuf16.data(), (u8 *)tmpTexBuf32.data(), bufw * h, clut); } diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index ddbd33ca6..014aaf100 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -129,6 +129,9 @@ private: u32 *clutBuf_; u32 clutHash_; + // True if the clut is just alpha values in the same order (RGBA4444-bit only.) + bool clutAlphaLinear_; + u16 clutAlphaLinearColor_; u32 lastBoundTexture; float maxAnisotropyLevel; From 5ecacd6bc8f09a51c12bcb7748e2c7f119dc07b2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 12 May 2013 09:31:23 -0700 Subject: [PATCH 3/6] Don't double alpha when alphablend is disabled. Probably doesn't affect much. --- GPU/GLES/FragmentShaderGenerator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index 7686992ab..018154479 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -73,6 +73,10 @@ static bool IsColorTestTriviallyTrue() { } static bool CanDoubleSrcBlendMode() { + if (!gstate.isAlphaBlendEnabled()) { + return false; + } + int funcA = gstate.getBlendFuncA(); int funcB = gstate.getBlendFuncB(); if (funcA != GE_SRCBLEND_DOUBLESRCALPHA) { From 39c0e6c096b30714632853b698a5936807b826fd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 12 May 2013 10:15:13 -0700 Subject: [PATCH 4/6] Add reporting for unsupported GE commands. And clean up some disasm for a couple others. --- GPU/GLES/DisplayListInterpreter.cpp | 27 +++++++++++++++++++++++++++ GPU/GeDisasm.cpp | 15 +++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index 33696f8f0..58be98a3e 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -938,6 +938,33 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { } break; +#ifndef USING_GLES2 + case GE_CMD_LOGICOPENABLE: + if (data != 0) + ERROR_LOG_REPORT(G3D, "Unsupported logic op enabled: %x", data); + break; + + case GE_CMD_LOGICOP: + if (data != 0) + ERROR_LOG_REPORT(G3D, "Unsupported logic op: %06x", data); + break; + + case GE_CMD_ANTIALIASENABLE: + if (data != 0) + WARN_LOG_REPORT(G3D, "Unsupported antialias enabled: %06x", data); + break; + + case GE_CMD_TEXLODSLOPE: + if (data != 0) + WARN_LOG_REPORT(G3D, "Unsupported texture lod slope: %06x", data); + break; + + case GE_CMD_TEXLEVEL: + if (data != 0) + WARN_LOG_REPORT(G3D, "Unsupported texture level bias settings: %06x", data); + break; +#endif + default: GPUCommon::ExecuteOp(op, diff); break; diff --git a/GPU/GeDisasm.cpp b/GPU/GeDisasm.cpp index 390ee9e51..cda5ff918 100644 --- a/GPU/GeDisasm.cpp +++ b/GPU/GeDisasm.cpp @@ -473,8 +473,8 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer) { case GE_CMD_TRANSFERSRCPOS: { - u32 x = (data & 1023)+1; - u32 y = ((data>>10) & 1023)+1; + u32 x = (data & 1023); + u32 y = ((data>>10) & 1023); if (data & 0xF00000) sprintf(buffer, "Block transfer src rect TL: %i, %i (extra %x)", x, y, data >> 20); else @@ -484,8 +484,8 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer) { case GE_CMD_TRANSFERDSTPOS: { - u32 x = (data & 1023)+1; - u32 y = ((data>>10) & 1023)+1; + u32 x = (data & 1023); + u32 y = ((data>>10) & 1023); if (data & 0xF00000) sprintf(buffer, "Block transfer dest rect TL: %i, %i (extra %x)", x, y, data >> 20); else @@ -849,7 +849,7 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer) { break; case GE_CMD_TEXMODE: - sprintf(buffer, "TexMode %06x (%s)", data, data & 1 ? "swizzle" : "no swizzle"); + sprintf(buffer, "TexMode %06x (%s, %d levels, %s)", data, data & 1 ? "swizzle" : "no swizzle", (data >> 16) & 7, (data >> 8) & 1 ? "separate cluts" : "shared clut"); break; case GE_CMD_TEXFORMAT: @@ -929,7 +929,10 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer) { break; case GE_CMD_STENCILOP: - sprintf(buffer, "Stencil op: %06x", data); + { + const char *stencilOps[] = { "KEEP", "ZERO", "REPLACE", "INVERT", "INCREMENT", "DECREMENT", "unsupported1", "unsupported2" }; + sprintf(buffer, "Stencil op: fail=%s, pass/depthfail=%s, pass=%s", stencilOps[data & 7], stencilOps[(data >> 8) & 7], stencilOps[(data >> 16) & 7]); + } break; case GE_CMD_STENCILTEST: From 5619c84432839e858997e24b9a6b1333e5e1eb16 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 12 May 2013 10:57:41 -0700 Subject: [PATCH 5/6] Defer palette conversion after clut load. Because the format can easily be specified afterward. --- GPU/GLES/DisplayListInterpreter.cpp | 2 +- GPU/GLES/TextureCache.cpp | 64 +++++++++++++++++------------ GPU/GLES/TextureCache.h | 4 +- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index 58be98a3e..fae1445eb 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -583,7 +583,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { case GE_CMD_LOADCLUT: gstate_c.textureChanged = true; - textureCache_.UpdateCurrentClut(); + textureCache_.LoadClut(); // This could be used to "dirty" textures with clut. break; diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 9a87f3393..943d4ffea 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -57,7 +57,7 @@ static inline u32 GetLevelBufw(int level, u32 texaddr) { return gstate.texbufwidth[level] & 0x7FF; } -TextureCache::TextureCache() : clearCacheNextFrame_(false), lowMemoryMode_(false) { +TextureCache::TextureCache() : clearCacheNextFrame_(false), lowMemoryMode_(false), clutDirty_(false) { lastBoundTexture = -1; // This is 5MB of temporary storage. Might be possible to shrink it. tmpTexBuf32.resize(1024 * 512); // 2MB @@ -184,7 +184,7 @@ void TextureCache::NotifyFramebufferDestroyed(u32 address, VirtualFramebuffer *f } } -static u32 GetClutAddr(u32 clutEntrySize) { +static u32 GetClutAddr() { return ((gstate.clutaddr & 0xFFFFFF) | ((gstate.clutaddrupper << 8) & 0x0F000000)); } @@ -773,39 +773,44 @@ inline bool TextureCache::TexCacheEntry::MatchesClut(bool hasClut, u8 clutformat return clutformat == clutformat2; } -void TextureCache::UpdateCurrentClut() { - GEPaletteFormat clutFormat = (GEPaletteFormat)(gstate.clutformat & 3); - const u32 clutColorBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? 4 : 2; - u32 clutAddr = GetClutAddr(clutFormat == GE_CMODE_32BIT_ABGR8888 ? 4 : 2); +void TextureCache::LoadClut() { + u32 clutAddr = GetClutAddr(); u32 clutTotalBytes = (gstate.loadclut & 0x3f) * 32; if (Memory::IsValidAddress(clutAddr)) { Memory::Memcpy((u8 *)clutBuf_, clutAddr, clutTotalBytes); - convertColors((u8 *)clutBuf_, getClutDestFormat(clutFormat), clutTotalBytes / clutColorBytes); clutHash_ = CityHash32((const char *)clutBuf_, clutTotalBytes); - - // Special optimization: fonts typically draw clut4 with just alpha values in a single color. - clutAlphaLinear_ = false; - clutAlphaLinearColor_ = 0; - if (gstate.clutformat == (0xC500FF00 | GE_CMODE_16BIT_ABGR4444)) { - const u16 *clut = GetCurrentClut(); - clutAlphaLinear_ = true; - clutAlphaLinearColor_ = clut[15] & 0xFFF0; - for (int i = 0; i < 16; ++i) { - if ((clut[i] & 0xf) != i) { - clutAlphaLinear_ = false; - break; - } - // Alpha 0 doesn't matter. - if (i != 0 && (clut[i] & 0xFFF0) != clutAlphaLinearColor_) { - clutAlphaLinear_ = false; - break; - } - } - } } else { memset(clutBuf_, 0xFF, clutTotalBytes); clutHash_ = 0; } + clutDirty_ = true; +} + +void TextureCache::UpdateCurrentClut() { + GEPaletteFormat clutFormat = (GEPaletteFormat)(gstate.clutformat & 3); + const u32 clutColorBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? 4 : 2; + u32 clutTotalBytes = (gstate.loadclut & 0x3f) * 32; + convertColors((u8 *)clutBuf_, getClutDestFormat(clutFormat), clutTotalBytes / clutColorBytes); + + // Special optimization: fonts typically draw clut4 with just alpha values in a single color. + clutAlphaLinear_ = false; + clutAlphaLinearColor_ = 0; + if (gstate.clutformat == (0xC500FF00 | GE_CMODE_16BIT_ABGR4444)) { + const u16 *clut = GetCurrentClut(); + clutAlphaLinear_ = true; + clutAlphaLinearColor_ = clut[15] & 0xFFF0; + for (int i = 0; i < 16; ++i) { + if ((clut[i] & 0xf) != i) { + clutAlphaLinear_ = false; + break; + } + // Alpha 0 doesn't matter. + if (i != 0 && (clut[i] & 0xFFF0) != clutAlphaLinearColor_) { + clutAlphaLinear_ = false; + break; + } + } + } } template @@ -837,6 +842,11 @@ void TextureCache::SetTexture() { u32 clutformat, cluthash; if (hasClut) { + if (clutDirty_) { + // We update here because the clut format can be specified after the load. + UpdateCurrentClut(); + clutDirty_ = false; + } clutformat = gstate.clutformat & 3; cluthash = GetCurrentClutHash(); cachekey |= (u64)cluthash << 32; diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 014aaf100..387598283 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -38,7 +38,7 @@ public: void Invalidate(u32 addr, int size, GPUInvalidationType type); void InvalidateAll(GPUInvalidationType type); void ClearNextFrame(); - void UpdateCurrentClut(); + void LoadClut(); // FramebufferManager keeps TextureCache updated about what regions of memory // are being rendered to. This is barebones so far. @@ -111,6 +111,7 @@ private: template const T *GetCurrentClut(); u32 GetCurrentClutHash(); + void UpdateCurrentClut(); TexCacheEntry *GetEntryAt(u32 texaddr); @@ -127,6 +128,7 @@ private: SimpleBuf tmpTexBufRearrange; + bool clutDirty_; u32 *clutBuf_; u32 clutHash_; // True if the clut is just alpha values in the same order (RGBA4444-bit only.) From 41fb41afdb98fda6c9b8c5d90267b5239914a831 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 12 May 2013 12:00:21 -0700 Subject: [PATCH 6/6] Only report these once to avoid spam. --- GPU/GLES/DisplayListInterpreter.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index fae1445eb..4e7e7f64c 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -941,27 +941,29 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { #ifndef USING_GLES2 case GE_CMD_LOGICOPENABLE: if (data != 0) - ERROR_LOG_REPORT(G3D, "Unsupported logic op enabled: %x", data); + ERROR_LOG_REPORT_ONCE(logicOpEnable, G3D, "Unsupported logic op enabled: %x", data); break; case GE_CMD_LOGICOP: if (data != 0) - ERROR_LOG_REPORT(G3D, "Unsupported logic op: %06x", data); + ERROR_LOG_REPORT_ONCE(logicOp, G3D, "Unsupported logic op: %06x", data); break; case GE_CMD_ANTIALIASENABLE: if (data != 0) - WARN_LOG_REPORT(G3D, "Unsupported antialias enabled: %06x", data); + WARN_LOG_REPORT_ONCE(antiAlias, G3D, "Unsupported antialias enabled: %06x", data); break; case GE_CMD_TEXLODSLOPE: if (data != 0) - WARN_LOG_REPORT(G3D, "Unsupported texture lod slope: %06x", data); + WARN_LOG_REPORT_ONCE(texLodSlope, G3D, "Unsupported texture lod slope: %06x", data); break; case GE_CMD_TEXLEVEL: - if (data != 0) - WARN_LOG_REPORT(G3D, "Unsupported texture level bias settings: %06x", data); + if (data == 1) + WARN_LOG_REPORT_ONCE(texLevel1, G3D, "Unsupported texture level bias settings: %06x", data) + else if (data != 0) + WARN_LOG_REPORT_ONCE(texLevel2, G3D, "Unsupported texture level bias settings: %06x", data); break; #endif