#include #include #include #include "OpenGL.h" #include "Textures.h" #include "GBI.h" #include "RSP.h" #include "gDP.h" #include "gSP.h" #include "N64.h" #include "convert.h" #include "FrameBuffer.h" #include "Config.h" #include "GLideNHQ/Ext_TxFilter.h" using namespace std; typedef u32 (*GetTexelFunc)( u64 *src, u16 x, u16 i, u8 palette ); inline u32 GetNone( u64 *src, u16 x, u16 i, u8 palette ) { return 0x00000000; } inline u32 GetCI4IA_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; if (x & 1) return IA88_RGBA4444( *(u16*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] ); else return IA88_RGBA4444( *(u16*)&TMEM[256 + (palette << 4) + (color4B >> 4)] ); } inline u32 GetCI4IA_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; if (x & 1) return IA88_RGBA8888( *(u16*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] ); else return IA88_RGBA8888( *(u16*)&TMEM[256 + (palette << 4) + (color4B >> 4)] ); } inline u32 GetCI4RGBA_RGBA5551( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; if (x & 1) return RGBA5551_RGBA5551( *(u16*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] ); else return RGBA5551_RGBA5551( *(u16*)&TMEM[256 + (palette << 4) + (color4B >> 4)] ); } inline u32 GetCI4RGBA_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; if (x & 1) return RGBA5551_RGBA8888( *(u16*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] ); else return RGBA5551_RGBA8888( *(u16*)&TMEM[256 + (palette << 4) + (color4B >> 4)] ); } inline u32 GetIA31_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; return IA31_RGBA8888( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) ); } inline u32 GetIA31_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; return IA31_RGBA4444( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) ); } inline u32 GetI4_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; return I4_RGBA8888( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) ); } inline u32 GetI4_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { u8 color4B; color4B = ((u8*)src)[(x>>1)^(i<<1)]; return I4_RGBA4444( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) ); } inline u32 GetCI8IA_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { return IA88_RGBA4444( *(u16*)&TMEM[256 + ((u8*)src)[x^(i<<1)]] ); } inline u32 GetCI8IA_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { return IA88_RGBA8888( *(u16*)&TMEM[256 + ((u8*)src)[x^(i<<1)]] ); } inline u32 GetCI8RGBA_RGBA5551( u64 *src, u16 x, u16 i, u8 palette ) { return RGBA5551_RGBA5551( *(u16*)&TMEM[256 + ((u8*)src)[x^(i<<1)]] ); } inline u32 GetCI8RGBA_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { return RGBA5551_RGBA8888( *(u16*)&TMEM[256 + ((u8*)src)[x^(i<<1)]] ); } inline u32 GetIA44_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { return IA44_RGBA8888(((u8*)src)[x^(i<<1)]); } inline u32 GetIA44_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { return IA44_RGBA4444(((u8*)src)[x^(i<<1)]); } inline u32 GetI8_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { return I8_RGBA8888(((u8*)src)[x^(i<<1)]); } inline u32 GetI8_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { return I8_RGBA4444(((u8*)src)[x^(i<<1)]); } inline u32 GetRGBA5551_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { u16 tex = ((u16*)src)[x^i]; switch (gDP.otherMode.textureLUT) { case G_TT_NONE: return RGBA5551_RGBA8888(tex); case G_TT_RGBA16: return RGBA5551_RGBA8888(*(u16*)&TMEM[256 + (tex>>8)]); case G_TT_IA16: tex = (*(u16*)&TMEM[256 + (tex >> 8)])>>8; return (tex<<24)|(tex<<16)|(tex<<8)|tex; } return RGBA5551_RGBA8888(tex); } inline u32 GetRGBA5551_RGBA5551( u64 *src, u16 x, u16 i, u8 palette ) { u16 tex = ((u16*)src)[x^i]; switch (gDP.otherMode.textureLUT) { case G_TT_NONE: return RGBA5551_RGBA5551(tex); case G_TT_RGBA16: return RGBA5551_RGBA5551(*(u16*)&TMEM[256 + (tex >> 8)]); case G_TT_IA16: tex = (*(u16*)&TMEM[256 + (tex >> 8)]) >> 11; return (1 << 15) | (tex << 10) | (tex << 5) | tex; } return RGBA5551_RGBA5551(tex); } inline u32 GetIA88_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { return IA88_RGBA8888(((u16*)src)[x^i]); } inline u32 GetIA88_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { return IA88_RGBA4444(((u16*)src)[x^i]); } inline u32 GetRGBA8888_RGBA8888( u64 *src, u16 x, u16 i, u8 palette ) { return ((u32*)src)[x^i]; } inline u32 GetRGBA8888_RGBA4444( u64 *src, u16 x, u16 i, u8 palette ) { return RGBA8888_RGBA4444(((u32*)src)[x^i]); } u32 YUV_RGBA8888(u8 y, u8 u, u8 v) { s32 r = (s32)(y + (1.370705f * (v - 128))); s32 g = (s32)((y - (0.698001f * (v - 128)) - (0.337633f * (u - 128)))); s32 b = (s32)(y + (1.732446f * (u - 128))); //clipping the result if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; return (0xff << 24) | (b << 16) | (g << 8) | r; } u16 YUV_RGBA4444(u8 y, u8 u, u8 v) { return RGBA8888_RGBA4444(YUV_RGBA8888(y, u, v)); } inline void GetYUV_RGBA8888(u64 * src, u32 * dst, u16 x) { const u32 t = (((u32*)src)[x]); u8 y1 = (u8)t & 0xFF; u8 v = (u8)(t >> 8) & 0xFF; u8 y0 = (u8)(t >> 16) & 0xFF; u8 u = (u8)(t >> 24) & 0xFF; u32 c = YUV_RGBA8888(y0, u, v); *(dst++) = c; c = YUV_RGBA8888(y1, u, v); *(dst++) = c; } inline void GetYUV_RGBA4444(u64 * src, u16 * dst, u16 x) { const u32 t = (((u32*)src)[x]); u8 y1 = (u8)t & 0xFF; u8 v = (u8)(t >> 8) & 0xFF; u8 y0 = (u8)(t >> 16) & 0xFF; u8 u = (u8)(t >> 24) & 0xFF; u16 c = YUV_RGBA4444(y0, u, v); *(dst++) = c; c = YUV_RGBA4444(y1, u, v); *(dst++) = c; } const struct { GetTexelFunc Get16; GLenum glType16; GLint glInternalFormat16; GetTexelFunc Get32; GLenum glType32; GLint glInternalFormat32; u32 autoFormat, lineShift, maxTexels; } imageFormat[4][5] = { // Get16 glType16 glInternalFormat16 Get32 glType32 glInternalFormat32 autoFormat { // 4-bit { GetCI4RGBA_RGBA5551, GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1, GetCI4RGBA_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 4, 4096 }, // CI (Banjo-Kazooie uses this, doesn't make sense, but it works...) { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // YUV { GetCI4RGBA_RGBA5551, GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1, GetCI4RGBA_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 4, 4096 }, // CI { GetIA31_RGBA4444, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetIA31_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // IA { GetI4_RGBA4444, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetI4_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // I }, { // 8-bit { GetCI8RGBA_RGBA5551, GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1, GetCI8RGBA_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // RGBA { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 4096 }, // YUV { GetCI8RGBA_RGBA5551, GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1, GetCI8RGBA_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // CI { GetIA44_RGBA4444, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetIA44_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 3, 4096 }, // IA { GetI8_RGBA4444, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetI8_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 3, 4096 }, // I }, { // 16-bit { GetRGBA5551_RGBA5551, GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1, GetRGBA5551_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 2, 2048 }, // RGBA { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 2, 2048 }, // YUV { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 2048 }, // CI { GetIA88_RGBA4444, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetIA88_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 2, 2048 }, // IA { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 2048 }, // I }, { // 32-bit { GetRGBA8888_RGBA4444, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetRGBA8888_RGBA8888, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 2, 1024 }, // RGBA { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // YUV { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // CI { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // IA { GetNone, GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, GetNone, GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // I } }; /** cite from RiceVideo */ inline u32 CalculateDXT(u32 txl2words) { if (txl2words == 0) return 1; else return (2048 + txl2words - 1) / txl2words; } u32 sizeBytes[4] = {0, 1, 2, 4}; inline u32 Txl2Words(u32 width, u32 size) { if (size == 0) return max(1, width / 16); else return max(1, width*sizeBytes[size] / 8); } inline u32 ReverseDXT(u32 val, u32 lrs, u32 width, u32 size) { if (val == 0x800) return 1; int low = 2047 / val; if (CalculateDXT(low) > val) low++; int high = 2047 / (val - 1); if (low == high) return low; for (int i = low; i <= high; i++) { if (Txl2Words(width, size) == (u32)i) return i; } return (low + high) / 2; } /** end RiceVideo cite */ void TextureCache::init() { u32 dummyTexture[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; m_bitDepth = config.texture.textureBitDepth; m_maxBytes = config.texture.maxBytes; m_pDummy = addFrameBufferTexture(); // we don't want to remove dummy texture m_pDummy->address = 0; m_pDummy->clampS = 1; m_pDummy->clampT = 1; m_pDummy->clampWidth = 2; m_pDummy->clampHeight = 2; m_pDummy->crc = 0; m_pDummy->format = 0; m_pDummy->size = 0; m_pDummy->frameBufferTexture = FALSE; m_pDummy->width = 2; m_pDummy->height = 2; m_pDummy->realWidth = 0; m_pDummy->realHeight = 0; m_pDummy->maskS = 0; m_pDummy->maskT = 0; m_pDummy->scaleS = 0.5f; m_pDummy->scaleT = 0.5f; m_pDummy->shiftScaleS = 1.0f; m_pDummy->shiftScaleT = 1.0f; m_pDummy->textureBytes = 64; m_pDummy->tMem = 0; glBindTexture( GL_TEXTURE_2D, m_pDummy->glName ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, dummyTexture ); m_cachedBytes = m_pDummy->textureBytes; activateDummy( 0 ); activateDummy( 1 ); current[0] = current[1] = NULL; } void TextureCache::destroy() { current[0] = current[1] = NULL; for (Textures::const_iterator cur = m_textures.cbegin(); cur != m_textures.cend(); ++cur) glDeleteTextures( 1, &cur->second.glName ); m_textures.clear(); for (Textures::const_iterator cur = m_fbTextures.cbegin(); cur != m_fbTextures.cend(); ++cur) glDeleteTextures( 1, &cur->second.glName ); m_fbTextures.clear(); m_cachedBytes = 0; } void TextureCache::_checkCacheSize() { if (m_cachedBytes <= m_maxBytes) return; Textures::const_iterator iter = m_textures.cend(); do { --iter; m_cachedBytes -= iter->second.textureBytes; glDeleteTextures( 1, &iter->second.glName ); } while (m_cachedBytes > m_maxBytes && iter != m_textures.cbegin()); m_textures.erase(iter, m_textures.cend()); } CachedTexture * TextureCache::_addTexture(u32 _crc32) { _checkCacheSize(); GLuint glName; glGenTextures(1, &glName); m_textures.emplace(_crc32, glName); CachedTexture & texture = m_textures.at(_crc32); texture.crc = _crc32; return &texture; } void TextureCache::removeFrameBufferTexture(CachedTexture * _pTexture) { Textures::const_iterator iter = m_fbTextures.find(_pTexture->glName); assert(iter != m_fbTextures.end()); m_cachedBytes -= iter->second.textureBytes; glDeleteTextures( 1, &iter->second.glName ); m_fbTextures.erase(iter); } CachedTexture * TextureCache::addFrameBufferTexture() { _checkCacheSize(); GLuint glName; glGenTextures(1, &glName); m_fbTextures.emplace(glName, glName); return &m_fbTextures.at(glName); } struct TileSizes { u32 maskWidth, clampWidth, width, realWidth; u32 maskHeight, clampHeight, height, realHeight; }; static void _calcTileSizes(u32 _t, TileSizes & _sizes, gDPTile * _pLoadTile) { gDPTile * pTile = _t < 2 ? gSP.textureTile[_t] : &gDP.tiles[_t]; const u32 maxTexels = imageFormat[pTile->size][pTile->format].maxTexels; const u32 tileWidth = (pTile->lrs - pTile->uls + 1) & 0x03FF; const u32 tileHeight = (pTile->lrt - pTile->ult + 1) & 0x03FF; const bool bUseLoadSizes = _pLoadTile != NULL && _pLoadTile->loadType == LOADTYPE_TILE && (pTile->tmem == _pLoadTile->tmem); u32 loadWidth = 0, loadHeight = 0; if (bUseLoadSizes) { loadWidth = (_pLoadTile->lrs - _pLoadTile->uls + 1) & 0x03FF; loadHeight = (_pLoadTile->lrt - _pLoadTile->ult + 1) & 0x03FF; } const u32 lineWidth = pTile->line << imageFormat[pTile->size][pTile->format].lineShift; const u32 lineHeight = lineWidth != 0 ? min(maxTexels / lineWidth, tileHeight) : 0; u32 maskWidth = 1 << pTile->masks; u32 maskHeight = 1 << pTile->maskt; u32 width, height; #if 0 if (pTile->textureMode == TEXTUREMODE_TEXRECT) { u16 texRectWidth = gDP.texRect.width - pTile->uls; u16 texRectHeight = gDP.texRect.height - pTile->ult; if (pTile->masks && ((maskWidth * maskHeight) <= maxTexels)) width = maskWidth; else if (bUseLoadSizes) { width = loadWidth; if (pTile->size < gDP.loadTile->size) width <<= (gDP.loadTile->size - pTile->size); } else if ((tileWidth * tileHeight) <= maxTexels) width = tileWidth; else if ((tileWidth * texRectHeight) <= maxTexels) width = tileWidth; else if ((texRectWidth * tileHeight) <= maxTexels) width = gDP.texRect.width; else if ((texRectWidth * texRectHeight) <= maxTexels) width = gDP.texRect.width; else width = lineWidth; if (pTile->maskt && ((maskWidth * maskHeight) <= maxTexels)) height = maskHeight; else if (bUseLoadSizes) height = loadHeight; else if ((tileWidth * tileHeight) <= maxTexels) height = tileHeight; else if ((tileWidth * texRectHeight) <= maxTexels) height = gDP.texRect.height; else if ((texRectWidth * tileHeight) <= maxTexels) height = tileHeight; else if ((texRectWidth * texRectHeight) <= maxTexels) height = gDP.texRect.height; else height = lineHeight; } else { if (pTile->masks && ((maskWidth * maskHeight) <= maxTexels)) width = maskWidth; // Use mask width if set and valid else if ((tileWidth * tileHeight) <= maxTexels) width = tileWidth; // else use tile width if valid else if (bUseLoadSizes) width = loadWidth; // else use load width if load done with LoadTile else width = lineWidth; // else use line-based width if (pTile->maskt && ((maskWidth * maskHeight) <= maxTexels)) height = maskHeight; else if ((tileWidth * tileHeight) <= maxTexels) height = tileHeight; else if (bUseLoadSizes) height = loadHeight; else height = lineHeight; } #else gDPLoadTileInfo &info = gDP.loadInfo[pTile->tmem]; if (info.loadType == LOADTYPE_TILE) { width = min(info.width, info.texWidth); if (info.size > pTile->size) width <<= info.size - pTile->size; height = info.height; } else { if (pTile->masks && ((maskWidth * maskHeight) <= maxTexels)) width = maskWidth; // Use mask width if set and valid else if ((tileWidth * tileHeight) <= maxTexels) width = tileWidth; // else use tile width if valid else width = lineWidth; // else use line-based width if (pTile->maskt && ((maskWidth * maskHeight) <= maxTexels)) height = maskHeight; else if ((tileWidth * tileHeight) <= maxTexels) height = tileHeight; else height = lineHeight; } #endif _sizes.clampWidth = (pTile->clamps && gDP.otherMode.cycleType != G_CYC_COPY) ? tileWidth : width; _sizes.clampHeight = (pTile->clampt && gDP.otherMode.cycleType != G_CYC_COPY) ? tileHeight : height; if (_sizes.clampWidth > 256) pTile->clamps = 0; if (_sizes.clampHeight > 256) pTile->clampt = 0; // Make sure masking is valid if (maskWidth > width) { pTile->masks = powof(width); maskWidth = 1 << pTile->masks; } if (maskHeight > height) { pTile->maskt = powof(height); maskHeight = 1 << pTile->maskt; } _sizes.maskWidth = maskWidth; _sizes.maskHeight = maskHeight; _sizes.width = width; _sizes.height = height; if (pTile->clamps != 0) _sizes.realWidth = _sizes.clampWidth; else if (pTile->mirrors + pTile->masks != 0) _sizes.realWidth = _sizes.maskWidth; else _sizes.realWidth = _sizes.width; if (pTile->clampt != 0) _sizes.realHeight = _sizes.clampHeight; else if (pTile->mirrort + pTile->maskt != 0) _sizes.realHeight = _sizes.maskHeight; else _sizes.realHeight = _sizes.height; if (gSP.texture.level > gSP.texture.tile) { _sizes.realWidth = pow2(_sizes.realWidth); _sizes.realHeight = pow2(_sizes.realHeight); } } void TextureCache::_loadBackground(CachedTexture *pTexture) { u32 *pDest; u8 *pSwapped, *pSrc; u32 numBytes, bpl; u32 x, y, j, tx, ty; u16 clampSClamp; u16 clampTClamp; GetTexelFunc GetTexel; GLuint glInternalFormat; GLenum glType; if (((imageFormat[pTexture->size][pTexture->format].autoFormat == GL_RGBA) || ((pTexture->format == G_IM_FMT_CI) && (gDP.otherMode.textureLUT == G_TT_IA16)) || (m_bitDepth == 2)) && (m_bitDepth != 0)) { pTexture->textureBytes = (pTexture->realWidth * pTexture->realHeight) << 2; if ((pTexture->format == G_IM_FMT_CI) && (gDP.otherMode.textureLUT == G_TT_IA16)) { if (pTexture->size == G_IM_SIZ_4b) GetTexel = GetCI4IA_RGBA8888; else GetTexel = GetCI8IA_RGBA8888; glInternalFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE; } else { GetTexel = imageFormat[pTexture->size][pTexture->format].Get32; glInternalFormat = imageFormat[pTexture->size][pTexture->format].glInternalFormat32; glType = imageFormat[pTexture->size][pTexture->format].glType32; } } else { pTexture->textureBytes = (pTexture->realWidth * pTexture->realHeight) << 1; if ((pTexture->format == G_IM_FMT_CI) && (gDP.otherMode.textureLUT == G_TT_IA16)) { if (pTexture->size == G_IM_SIZ_4b) GetTexel = GetCI4IA_RGBA4444; else GetTexel = GetCI8IA_RGBA4444; glInternalFormat = GL_RGBA4; glType = GL_UNSIGNED_SHORT_4_4_4_4; } else { GetTexel = imageFormat[pTexture->size][pTexture->format].Get16; glInternalFormat = imageFormat[pTexture->size][pTexture->format].glInternalFormat16; glType = imageFormat[pTexture->size][pTexture->format].glType16; } } bpl = gSP.bgImage.width << gSP.bgImage.size >> 1; numBytes = bpl * gSP.bgImage.height; pSwapped = (u8*)malloc( numBytes ); UnswapCopy( &RDRAM[gSP.bgImage.address], pSwapped, numBytes ); pDest = (u32*)malloc( pTexture->textureBytes ); clampSClamp = pTexture->width - 1; clampTClamp = pTexture->height - 1; j = 0; for (y = 0; y < pTexture->realHeight; y++) { ty = min(y, (u32)clampTClamp); pSrc = &pSwapped[bpl * ty]; for (x = 0; x < pTexture->realWidth; x++) { tx = min(x, (u32)clampSClamp); if (glInternalFormat == GL_RGBA) ((u32*)pDest)[j++] = GetTexel( (u64*)pSrc, tx, 0, pTexture->palette ); else ((u16*)pDest)[j++] = GetTexel( (u64*)pSrc, tx, 0, pTexture->palette ); } } GLint curUnpackAlignment; glGetIntegerv(GL_UNPACK_ALIGNMENT, &curUnpackAlignment); if (pTexture->realWidth % 2 != 0 && glInternalFormat != GL_RGBA) glPixelStorei(GL_UNPACK_ALIGNMENT, 2); glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, pTexture->realWidth, pTexture->realHeight, 0, GL_RGBA, glType, pDest); glPixelStorei(GL_UNPACK_ALIGNMENT, curUnpackAlignment); free(pDest); } inline void _updateCachedTexture(const GHQTexInfo & _info, CachedTexture *_pTexture) { _pTexture->textureBytes = _info.width * _info.height; switch (_info.format) { case GL_RGB: case GL_RGBA4: case GL_RGB5_A1: case GL_LUMINANCE8_ALPHA8: _pTexture->textureBytes <<= 1; break; default: _pTexture->textureBytes <<= 2; } /* _pTexture->realWidth = _info.width; _pTexture->realHeight = _info.height; _pTexture->scaleS = 1.0f / (f32)(_pTexture->realWidth); _pTexture->scaleT = 1.0f / (f32)(_pTexture->realHeight); */ } void TextureCache::_load(u32 _tile , CachedTexture *_pTexture) { if (TFH.isInited()) { isGLError(); GHQTexInfo ghqTexInfo; gDPLoadTileInfo & info = gDP.loadInfo[gSP.textureTile[_tile]->tmem]; int bpl; u8 * addr = (u8*)(RDRAM + info.texAddress); int tile_width = _pTexture->width; int tile_height = _pTexture->height; if (info.loadType == LOADTYPE_TILE) { bpl = info.width << info.size >> 1; addr += (info.ult * bpl) + (((info.uls << info.size) + 1) >> 1); } else { if (gSP.textureTile[_tile]->size == G_IM_SIZ_32b) bpl = gSP.textureTile[_tile]->line << 4; else if (info.dxt == 0) bpl = gSP.textureTile[_tile]->line << 3; else { u32 dxt = info.dxt; if (dxt > 1) dxt = ReverseDXT(dxt, info.width, _pTexture->width, gSP.textureTile[_tile]->size); bpl = dxt << 3; } } u8 * paladdr = NULL; u16 * palette = NULL; if ((gSP.textureTile[_tile]->size < G_IM_SIZ_16b) && (gDP.otherMode.textureLUT != G_TT_NONE || gSP.textureTile[_tile]->format == G_IM_FMT_CI)) { if (gSP.textureTile[_tile]->size == G_IM_SIZ_8b) paladdr = (u8*)(gDP.TexFilterPalette); else if (config.textureFilter.txHresAltCRC) paladdr = (u8*)(gDP.TexFilterPalette + (gSP.textureTile[_tile]->palette << 5)); else paladdr = (u8*)(gDP.TexFilterPalette + (gSP.textureTile[_tile]->palette << 4)); // TODO: fix palette load // palette = (rdp.pal_8 + (gSP.textureTile[_t]->palette << 4)); } u64 ricecrc = txfilter_checksum(addr, tile_width, tile_height, (unsigned short)(gSP.textureTile[_tile]->format << 8 | gSP.textureTile[_tile]->size), bpl, paladdr); if (txfilter_hirestex(_pTexture->crc, ricecrc, palette, &ghqTexInfo)) { glTexImage2D(GL_TEXTURE_2D, 0, ghqTexInfo.format, ghqTexInfo.width, ghqTexInfo.height, 0, ghqTexInfo.texture_format, ghqTexInfo.pixel_type, ghqTexInfo.data); assert(!isGLError()); _updateCachedTexture(ghqTexInfo, _pTexture); return; } } u32 *pDest; u64 *pSrc; u16 x, y, i, j, tx, ty, line; u16 mirrorSBit, maskSMask, clampSClamp; u16 mirrorTBit, maskTMask, clampTClamp; GetTexelFunc GetTexel; GLuint glInternalFormat; GLenum glType; u32 sizeShift; if (((imageFormat[_pTexture->size][_pTexture->format].autoFormat == GL_RGBA) || ((_pTexture->format == G_IM_FMT_CI) && (gDP.otherMode.textureLUT == G_TT_IA16)) || (m_bitDepth == 2)) && (m_bitDepth != 0)) { sizeShift = 2; _pTexture->textureBytes = (_pTexture->realWidth * _pTexture->realHeight) << sizeShift; if ((_pTexture->format == G_IM_FMT_CI) && (gDP.otherMode.textureLUT == G_TT_IA16)) { if (_pTexture->size == G_IM_SIZ_4b) GetTexel = GetCI4IA_RGBA8888; else GetTexel = GetCI8IA_RGBA8888; glInternalFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE; } else { GetTexel = imageFormat[_pTexture->size][_pTexture->format].Get32; glInternalFormat = imageFormat[_pTexture->size][_pTexture->format].glInternalFormat32; glType = imageFormat[_pTexture->size][_pTexture->format].glType32; } } else { sizeShift = 1; _pTexture->textureBytes = (_pTexture->realWidth * _pTexture->realHeight) << sizeShift; if ((_pTexture->format == G_IM_FMT_CI) && (gDP.otherMode.textureLUT == G_TT_IA16)) { if (_pTexture->size == G_IM_SIZ_4b) GetTexel = GetCI4IA_RGBA4444; else GetTexel = GetCI8IA_RGBA4444; glInternalFormat = GL_RGBA4; glType = GL_UNSIGNED_SHORT_4_4_4_4; } else { GetTexel = imageFormat[_pTexture->size][_pTexture->format].Get16; glInternalFormat = imageFormat[_pTexture->size][_pTexture->format].glInternalFormat16; glType = imageFormat[_pTexture->size][_pTexture->format].glType16; } } pDest = (u32*)malloc( _pTexture->textureBytes ); GLint mipLevel = 0, maxLevel = 0; if (gSP.texture.level > gSP.texture.tile + 1) maxLevel = _tile == 0 ? 0 : gSP.texture.level - gSP.texture.tile - 1; _pTexture->max_level = maxLevel; CachedTexture tmptex(0); memcpy(&tmptex, _pTexture, sizeof(CachedTexture)); line = tmptex.line; GLint curUnpackAlignment; glGetIntegerv(GL_UNPACK_ALIGNMENT, &curUnpackAlignment); while (true) { if (tmptex.maskS > 0) { clampSClamp = tmptex.clampS ? tmptex.clampWidth - 1 : (tmptex.mirrorS ? (tmptex.width << 1) - 1 : tmptex.width - 1); maskSMask = (1 << tmptex.maskS) - 1; mirrorSBit = tmptex.mirrorS ? 1 << tmptex.maskS : 0; } else { clampSClamp = min(tmptex.clampWidth, tmptex.width) - 1; maskSMask = 0xFFFF; mirrorSBit = 0x0000; } if (tmptex.maskT > 0) { clampTClamp = tmptex.clampT ? tmptex.clampHeight - 1 : (tmptex.mirrorT ? (tmptex.height << 1) - 1: tmptex.height - 1); maskTMask = (1 << tmptex.maskT) - 1; mirrorTBit = tmptex.mirrorT ? 1 << tmptex.maskT : 0; } else { clampTClamp = min( tmptex.clampHeight, tmptex.height ) - 1; maskTMask = 0xFFFF; mirrorTBit = 0x0000; } // Hack for Zelda warp texture if (((tmptex.tMem << 3) + (tmptex.width * tmptex.height << tmptex.size >> 1)) > 4096) tmptex.tMem = 0; if (tmptex.size == G_IM_SIZ_32b) { const u16 * tmem16 = (u16*)TMEM; const u32 tbase = tmptex.tMem << 2; u32 width = (tmptex.clampWidth) << 2; if (width & 15) width += 16; width &= 0xFFFFFFF0; width >>= 2; u16 gr, ab; j = 0; for (y = 0; y < tmptex.realHeight; ++y) { ty = min(y, clampTClamp) & maskTMask; if (y & mirrorTBit) ty ^= maskTMask; u32 tline = tbase + width * ty; u32 xorval = (ty & 1) ? 3 : 1; for (x = 0; x < tmptex.realWidth; ++x) { tx = min(x, clampSClamp) & maskSMask; if (x & mirrorSBit) tx ^= maskSMask; u32 taddr = ((tline + tx) ^ xorval) & 0x3ff; gr = swapword(tmem16[taddr]); ab = swapword(tmem16[taddr | 0x400]); pDest[j++] = (ab << 16) | gr; } } } else if (tmptex.format == G_IM_FMT_YUV) { j = 0; line <<= 1; for (y = 0; y < tmptex.realHeight; ++y) { pSrc = &TMEM[tmptex.tMem] + line * y; for (x = 0; x < tmptex.realWidth / 2; x++) { if (glInternalFormat == GL_RGBA) GetYUV_RGBA8888(pSrc, pDest + j, x); else GetYUV_RGBA4444(pSrc, (u16*)pDest + j, x); j += 2; } } } else { j = 0; for (y = 0; y < tmptex.realHeight; ++y) { ty = min(y, clampTClamp) & maskTMask; if (y & mirrorTBit) ty ^= maskTMask; pSrc = &TMEM[(tmptex.tMem + line * ty)&0x1FF]; i = (ty & 1) << 1; for (x = 0; x < tmptex.realWidth; ++x) { tx = min(x, clampSClamp) & maskSMask; if (x & mirrorSBit) tx ^= maskSMask; if (glInternalFormat == GL_RGBA) pDest[j++] = GetTexel(pSrc, tx, i, tmptex.palette); else ((u16*)pDest)[j++] = GetTexel(pSrc, tx, i, tmptex.palette); } } } bool bLoaded = false; if (TFH.isInited() && maxLevel == 0) { GHQTexInfo ghqTexInfo; if (txfilter_filter((u8*)pDest, tmptex.realWidth, tmptex.realHeight, glInternalFormat, (uint64)_pTexture->crc, &ghqTexInfo) != 0 && ghqTexInfo.data != NULL) { glTexImage2D(GL_TEXTURE_2D, 0, ghqTexInfo.format, ghqTexInfo.width, ghqTexInfo.height, 0, ghqTexInfo.texture_format, ghqTexInfo.pixel_type, ghqTexInfo.data); _updateCachedTexture(ghqTexInfo, _pTexture); bLoaded = true; } } if (!bLoaded) { if (tmptex.realWidth % 2 != 0 && glInternalFormat != GL_RGBA) glPixelStorei(GL_UNPACK_ALIGNMENT, 2); glTexImage2D(GL_TEXTURE_2D, mipLevel, glInternalFormat, tmptex.realWidth, tmptex.realHeight, 0, GL_RGBA, glType, pDest); } if (mipLevel == maxLevel) break; ++mipLevel; const u32 tileMipLevel = gSP.texture.tile + mipLevel + 1; gDPTile & mipTile = gDP.tiles[tileMipLevel]; line = mipTile.line; tmptex.tMem = mipTile.tmem; tmptex.palette = mipTile.palette; tmptex.maskS = mipTile.masks; tmptex.maskT = mipTile.maskt; TileSizes sizes; _calcTileSizes(tileMipLevel, sizes, NULL); tmptex.width = sizes.width; tmptex.clampWidth = sizes.clampWidth; tmptex.height = sizes.height; tmptex.clampHeight = sizes.clampHeight; // Insure mip-map levels size consistency. if (tmptex.realWidth > 1) tmptex.realWidth >>= 1; if (tmptex.realHeight > 1) tmptex.realHeight >>= 1; _pTexture->textureBytes += (tmptex.realWidth * tmptex.realHeight) << sizeShift; }; glPixelStorei(GL_UNPACK_ALIGNMENT, curUnpackAlignment); free(pDest); } struct TextureParams { u16 width; u16 height; u16 clampWidth; u16 clampHeight; u8 maskS; u8 maskT; u8 mirrorS; u8 mirrorT; u8 clampS; u8 clampT; u8 format; u8 size; }; static u32 _calculateCRC(u32 t, const TextureParams & _params) { u32 crc; u32 y, bpl, lineBytes, line; u64 *src; src = (u64*)&TMEM[gSP.textureTile[t]->tmem]; bpl = _params.width << gSP.textureTile[t]->size >> 1; lineBytes = gSP.textureTile[t]->line << 3; line = gSP.textureTile[t]->line; if (gSP.textureTile[t]->size == G_IM_SIZ_32b) line <<= 1; crc = 0xFFFFFFFF; for (y = 0; y < _params.height; y++) { crc = CRC_Calculate( crc, src, bpl ); src += line; } if (gDP.otherMode.textureLUT != G_TT_NONE || gSP.textureTile[t]->format == G_IM_FMT_CI) { if (gSP.textureTile[t]->size == G_IM_SIZ_4b) crc = CRC_Calculate( crc, &gDP.paletteCRC16[gSP.textureTile[t]->palette], 4 ); else if (gSP.textureTile[t]->size == G_IM_SIZ_8b) crc = CRC_Calculate( crc, &gDP.paletteCRC256, 4 ); } crc = CRC_Calculate(crc, &_params, sizeof(_params)); return crc; } void TextureCache::activateTexture(u32 _t, CachedTexture *_pTexture) { glActiveTexture( GL_TEXTURE0 + _t ); // Bind the cached texture glBindTexture( GL_TEXTURE_2D, _pTexture->glName ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, _pTexture->max_level); // Set filter mode. Almost always bilinear, but check anyways if ((gDP.otherMode.textureFilter == G_TF_BILERP) || (gDP.otherMode.textureFilter == G_TF_AVERAGE) || ((gSP.objRendermode&G_OBJRM_BILERP) != 0) || (config.texture.forceBilinear)) { if (_pTexture->max_level > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { if (_pTexture->max_level > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } // Set clamping modes glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _pTexture->clampS ? GL_CLAMP_TO_EDGE : _pTexture->mirrorS ? GL_MIRRORED_REPEAT : GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _pTexture->clampT ? GL_CLAMP_TO_EDGE : _pTexture->mirrorT ? GL_MIRRORED_REPEAT : GL_REPEAT); _pTexture->lastDList = RSP.DList; current[_t] = _pTexture; } void TextureCache::activateDummy(u32 _t) { glActiveTexture( GL_TEXTURE0 + _t ); glBindTexture( GL_TEXTURE_2D, m_pDummy->glName ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); } void TextureCache::_updateBackground() { u32 numBytes = gSP.bgImage.width * gSP.bgImage.height << gSP.bgImage.size >> 1; u32 crc; crc = CRC_Calculate( 0xFFFFFFFF, &RDRAM[gSP.bgImage.address], numBytes ); if (gDP.otherMode.textureLUT != G_TT_NONE || gSP.bgImage.format == G_IM_FMT_CI) { if (gSP.bgImage.size == G_IM_SIZ_4b) crc = CRC_Calculate( crc, &gDP.paletteCRC16[gSP.bgImage.palette], 4 ); else if (gSP.bgImage.size == G_IM_SIZ_8b) crc = CRC_Calculate( crc, &gDP.paletteCRC256, 4 ); } u32 params[4] = {gSP.bgImage.width, gSP.bgImage.height, gSP.bgImage.format, gSP.bgImage.size}; crc = CRC_Calculate(crc, params, sizeof(u32)*4); Textures::iterator iter = m_textures.find(crc); if (iter != m_textures.end()) { CachedTexture & current = iter->second; assert((current.width == gSP.bgImage.width) && (current.height == gSP.bgImage.height) && (current.format == gSP.bgImage.format) && (current.size == gSP.bgImage.size)); activateTexture(0, ¤t); m_hits++; return; } m_misses++; glActiveTexture( GL_TEXTURE0 ); CachedTexture * pCurrent = _addTexture(crc); glBindTexture( GL_TEXTURE_2D, pCurrent->glName ); pCurrent->address = gSP.bgImage.address; pCurrent->format = gSP.bgImage.format; pCurrent->size = gSP.bgImage.size; pCurrent->width = gSP.bgImage.width; pCurrent->height = gSP.bgImage.height; pCurrent->clampWidth = gSP.bgImage.width; pCurrent->clampHeight = gSP.bgImage.height; pCurrent->palette = gSP.bgImage.palette; pCurrent->maskS = 0; pCurrent->maskT = 0; pCurrent->mirrorS = 0; pCurrent->mirrorT = 0; pCurrent->clampS = 0; pCurrent->clampT = 0; pCurrent->line = 0; pCurrent->tMem = 0; pCurrent->lastDList = RSP.DList; pCurrent->frameBufferTexture = FALSE; pCurrent->realWidth = gSP.bgImage.width; pCurrent->realHeight = gSP.bgImage.height; pCurrent->scaleS = 1.0f / (f32)(pCurrent->realWidth); pCurrent->scaleT = 1.0f / (f32)(pCurrent->realHeight); pCurrent->shiftScaleS = 1.0f; pCurrent->shiftScaleT = 1.0f; pCurrent->offsetS = 0.5f; pCurrent->offsetT = 0.5f; _loadBackground(pCurrent); activateTexture(0, pCurrent); m_cachedBytes += pCurrent->textureBytes; current[0] = pCurrent; } void TextureCache::update(u32 _t) { if (m_bitDepth != config.texture.textureBitDepth) { destroy(); init(); } switch(gSP.textureTile[_t]->textureMode) { case TEXTUREMODE_BGIMAGE: _updateBackground(); return; case TEXTUREMODE_FRAMEBUFFER: FrameBuffer_ActivateBufferTexture( _t, gSP.textureTile[_t]->frameBuffer ); return; case TEXTUREMODE_FRAMEBUFFER_BG: FrameBuffer_ActivateBufferTextureBG( _t, gSP.textureTile[_t]->frameBuffer ); return; } if (gSP.texture.tile == 7 && _t == 0 && gSP.textureTile[0] == gDP.loadTile && gDP.loadTile->loadType == LOADTYPE_BLOCK && gSP.textureTile[0]->tmem == gSP.textureTile[1]->tmem) gSP.textureTile[0] = gSP.textureTile[1]; TileSizes sizes; _calcTileSizes(_t, sizes, gDP.loadTile); u32 crc; { TextureParams params; params.width = sizes.width; params.height = sizes.height; params.clampWidth = sizes.clampWidth; params.clampHeight = sizes.clampHeight; params.maskS = gSP.textureTile[_t]->masks; params.maskT = gSP.textureTile[_t]->maskt; params.mirrorS = gSP.textureTile[_t]->mirrors; params.mirrorT = gSP.textureTile[_t]->mirrort; params.clampS = gSP.textureTile[_t]->clamps; params.clampT = gSP.textureTile[_t]->clampt; params.format = gSP.textureTile[_t]->format; params.size = gSP.textureTile[_t]->size; crc = _calculateCRC( _t, params ); } if (current[_t] != NULL && current[_t]->crc == crc) { activateTexture(_t, current[_t]); return; } Textures::iterator iter = m_textures.find(crc); if (iter != m_textures.end()) { CachedTexture & current = iter->second; assert((current.width == sizes.width) && (current.height == sizes.height) && (current.clampWidth == sizes.clampWidth) && (current.clampHeight == sizes.clampHeight) && (current.maskS == gSP.textureTile[_t]->masks) && (current.maskT == gSP.textureTile[_t]->maskt) && (current.mirrorS == gSP.textureTile[_t]->mirrors) && (current.mirrorT == gSP.textureTile[_t]->mirrort) && (current.clampS == gSP.textureTile[_t]->clamps) && (current.clampT == gSP.textureTile[_t]->clampt) && (current.format == gSP.textureTile[_t]->format) && (current.size == gSP.textureTile[_t]->size) ); activateTexture(_t, ¤t); m_hits++; return; } m_misses++; glActiveTexture( GL_TEXTURE0 + _t ); CachedTexture * pCurrent = _addTexture(crc); glBindTexture( GL_TEXTURE_2D, pCurrent->glName ); pCurrent->address = gDP.textureImage.address; pCurrent->format = gSP.textureTile[_t]->format; pCurrent->size = gSP.textureTile[_t]->size; pCurrent->width = sizes.width; pCurrent->height = sizes.height; pCurrent->clampWidth = sizes.clampWidth; pCurrent->clampHeight = sizes.clampHeight; pCurrent->palette = gSP.textureTile[_t]->palette; /* pCurrent->fulS = gSP.textureTile[t]->fulS; pCurrent->fulT = gSP.textureTile[t]->fulT; pCurrent->ulS = gSP.textureTile[t]->ulS; pCurrent->ulT = gSP.textureTile[t]->ulT; pCurrent->lrS = gSP.textureTile[t]->lrS; pCurrent->lrT = gSP.textureTile[t]->lrT;*/ pCurrent->maskS = gSP.textureTile[_t]->masks; pCurrent->maskT = gSP.textureTile[_t]->maskt; pCurrent->mirrorS = gSP.textureTile[_t]->mirrors; pCurrent->mirrorT = gSP.textureTile[_t]->mirrort; pCurrent->clampS = gSP.textureTile[_t]->clamps; pCurrent->clampT = gSP.textureTile[_t]->clampt; pCurrent->line = gSP.textureTile[_t]->line; pCurrent->tMem = gSP.textureTile[_t]->tmem; pCurrent->lastDList = RSP.DList; pCurrent->frameBufferTexture = FALSE; pCurrent->realWidth = sizes.realWidth; pCurrent->realHeight = sizes.realHeight; pCurrent->scaleS = 1.0f / (f32)(pCurrent->realWidth); pCurrent->scaleT = 1.0f / (f32)(pCurrent->realHeight); pCurrent->shiftScaleS = 1.0f; pCurrent->shiftScaleT = 1.0f; pCurrent->offsetS = 0.5f; pCurrent->offsetT = 0.5f; if (gSP.textureTile[_t]->shifts > 10) pCurrent->shiftScaleS = (f32)(1 << (16 - gSP.textureTile[_t]->shifts)); else if (gSP.textureTile[_t]->shifts > 0) pCurrent->shiftScaleS /= (f32)(1 << gSP.textureTile[_t]->shifts); if (gSP.textureTile[_t]->shiftt > 10) pCurrent->shiftScaleT = (f32)(1 << (16 - gSP.textureTile[_t]->shiftt)); else if (gSP.textureTile[_t]->shiftt > 0) pCurrent->shiftScaleT /= (f32)(1 << gSP.textureTile[_t]->shiftt); _load( _t, pCurrent ); activateTexture( _t, pCurrent ); m_cachedBytes += pCurrent->textureBytes; current[_t] = pCurrent; }