/*============================================================================= TexManStreaming.cpp : Common Texture manager implementation. Copyright (c) 2001 Crytek Studios. All Rights Reserved. Revision history: * Created by Khonich Andrey =============================================================================*/ #include "RenderPCH.h" #include "../CommonRender.h" #include "Image/DDSImage.h" #include "Image/dds.h" // DXT compressed block for black image (DXT1, DXT3, DXT5) static byte sDXTData[3][16] = { {0,0,0,0,0,0,0,0}, {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0,0,0,0,0,0,0,0}, {0xff,0xff,0,0,0,0,0,0, 0,0,0,0,0,0,0,0} }; void STexPic::CreateMips() { int nSides = (m_eTT == eTT_Cubemap) ? 6 : 1; int nMips = m_nMips; for (int i=0; iDataArray.Free(); } if (bRelease) SAFE_DELETE_ARRAY(m_Mips[i]); } } } void STexPic::Unload() { if (m_Flags2 & FT2_WASUNLOADED) return; if (!IsStreamed()) return; // Set(); if (gRenDev->m_TexMan->m_Streamed & 1) { if (gRenDev->m_LogFileStr) gRenDev->LogStrv(SRendItem::m_RecurseLevel, "Unload '%s', Time: %.3f\n", m_SourceName.c_str(), iTimer->GetAsyncCurTime()); SaveToCache(); ReleaseDriverTexture(); m_Flags2 |= FT2_WASUNLOADED; gRenDev->SetTexture(0, eTT_Base); return; } } void STexPic::Restore() { if (gRenDev->m_TexMan->m_Streamed & 1) { // Check the distance float fDist = -1.0f; if (gRenDev->m_RP.m_pRE) { #ifndef PIPE_USE_INSTANCING fDist = gRenDev->m_RP.m_pRE->mfDistanceToCameraSquared(*gRenDev->m_RP.m_pCurObject); #else fDist = 999999.0f; int nObj = 0; CCObject *pSaveObj = gRenDev->m_RP.m_pCurObject; CCObject *pObj = pSaveObj; while (true) { if (nObj) { gRenDev->m_RP.m_pCurObject = gRenDev->m_RP.m_MergedObjects[nObj]; gRenDev->m_RP.m_FrameObject++; pObj = gRenDev->m_RP.m_pCurObject; } fDist = min(fDist, gRenDev->m_RP.m_pRE->mfDistanceToCameraSquared(*pObj)); nObj++; if (nObj >= gRenDev->m_RP.m_MergedObjects.Num()) break; } if (pSaveObj != gRenDev->m_RP.m_pCurObject) { gRenDev->m_RP.m_pCurObject = pSaveObj; gRenDev->m_RP.m_FrameObject++; } #endif fDist = crySqrtf(fDist); if (m_Flags2 & FT2_NEEDTORELOAD) fDist = min(m_fMinDistance, fDist); } //gRenDev->m_TexMan->CheckTexLimits(this); LoadFromCache(FPR_IMMEDIATELLY, fDist); gRenDev->SetTexture(0, eTT_Base); return; } } static char *sETFToStr(ETEX_Format ETF) { char *sETF; switch (ETF) { case eTF_Index: sETF = "Index"; break; case eTF_0888: sETF = "0888"; break; case eTF_8888: sETF = "8888"; break; case eTF_8000: sETF = "8000"; break; case eTF_0088: sETF = "0088"; break; case eTF_DXT1: sETF = "DXT1"; break; case eTF_DXT3: sETF = "DXT3"; break; case eTF_DXT5: sETF = "DXT5"; break; case eTF_SIGNED_HILO16: sETF = "SIGNED_HILO16"; break; case eTF_DSDT_MAG: sETF = "DSDT_MAG"; break; case eTF_DSDT: sETF = "DSDT"; break; case eTF_RGB8: sETF = "RGB8"; break; case eTF_RGBA: sETF = "RGBA"; break; default: assert(0); sETF = "Unknown"; // for better behaviour in non debug break; } return sETF; } void STexPic::GetCacheName(char *name) { char *sETT; switch (m_eTT) { case eTT_Base: sETT = "Base"; break; case eTT_DSDTBump: sETT = "DSDTBump"; break; case eTT_Bumpmap: sETT = "Normalmap"; break; case eTT_Cubemap: sETT = "Cubemap"; break; case eTT_Rectangle: sETT = "Rectangle"; break; } char nam[512]; if (!strncmp("Spr_$", m_SourceName.c_str(), 5)) strcpy(nam, m_SourceName.c_str()); else StripExtension(m_SourceName.c_str(), nam); sprintf(name, "%s[%s]", nam, sETT); } void STexPic::SaveToCache() { int i, j; if (m_CacheID >= 0 || (m_Flags2 & FT2_STREAMFROMDDS)) return; CResFile *rf; rf = gRenDev->m_TexMan->m_TexCache; SDirEntry de; char name[512]; GetCacheName(name); if (!rf) gRenDev->m_TexMan->CreateCacheFile(); rf = gRenDev->m_TexMan->m_TexCache; bool bKeepPlace = false; if (rf->mfFileExist(name)>=0) { if (m_Flags2 & FT2_VERSIONWASCHECKED) return; m_Flags2 |= FT2_VERSIONWASCHECKED; if (m_CacheID < 0) m_CacheID = rf->mfFileGetNum(name); if (m_Flags2 & FT2_DISCARDINCACHE) { STexCacheFileHeader fh; STexCacheMipHeader mh; rf->mfFileSeek(m_CacheID, 0, SEEK_SET); rf->mfFileRead2(m_CacheID, sizeof(STexCacheFileHeader), &fh); rf->mfFileRead2(m_CacheID, sizeof(STexCacheMipHeader), &mh); if (m_Width == mh.m_USize && m_Height == mh.m_VSize) bKeepPlace = true; } if (!bKeepPlace) { STexCacheFileHeader fh; rf->mfFileSeek(m_CacheID, 0, SEEK_SET); rf->mfFileRead2(m_CacheID, sizeof(STexCacheFileHeader), &fh); if (fh.m_sExt[0] == 0) { if (fh.m_nMips == m_nMips) return; else { rf->mfFileDelete(m_CacheID); m_CacheID = -1; } } if (m_CacheID >= 0) { char nameInRes[512]; char nameFile[2][512]; int nFiles = GetFileNames(nameFile[0], nameFile[1], 128); int n = 0; while (name[n] != '[') { nameInRes[n] = name[n]; nameInRes[n+1] = 0; n++; } strcat(nameInRes, fh.m_sExt); if (!stricmp(nameInRes, m_SourceName.c_str())) { int nCompares = 0; for (i=0; imfFileDelete(m_CacheID); m_CacheID = -1; } } } STexCacheMipHeader mh; STexCacheFileHeader fh; TArray Data; memset(&fh, 0, sizeof(fh)); BuildMips(); int nSides; if (m_Mips[5]) nSides = 6; else nSides = 1; fh.m_SizeOf = sizeof(fh); fh.m_Version = TEXCACHE_VERSION; fh.m_nSides = nSides; fh.m_nMips = m_nMips; fh.m_DstFormat = m_DstFormat; fh.m_bPolyBump = false; fh.m_bCloneSpace = false; const char *pExt; if (m_SourceName.c_str()[0] && (pExt=GetExtension(m_SourceName.c_str()))) { if (strlen(pExt) < 6) strncpy(fh.m_sExt, pExt, 6); else fh.m_sExt[0] = 0; } else fh.m_sExt[0] = 0; strcpy(fh.m_sETF, sETFToStr(m_ETF)); char nameFile[2][128]; int nFiles = GetFileNames(nameFile[0], nameFile[1], 128); for (i=0; iUSize; mh.m_VSize = mp->VSize; mh.m_Size = mp->DataArray.Num(); mh.m_SizeWithMips = 0; for (j=i; jDataArray.Num(); } int n = Data.Num(); Data.Grow(sizeof(STexCacheMipHeader)); memcpy(&Data[n], &mh, sizeof(STexCacheMipHeader)); } for (j=0; jDataArray.Num()); memcpy(&Data[n], &mp->DataArray[0], mp->DataArray.Num()); } } SDirEntry *pde = rf->mfGetEntry(m_CacheID); if (bKeepPlace && Data.Num() == pde->size) { iSystem->GetIPak()->FSeek(rf->mfGetHandle(), pde->offset, SEEK_SET); iSystem->GetIPak()->FWrite(&Data[0], 1, Data.Num(), rf->mfGetHandle()); } else { memset(&de, 0, sizeof(de)); de.ID = CName(name, eFN_Add).GetIndex(); de.size = Data.Num(); de.eid = eRI_BIN; de.user.data = &Data[0]; de.earc = eARC_NONE; rf->mfFileAdd(&de); rf->mfFlush(); } RemoveMips(4); } bool STexPic::ValidateMipsData(int nStartMip, int nEndMip) { int i, j; bool bOk = true; if (m_eTT == eTT_Cubemap) { for (j=0; j<6; j++) { for (i=nStartMip; i<=nEndMip; i++) { SMipmap *mp = m_Mips[j][i]; if (!CRenderer::CV_r_texturesstreamingonlyvideo) { if (mp && mp->m_bUploaded) continue; } if (!mp || !mp->DataArray.GetSize()) { bOk = false; break; } } } } else { for (i=nStartMip; i<=nEndMip; i++) { SMipmap *mp = m_Mips[0][i]; if (!CRenderer::CV_r_texturesstreamingonlyvideo) { if (mp && mp->m_bUploaded) continue; } if (!mp || !mp->DataArray.GetSize()) { bOk = false; break; } } } return bOk; } void CTextureStreamCallback::StreamOnComplete (IReadStream* pStream, unsigned nError) { PROFILE_FRAME(Texture_AsyncUpload); int i, j; STexCacheInfo *pTexCacheFileInfo = (STexCacheInfo *)pStream->GetUserData(); if (gRenDev->m_TexMan->m_Textures.Num() <= pTexCacheFileInfo->m_TexId) return; STexPic *tp = gRenDev->m_TexMan->m_Textures[pTexCacheFileInfo->m_TexId]; if (!tp || !tp->m_pFileTexMips) { //Warning( VALIDATOR_FLAG_TEXTURE,tp->GetName(),"CTextureStreamCallback::StreamOnComplete texture is missing %s",tp->GetName() ); return; } int n = 0; byte *Src = (byte *)pStream->GetBuffer(); STexCacheFileHeader *fh = &tp->m_CacheFileHeader; STexCacheMipHeader *mh = tp->m_pFileTexMips; int nMips = fh->m_nMips; int nSide = pTexCacheFileInfo->m_nCubeSide; int nS = nSide < 0 ? 0 : nSide; if (gRenDev->m_LogFileStr) gRenDev->LogStrv(SRendItem::m_RecurseLevel, "Async Finish Load '%s' (Side: %d, %d-%d[%d]), Size: %d, Time: %.3f\n", tp->m_SourceName.c_str(), nS, pTexCacheFileInfo->m_nStartLoadMip, pTexCacheFileInfo->m_nEndLoadMip, tp->m_nMips, pTexCacheFileInfo->m_nSizeToLoad, iTimer->GetAsyncCurTime()); for (i=pTexCacheFileInfo->m_nStartLoadMip; i<=pTexCacheFileInfo->m_nEndLoadMip; i++) { SMipmap *mp = tp->m_Mips[nS][i]; assert(mp); mp->m_bLoading = false; if (!mp->m_bUploaded || (CRenderer::CV_r_texturesstreamingonlyvideo && !mp->DataArray.GetSize())) // Already uploaded synchronously { double time0 = 0; ticks(time0); if (!mp->DataArray.GetSize()) mp->DataArray.Alloc(mh[i].m_Size); if (tp->m_ETF == eTF_0888 && (tp->m_Flags2 & FT2_STREAMFROMDDS)) { byte *pTemp = new byte[mp->USize*mp->VSize*3]; cryMemcpy(pTemp, &Src[n], mp->USize*mp->VSize*3); for (int n=0; nUSize*mp->VSize; n++) { mp->DataArray[n*4+0] = pTemp[n*3+0]; mp->DataArray[n*4+1] = pTemp[n*3+1]; mp->DataArray[n*4+2] = pTemp[n*3+2]; mp->DataArray[n*4+3] = 255; } delete [] pTemp; } else cryMemcpy(&mp->DataArray[0], &Src[n], mh[i].m_Size); unticks(time0); gRenDev->m_RP.m_PS.m_fTexUploadTime += (float)(time0*1000.0*g_SecondsPerCycle); } if (tp->m_ETF == eTF_0888 && (tp->m_Flags2 & FT2_STREAMFROMDDS)) n += mh[i].m_USize*mh[i].m_VSize*3; else n += mh[i].m_Size; } gRenDev->m_TexMan->ValidateTexSize(); bool bNeedToWait = false; if (nSide >= 0) { if (tp->m_Flags2 & (FT2_REPLICATETOALLSIDES | FT2_FORCECUBEMAP)) { for (j=1; j<6; j++) { for (int i=pTexCacheFileInfo->m_nStartLoadMip; i<=pTexCacheFileInfo->m_nEndLoadMip; i++) { if (!tp->m_Mips[j][i]) tp->m_Mips[j][i] = new SMipmap(mh[i].m_USize, mh[i].m_VSize); SMipmap *mp = tp->m_Mips[j][i]; if (!mp->DataArray.GetSize()) mp->DataArray.Alloc(mh[i].m_Size); if (tp->m_Flags2 & FT2_REPLICATETOALLSIDES) cryMemcpy(&mp->DataArray[0], &tp->m_Mips[0][i]->DataArray[0], mp->DataArray.GetSize()); else if (tp->m_Flags2 & FT2_FORCECUBEMAP) { if (tp->m_ETF == eTF_DXT1 || tp->m_ETF == eTF_DXT3 || tp->m_ETF == eTF_DXT5) { int nBlocks = ((mp->USize+3)/4)*((mp->VSize+3)/4); int blockSize = tp->m_ETF == eTF_DXT1 ? 8 : 16; int nOffsData = tp->m_ETF - eTF_DXT1; int nCur = 0; for (int n=0; nDataArray[nCur], sDXTData[nOffsData], blockSize); nCur += blockSize; } } else memset(&mp->DataArray[0], 0, mp->DataArray.GetSize()); } } } } for (j=0; j<6; j++) { if (!tp->m_Mips[j]) { bNeedToWait = true; break; } for (i=pTexCacheFileInfo->m_nStartLoadMip; i<=pTexCacheFileInfo->m_nEndLoadMip; i++) { SMipmap *mp = tp->m_Mips[j][i]; if (!mp || !mp->DataArray.GetSize()) { bNeedToWait = true; break; } } if (i <= pTexCacheFileInfo->m_nEndLoadMip) break; } } if (!bNeedToWait) { bool bOk = tp->ValidateMipsData(pTexCacheFileInfo->m_nStartLoadMip, nMips-1); if (!bOk) { tp->RemoveMips(4); tp->m_Flags2 |= FT2_WASUNLOADED; } else { bool bRes = tp->UploadMips(pTexCacheFileInfo->m_nStartLoadMip, nMips-1); if (!pTexCacheFileInfo->m_nStartLoadMip && bRes) tp->RemoveMips(4); tp->m_Flags2 &= ~FT2_WASUNLOADED; if (!tp->m_Mips[0][0] || !tp->m_Mips[0][0]->m_bUploaded) tp->m_Flags2 |= FT2_PARTIALLYLOADED; else tp->m_Flags2 &= ~FT2_PARTIALLYLOADED; } tp->Relink(&STexPic::m_Root); gRenDev->m_TexMan->CheckTexLimits(NULL); } SAFE_DELETE(pTexCacheFileInfo); tp->m_Flags2 &= ~FT2_STREAMINGINPROGRESS; gRenDev->m_TexMan->ValidateTexSize(); } int STexPic::UpdateMip(float fDist) { int nMip = 0; if (fDist) { fDist *= gRenDev->m_TexMan->m_fStreamDistFactor; nMip = min(m_nMips-1, (int)(fDist / (float)CRenderer::CV_r_texturespixelsize)); } if ((m_Width > 64 || m_Height > 64) && !CRenderer::CV_r_texturesstreamingsync) { int nResMip = CRenderer::CV_r_texturesbasemip; if (m_eTT == eTT_Bumpmap) nResMip += CRenderer::CV_r_texbumpresolution + m_nCustomMip; else nResMip += CRenderer::CV_r_texresolution + m_nCustomMip; nResMip = min(m_nMips-1, nResMip); m_nBaseMip = nResMip; nMip = min(m_nMips-1, nMip+nResMip); } else m_nBaseMip = 0; return nMip; } void STexPic::LoadFromCache(int Flags, float fDist) { int i, j; PROFILE_FRAME(Texture_LoadFromCache); int nStartMip = 0; if (fDist >= 0) { //float fTSize = (float)Max(m_Width, m_Height); if (m_nFrameCache != gRenDev->GetFrameID()) { m_nFrameCache = gRenDev->GetFrameID(); m_fMinDistance = fDist; } else if (fDist < m_fMinDistance) m_fMinDistance = fDist; nStartMip = UpdateMip(m_fMinDistance); } else if (m_nFrameCache != gRenDev->GetFrameID()) nStartMip = UpdateMip(0); else nStartMip = UpdateMip(m_fMinDistance); if (m_Mips[0] && m_Mips[0][nStartMip] && m_Mips[0][nStartMip]->m_bUploaded) { if ((m_Flags2 & FT2_NEEDTORELOAD)) { if (m_Mips[0][nStartMip]->DataArray.GetSize()) { gRenDev->m_TexMan->ValidateTexSize(); //STexPic ttt; //memcpy (&ttt, this, sizeof(ttt)); //memset (this, &ttt, sizeof(ttt)); if (ValidateMipsData(nStartMip, m_nMips-1)) UploadMips(nStartMip, m_nMips-1); m_Flags2 &= ~FT2_NEEDTORELOAD; for (i=0; im_bUploaded = false; } if (!m_Mips[0][0] || !m_Mips[0][0]->m_bUploaded) m_Flags2 |= FT2_PARTIALLYLOADED; else m_Flags2 &= ~FT2_PARTIALLYLOADED; gRenDev->m_TexMan->ValidateTexSize(); return; } else { for (i=0; im_bUploaded = false; } } } else return; } gRenDev->m_TexMan->ValidateTexSize(); m_Flags2 &= ~FT2_NEEDTORELOAD; CResFile *rf; rf = gRenDev->m_TexMan->m_TexCache; int nMips, nSides; if (!m_pFileTexMips) { if (m_CacheID < 0) { char name[512]; GetCacheName(name); if (rf->mfFileExist(name)<0) return; // Should never happen m_CacheID = rf->mfFileGetNum(name); } assert(m_CacheID >= 0); rf->mfFileSeek(m_CacheID, 0, SEEK_SET); rf->mfFileRead2(m_CacheID, sizeof(STexCacheFileHeader), &m_CacheFileHeader); nSides = m_CacheFileHeader.m_nSides; nMips = m_CacheFileHeader.m_nMips; m_pFileTexMips = new STexCacheMipHeader[nMips]; rf->mfFileRead2(m_CacheID, sizeof(STexCacheMipHeader)*nMips, m_pFileTexMips); } STexCacheFileHeader *fh = &m_CacheFileHeader; STexCacheMipHeader *mh = m_pFileTexMips; nSides = fh->m_nSides; nMips = fh->m_nMips; assert(nMips == m_nMips); if (!m_Mips[0]) CreateMips(); int Size; int nEndMip = m_nMips-1; int n = nEndMip; int nSyncStartMip = -1; int nSyncEndMip = -1; int nASyncStartMip = -1; int nASyncEndMip = -1; if (nMips == 1 || CRenderer::CV_r_texturesstreamingsync) { nSyncStartMip = nStartMip; nSyncEndMip = nEndMip; } else { // Always stream lowest 4 mips synchronously int nStartLowestM = max(nStartMip, nEndMip-3); for (i=nStartLowestM; i<=nEndMip; i++) { if (!m_Mips[0][i] || !m_Mips[0][i]->m_bUploaded) { nSyncStartMip = i; for (j=i+1; j<=nEndMip; j++) { if (m_Mips[0][j] && m_Mips[0][j]->m_bUploaded) break; } nSyncEndMip = j-1; break; } } // Let's see which part of the texture not loaded yet and stream it asynhronously if (nStartLowestM > nStartMip) { for (i=nStartMip; i<=nStartLowestM-1; i++) { if (!m_Mips[0][i] || (!m_Mips[0][i]->m_bLoading && !m_Mips[0][i]->m_bUploaded)) { nASyncStartMip = i; break; } } if (nASyncStartMip >= 0 && nASyncStartMip <= nStartLowestM-1) { for (i=nASyncStartMip; i<=nStartLowestM-1; i++) { if (m_Mips[0][i] && (m_Mips[0][i]->m_bUploaded || m_Mips[0][i]->m_bLoading)) break; } nASyncEndMip = i-1; } } } if (nASyncStartMip < 0 && nSyncStartMip < 0) return; if (!(Flags & FPR_IMMEDIATELLY)) { int nSize = 0; for (i=nSyncStartMip; i<=nSyncEndMip; i++) { nSize += mh[i].m_Size; } for (i=nASyncStartMip; i<=nASyncEndMip; i++) { nSize += mh[i].m_Size; } int n10Perc = CRenderer::CV_r_texturesstreampoolsize*1024*1024/100*10; if (gRenDev->m_TexMan->m_StatsCurTexMem+nSize+n10Perc >= CRenderer::CV_r_texturesstreampoolsize*1024*1024) return; } Relink(&STexPic::m_Root); IStreamEngine *pSE = iSystem->GetStreamEngine(); FILE *fp = NULL; // Synchronous loading static char* cubefaces[6] = {"posx","negx","posy","negy","posz","negz"}; if (nSyncStartMip >= 0) { PROFILE_FRAME(Texture_LoadFromCacheSync); assert(nSyncStartMip <= nSyncEndMip); int nSeekFromStart = 0; for (i=0; i 1) Size = m_Size/6; else Size = m_Size; for (j=0; jGetIPak()->FClose(fp); char name[512]; StripExtension(m_SourceName.c_str(), name); size_t len = strlen(name); if (len > 5) { for (int i=0; i<6; i++) { if (!stricmp(&name[len-4], cubefaces[i])) { if (name[len-5] == '_') len--; name[len-4] = 0; break; } } } char cube[512]; sprintf(cube, "%s_%s.dds", name, cubefaces[j]); fp = iSystem->GetIPak()->FOpen(cube, "rb"); } if (!j || !(m_Flags2 & FT2_FORCECUBEMAP | FT2_REPLICATETOALLSIDES)) { if (!fp) fp = iSystem->GetIPak()->FOpen(m_SourceName.c_str(), "rb"); } if (fp) iSystem->GetIPak()->FSeek(fp, sizeof(DWORD)+sizeof(DDS_HEADER)+nSeekFromStart, SEEK_SET); } else rf->mfFileSeek(m_CacheID, sizeof(STexCacheFileHeader)+nMips*sizeof(STexCacheMipHeader)+Size*j+nSeekFromStart, SEEK_SET); int SizeToLoad = 0; for (i=nSyncStartMip; i<=nSyncEndMip; i++) { if (m_Mips[j][i] && (m_Mips[j][i]->DataArray.GetSize() || m_Mips[j][i]->m_bUploaded)) { i++; break; } SMipmap *mp; if (!m_Mips[j][i]) m_Mips[j][i] = new SMipmap(mh[i].m_USize, mh[i].m_VSize); mp = m_Mips[j][i]; if (!mp->DataArray.GetSize()) mp->DataArray.Alloc(mh[i].m_Size); assert(!mp->m_bUploaded); gRenDev->m_TexMan->m_LoadBytes += mh[i].m_Size; SizeToLoad += mh[i].m_Size; if (fp) { if (m_ETF == eTF_0888 && (m_Flags2 & FT2_STREAMFROMDDS)) { byte *pTemp = new byte[mp->USize*mp->VSize*3]; iSystem->GetIPak()->FRead(pTemp, mp->USize*mp->VSize*3, 1, fp); for (int n=0; nUSize*mp->VSize; n++) { mp->DataArray[n*4+0] = pTemp[n*3+0]; mp->DataArray[n*4+1] = pTemp[n*3+1]; mp->DataArray[n*4+2] = pTemp[n*3+2]; mp->DataArray[n*4+3] = 255; } delete [] pTemp; } else if (fp) iSystem->GetIPak()->FRead(&mp->DataArray[0], mh[i].m_Size, 1, fp); else if (j) { if (m_Flags2 & FT2_REPLICATETOALLSIDES) memcpy(&mp->DataArray[0], &m_Mips[0][i]->DataArray[0], mp->DataArray.GetSize()); else if (m_Flags2 & FT2_FORCECUBEMAP) { if (m_ETF == eTF_DXT1 || m_ETF == eTF_DXT3 || m_ETF == eTF_DXT5) { int nBlocks = ((mp->USize+3)/4)*((mp->VSize+3)/4); int blockSize = m_ETF == eTF_DXT1 ? 8 : 16; int nOffsData = m_ETF - eTF_DXT1; int nCur = 0; for (int n=0; nDataArray[nCur], sDXTData[nOffsData], blockSize); nCur += blockSize; } } else memset(&mp->DataArray[0], 0, mp->DataArray.GetSize()); } } } else if (m_CacheID >= 0) rf->mfFileRead2(m_CacheID, mh[i].m_Size, &mp->DataArray[0]); } if (gRenDev->m_LogFileStr && SizeToLoad > 0) gRenDev->LogStrv(SRendItem::m_RecurseLevel, "Sync Load '%s' (Side: %d, %d-%d[%d]), Size: %d, Time: %.3f\n", m_SourceName.c_str(), j, nSyncStartMip, i-1, nMips, SizeToLoad, iTimer->GetAsyncCurTime()); } if (fp) iSystem->GetIPak()->FClose(fp); bool bRes; { PROFILE_FRAME(Texture_LoadFromCache_UploadSync); bRes = UploadMips(nSyncStartMip, nMips-1); } if (!nSyncStartMip && bRes) RemoveMips(4); m_Flags2 &= ~FT2_WASUNLOADED; if (!m_Mips[0][0] || !m_Mips[0][0]->m_bUploaded) m_Flags2 |= FT2_PARTIALLYLOADED; else m_Flags2 &= ~FT2_PARTIALLYLOADED; //gRenDev->m_TexMan->CheckTexLimits(this); } // Asynchronous loading if (nASyncStartMip >= 0) { PROFILE_FRAME(Texture_LoadFromCacheASync); assert(nASyncStartMip <= nASyncEndMip); int nSeekFromStart = 0; for (i=0; i 1) Size = m_Size/6; else Size = m_Size; for (j=0; jm_bLoading || m_Mips[j][i]->m_bUploaded)) break; if (!m_Mips[j][i]) m_Mips[j][i] = new SMipmap(mh[i].m_USize, mh[i].m_VSize); m_Mips[j][i]->m_bLoading = true; if (m_ETF == eTF_0888 && (m_Flags2 & FT2_STREAMFROMDDS)) SizeToLoad += mh[i].m_USize*mh[i].m_VSize*3; else SizeToLoad += mh[i].m_Size; } if (j && (m_Flags2 & (FT2_FORCECUBEMAP | FT2_REPLICATETOALLSIDES))) continue; if (SizeToLoad) { i--; if (gRenDev->m_LogFileStr) gRenDev->LogStrv(SRendItem::m_RecurseLevel, "Async Start Load '%s' (Side: %d, %d-%d[%d]), Size: %d, Time: %.3f\n", m_SourceName.c_str(), j, nASyncStartMip, i, nMips, SizeToLoad, iTimer->GetAsyncCurTime()); gRenDev->m_TexMan->m_LoadBytes += SizeToLoad; STexCacheInfo *pTexCacheFileInfo = new STexCacheInfo; pTexCacheFileInfo->m_TexId = m_Id; pTexCacheFileInfo->m_pTempBufferToStream = new byte[SizeToLoad]; byte *buf = (byte *)pTexCacheFileInfo->m_pTempBufferToStream; pTexCacheFileInfo->m_nStartLoadMip = nASyncStartMip; pTexCacheFileInfo->m_nEndLoadMip = i; pTexCacheFileInfo->m_nSizeToLoad = SizeToLoad; pTexCacheFileInfo->m_nCubeSide = nSides > 1 ? j : -1; pTexCacheFileInfo->m_fStartTime = iTimer->GetCurrTime(); m_Flags2 |= FT2_STREAMINGINPROGRESS; StreamReadParams StrParams; if (m_CacheID >= 0) { SDirEntry *de = rf->mfGetEntry(m_CacheID); StrParams.nOffset = de->offset+sizeof(STexCacheFileHeader)+nMips*sizeof(STexCacheMipHeader)+Size*j+nSeekFromStart; } else StrParams.nOffset = sizeof(DWORD)+sizeof(DDS_HEADER)+nSeekFromStart; StrParams.dwUserData = (DWORD_PTR)pTexCacheFileInfo; StrParams.nLoadTime = 1; StrParams.nMaxLoadTime = 4; StrParams.nPriority = 0; StrParams.pBuffer = pTexCacheFileInfo->m_pTempBufferToStream; StrParams.nSize = SizeToLoad; if (m_CacheID >= 0) { StrParams.nFlags |= SRP_FLAGS_MAKE_PERMANENT; IReadStreamPtr pStream = pSE->StartRead(CName(m_CacheID).c_str(), rf->mfGetFileName(), &pTexCacheFileInfo->m_Callback, &StrParams); } else { if (!j) IReadStreamPtr pStream = pSE->StartRead(m_SourceName.c_str(), m_SourceName.c_str(), &pTexCacheFileInfo->m_Callback, &StrParams); else { char name[512]; StripExtension(m_SourceName.c_str(), name); size_t len = strlen(name); if (len > 5) { for (int i=0; i<6; i++) { if (!stricmp(&name[len-4], cubefaces[i])) { if (name[len-5] == '_') len--; name[len-4] = 0; break; } } } char cube[512]; sprintf(cube, "%s_%s.dds", name, cubefaces[j]); IReadStreamPtr pStream = pSE->StartRead(cube, cube, &pTexCacheFileInfo->m_Callback, &StrParams); } } } } } } static ETEX_Format sStrToETF(const char *sETF) { if (!stricmp(sETF, "Index")) return eTF_Index; if (!stricmp(sETF, "0888")) return eTF_0888; if (!stricmp(sETF, "RGB8")) return eTF_RGB8; if (!stricmp(sETF, "8888")) return eTF_8888; if (!stricmp(sETF, "8000")) return eTF_8000; if (!stricmp(sETF, "0088")) return eTF_0088; if (!stricmp(sETF, "DXT1")) return eTF_DXT1; if (!stricmp(sETF, "DXT3")) return eTF_DXT3; if (!stricmp(sETF, "DXT5")) return eTF_DXT5; if (!stricmp(sETF, "SIGNED_HILO16")) return eTF_SIGNED_HILO16; if (!stricmp(sETF, "DSDT_MAG")) return eTF_DSDT_MAG; if (!stricmp(sETF, "DSDT")) return eTF_DSDT; assert (0); return eTF_8888; } static ETexType sStrToETT(const char *sETT) { if (!stricmp(sETT, "Base")) return eTT_Base; if (!stricmp(sETT, "Rectangle")) return eTT_Rectangle; if (!stricmp(sETT, "Cubemap")) return eTT_Cubemap; assert(0); return eTT_Base; } #ifdef WIN64 #pragma warning( push ) //AMD Port #pragma warning( disable : 4267 ) // conversion from 'size_t' to 'int', possible loss of data #endif STexPic *CTexMan::LoadFromCache(STexPic *ti, int flags, int flags2, char *name, const char *szModelName, ETexType eTT) { if (!(m_Streamed & 1)) return NULL; if (flags & FT_NOSTREAM) return NULL; int i, n; FILE *fp = NULL; CResFile *rf = gRenDev->m_TexMan->m_TexCache; char szName[512]; if (ti && !(flags2 & FT2_DISCARDINCACHE)) { strcpy(szName, ti->m_SourceName.c_str()); StripExtension(szName, szName); AddExtension(szName, ".dds"); fp = iSystem->GetIPak()->FOpen(szName, "rb"); if (ti->m_eTT == eTT_Bumpmap && !strstr(ti->m_SourceName.c_str(), "_ddn")) { if (fp) { iSystem->GetIPak()->FClose(fp); fp = NULL; } iLog->Log("Warning: streaming of heightmap texture %s.dds are not supported (texture name should contain _ddn substring)", ti->m_SourceName.c_str()); } if (ti->m_eTT == eTT_DSDTBump && !strstr(ti->m_SourceName.c_str(), "_ddt")) { if (fp) { iSystem->GetIPak()->FClose(fp); fp = NULL; } iLog->Log("Warning: streaming of heightmap texture %s.dds are not supported (texture name should contain _ddt substring)", ti->m_SourceName.c_str()); } } if (!fp && !rf) { if (!CreateCacheFile()) return NULL; rf = gRenDev->m_TexMan->m_TexCache; } bool bSprite = false; if (ti && !(flags2 & FT2_DISCARDINCACHE)) ti->GetCacheName(szName); else if (!ti) sprintf(szName, "%s[Base]", name); else { strcpy(szName, ti->m_Name.c_str()); szModelName = &ti->m_SourceName.c_str()[0]; } ConvertDOSToUnixName(szName, szName); name = szName; if (eTT == eTT_Cubemap && m_LastCMStreamed) { char SourceName[512]; n = 0; while (name[n] != '[') { SourceName[n] = name[n]; n++; SourceName[n] = 0; } strcat(SourceName, m_LastCMStreamed->m_CacheFileHeader.m_sExt); ti->m_Width = m_LastCMStreamed->m_Width; ti->m_Height = m_LastCMStreamed->m_Height; ti->m_SourceName = SourceName; ti->m_nMips = m_LastCMStreamed->m_nMips; ti->m_Size = 0; if (ti->m_nMips <= 1) ti->m_Flags |= FT_NOMIPS; ti->m_eTT = eTT; int CubeSide; int tgt = TEXTGT_CUBEMAP; n = strlen(ti->m_SearchName.c_str()) - 4; if (!strcmp(&ti->m_SearchName.c_str()[n], "posx")) CubeSide = 0; else if (!strcmp(&ti->m_SearchName.c_str()[n], "negx")) CubeSide = 1; else if (!strcmp(&ti->m_SearchName.c_str()[n], "posy")) CubeSide = 2; else if (!strcmp(&ti->m_SearchName.c_str()[n], "negy")) CubeSide = 3; else if (!strcmp(&ti->m_SearchName.c_str()[n], "posz")) CubeSide = 4; else if (!strcmp(&ti->m_SearchName.c_str()[n], "negz")) CubeSide = 5; else CubeSide = 0; if (m_LastCMStreamed->m_CubeSide >= CubeSide) { m_LastCMStreamed = NULL; return NULL; } ti->m_DstFormat = m_LastCMStreamed->m_DstFormat; if (CubeSide == 5) m_LastCMStreamed = NULL; ti->m_CubeSide = CubeSide; ti->m_Flags2 |= FT2_WASUNLOADED; ti->m_Bind = TX_FIRSTBIND + ti->m_Id; return ti; } if (!fp && rf->mfFileExist(name)<0) return NULL; if (!ti) { bSprite = true; ti = TextureInfoForName(name, -1, eTT_Base, flags, flags2, -1); if (ti->m_bBusy) return ti; ti->m_bBusy = true; ti->m_Flags = flags; ti->m_Flags2 = flags2 & ~FT2_RELOAD; ti->m_Bind = TX_FIRSTBIND + ti->m_Id; } else if (flags2 & FT2_DISCARDINCACHE) bSprite = true; STexCacheFileHeader fh; int nSides, nMips; if (fp) { iSystem->GetIPak()->FSeek(fp, 0, SEEK_END); int size = iSystem->GetIPak()->FTell(fp); iSystem->GetIPak()->FSeek(fp, 0, SEEK_SET); byte *buf = new byte [size+1]; size = iSystem->GetIPak()->FRead(buf, 1, size, fp); iSystem->GetIPak()->FClose(fp); CImageFile::m_CurFileName[0] = 0; CImageDDSFile im(buf, size); delete [] buf; if (im.mfGet_error() == eIFE_OK && im.mfGetFormat() != eIF_Unknown) { ti->m_Flags2 |= FT2_STREAMFROMDDS; int i, j; fh.m_SizeOf = sizeof(STexCacheFileHeader); fh.m_bPolyBump = false; fh.m_bCloneSpace = false; fh.m_nMips = im.mfGet_numMips(); ETEX_Format eTF; if (ti->m_eTT == eTT_DSDTBump) eTF = eTF_DSDT_MAG; else eTF = sImageFormat2TexFormat(im.mfGetFormat()); strcpy(fh.m_sETF, sETFToStr(eTF)); strcpy(fh.m_sExt, ".dds"); fh.m_nSides = ti->m_eTT == eTT_Cubemap ? 6 : 1; fh.m_DstFormat = ti->DstFormatFromTexFormat(eTF); memcpy(&ti->m_CacheFileHeader, &fh, sizeof(fh)); nSides = ti->m_CacheFileHeader.m_nSides; nMips = ti->m_CacheFileHeader.m_nMips; ti->m_pFileTexMips = new STexCacheMipHeader[nMips]; int wdt = im.mfGet_width(); int hgt = im.mfGet_height(); for (i=0; im_pFileTexMips[i].m_USize = wdt; ti->m_pFileTexMips[i].m_VSize = hgt; if (eTF == eTF_DXT1 || eTF == eTF_DXT3 || eTF == eTF_DXT5) { ti->m_pFileTexMips[i].m_USize = (ti->m_pFileTexMips[i].m_USize + 3) & ~3; ti->m_pFileTexMips[i].m_VSize = (ti->m_pFileTexMips[i].m_VSize + 3) & ~3; } ti->m_pFileTexMips[i].m_Size = ti->TexSize(wdt, hgt, fh.m_DstFormat); ti->m_pFileTexMips[i].m_SizeOf = sizeof(STexCacheMipHeader); wdt >>= 1; hgt >>= 1; } for (i=0; im_pFileTexMips[i].m_SizeWithMips = 0; for (j=i; jm_pFileTexMips[i].m_SizeWithMips += ti->m_pFileTexMips[j].m_Size; } } } else fp = NULL; } if (!fp) { if (ti->m_CacheID < 0) ti->m_CacheID = rf->mfFileGetNum(name); rf->mfFileSeek(ti->m_CacheID, 0, SEEK_SET); rf->mfFileRead2(ti->m_CacheID, sizeof(STexCacheFileHeader), &fh); if (fh.m_sExt[0] == 0 && !bSprite) return NULL; memcpy(&ti->m_CacheFileHeader, &fh, sizeof(fh)); nSides = ti->m_CacheFileHeader.m_nSides; nMips = ti->m_CacheFileHeader.m_nMips; ti->m_pFileTexMips = new STexCacheMipHeader[nMips]; rf->mfFileRead2(ti->m_CacheID, sizeof(STexCacheMipHeader)*nMips, ti->m_pFileTexMips); } char SourceName[512]; n = 0; char sETT[32]; if (bSprite) { strcpy(SourceName, szModelName); ti->m_ETF = eTF_8888; } else { while (name[n] != '[') { SourceName[n] = name[n]; n++; SourceName[n] = 0; } n++; int m = 0; while (name[n] != ']') { sETT[m] = name[n]; n++; m++; } sETT[m] = 0; strcat(SourceName, fh.m_sExt); ti->m_ETF = sStrToETF(fh.m_sETF); if (ti->m_eTT == eTT_DSDTBump) ti->m_ETF = eTF_0888; } char nameFile[2][128]; int nFiles = ti->GetFileNames(nameFile[0], nameFile[1], 128); if (nFiles > 1) strcat(nameFile[1], fh.m_sExt); else { StripExtension(nameFile[0], nameFile[0]); strcat(nameFile[0], fh.m_sExt); } if (!bSprite && !fp) { int nComp = 0; for (i=0; im_CacheID = -1; return NULL; } } else if (bSprite && !(flags2 & FT2_RELOAD)) { HANDLE status2 = CreateFile(szModelName,GENERIC_READ,FILE_SHARE_READ, NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL); if (status2 != INVALID_HANDLE_VALUE) { FILETIME writetime1,writetime2; writetime1 = fh.m_WriteTime[0]; GetFileTime(status2,NULL,NULL,&writetime2); CloseHandle(status2); if (CompareFileTime(&writetime1,&writetime2)!=0) { ti->m_CacheID = -1; return NULL; } } } STexCacheMipHeader *mh = ti->m_pFileTexMips; ti->m_eTT = eTT; ti->m_Width = ti->m_pFileTexMips[0].m_USize; ti->m_Height = ti->m_pFileTexMips[0].m_VSize; ti->m_SourceName = SourceName; ti->m_nMips = nMips; ti->m_Size = ti->m_pFileTexMips[0].m_SizeWithMips * nSides; if (ti->m_nMips <= 1) ti->m_Flags |= FT_NOMIPS; int CubeSide = -1; int tgt = TEXTGT_2D; if (ti->m_eTT == eTT_Cubemap) { tgt = TEXTGT_CUBEMAP; int n = strlen(ti->m_SearchName.c_str()) - 4; assert (n > 0); if (!strcmp(&ti->m_SearchName.c_str()[n], "posx")) CubeSide = 0; else if (!strcmp(&ti->m_SearchName.c_str()[n], "negx")) CubeSide = 1; else if (!strcmp(&ti->m_SearchName.c_str()[n], "posy")) CubeSide = 2; else if (!strcmp(&ti->m_SearchName.c_str()[n], "negy")) CubeSide = 3; else if (!strcmp(&ti->m_SearchName.c_str()[n], "posz")) CubeSide = 4; else if (!strcmp(&ti->m_SearchName.c_str()[n], "negz")) CubeSide = 5; else CubeSide = 0; } if (CubeSide < 0 || CubeSide == 5) m_LastCMStreamed = NULL; else m_LastCMStreamed = ti; ti->m_CubeSide = CubeSide; ti->Unlink(); if (ti->m_eTT != eTT_Cubemap || !ti->m_CubeSide) ti->m_Flags2 |= FT2_WASUNLOADED; ti->m_Flags2 |= FT2_WASLOADED; ti->m_Bind = TX_FIRSTBIND + ti->m_Id; AddToHash(ti->m_Bind, ti); ti->m_RefTex.m_MipFilter = nMips > 1 ? m_MipFilter : 0; ti->m_DstFormat = fh.m_DstFormat; if (ti->m_eTT == eTT_Cubemap) { ti->m_RefTex.bRepeats = false; ti->m_RefTex.m_MinFilter = GetMinFilter(); ti->m_RefTex.m_MagFilter = GetMagFilter(); ti->m_RefTex.m_AnisLevel = 1; } else { if (ti->m_Flags & FT_CLAMP) ti->m_RefTex.bRepeats = false; else ti->m_RefTex.bRepeats = true; ti->m_RefTex.m_MinFilter = GetMinFilter(); ti->m_RefTex.m_MagFilter = GetMagFilter(); ti->m_RefTex.m_AnisLevel = 1; } ti->m_RefTex.m_Type = tgt; ti->m_RefTex.bProjected = (ti->m_Flags & FT_PROJECTED) ? true : false; // Always load lowest 4 mips synchronously fp = NULL; if (ti->m_nMips > 1) { int i, j; int Size; if (!ti->m_Mips[0]) ti->CreateMips(); STexCacheFileHeader *fh = &ti->m_CacheFileHeader; STexCacheMipHeader *mh = ti->m_pFileTexMips; nSides = fh->m_nSides; nMips = fh->m_nMips; int nSyncStartMip = -1; int nSyncEndMip = -1; int nEndMip = ti->m_nMips-1; int nStartLowestM = max(0, nEndMip-3); for (i=nStartLowestM; i<=nEndMip; i++) { if (!ti->m_Mips[0][i] || !ti->m_Mips[0][i]->m_bUploaded) { nSyncStartMip = i; for (j=i+1; j<=nEndMip; j++) { if (ti->m_Mips[0][j] && ti->m_Mips[0][j]->m_bUploaded) break; } nSyncEndMip = j-1; break; } } if (nSyncStartMip >= 0) { assert(nSyncStartMip <= nSyncEndMip); int nSeekFromStart = 0; for (i=0; im_ETF == eTF_0888 && (ti->m_Flags2 & FT2_STREAMFROMDDS)) nSeekFromStart += mh[i].m_USize*mh[i].m_VSize*3; else nSeekFromStart += mh[i].m_Size; } if (nSides > 1) Size = ti->m_Size/6; else Size = ti->m_Size; static char* cubefaces[6] = {"posx","negx","posy","negy","posz","negz"}; for (j=0; jm_CacheID < 0) { if (j) { if (fp) iSystem->GetIPak()->FClose(fp); char name[512]; StripExtension(ti->m_SourceName.c_str(), name); int len = strlen(name); if (len > 5) { for (int i=0; i<6; i++) { if (!stricmp(&name[len-4], cubefaces[i])) { if (name[len-5] == '_') len--; name[len-4] = 0; break; } } } char cube[512]; sprintf(cube, "%s_%s.dds", name, cubefaces[j]); fp = iSystem->GetIPak()->FOpen(cube, "rb"); } if (!fp) { if (!j) fp = iSystem->GetIPak()->FOpen(ti->m_SourceName.c_str(), "rb"); } if (fp) iSystem->GetIPak()->FSeek(fp, sizeof(DWORD)+sizeof(DDS_HEADER)+nSeekFromStart, SEEK_SET); } else rf->mfFileSeek(ti->m_CacheID, sizeof(STexCacheFileHeader)+nMips*sizeof(STexCacheMipHeader)+Size*j+nSeekFromStart, SEEK_SET); int SizeToLoad = 0; for (i=nSyncStartMip; i<=nSyncEndMip; i++) { if (ti->m_Mips[j][i] && (ti->m_Mips[j][i]->DataArray.GetSize() || ti->m_Mips[j][i]->m_bUploaded)) { i++; break; } SMipmap *mp; if (!ti->m_Mips[j][i]) ti->m_Mips[j][i] = new SMipmap(mh[i].m_USize, mh[i].m_VSize); mp = ti->m_Mips[j][i]; if (!mp->DataArray.GetSize()) mp->DataArray.Alloc(mh[i].m_Size); assert(!mp->m_bUploaded); gRenDev->m_TexMan->m_LoadBytes += mh[i].m_Size; SizeToLoad += mh[i].m_Size; if (fp) { if (ti->m_ETF == eTF_0888) { byte *pTemp = new byte[mh[i].m_USize*mh[i].m_VSize*3]; iSystem->GetIPak()->FRead(pTemp, mh[i].m_USize*mh[i].m_VSize*3, 1, fp); for (int n=0; nDataArray[n*4+0] = pTemp[n*3+0]; mp->DataArray[n*4+1] = pTemp[n*3+1]; mp->DataArray[n*4+2] = pTemp[n*3+2]; mp->DataArray[n*4+3] = 255; } delete [] pTemp; } else iSystem->GetIPak()->FRead(&mp->DataArray[0], mh[i].m_Size, 1, fp); } else if (ti->m_CacheID >= 0) rf->mfFileRead2(ti->m_CacheID, mh[i].m_Size, &mp->DataArray[0]); else if (j) { if (ti->m_Flags2 & FT2_REPLICATETOALLSIDES) memcpy(&mp->DataArray[0], &ti->m_Mips[0][i]->DataArray[0], mp->DataArray.GetSize()); else if (ti->m_Flags2 & FT2_FORCECUBEMAP) { if (ti->m_ETF == eTF_DXT1 || ti->m_ETF == eTF_DXT3 || ti->m_ETF == eTF_DXT5) { int nBlocks = ((mp->USize+3)/4)*((mp->VSize+3)/4); int blockSize = ti->m_ETF == eTF_DXT1 ? 8 : 16; int nOffsData = ti->m_ETF - eTF_DXT1; int nCur = 0; for (int n=0; nDataArray[nCur], sDXTData[nOffsData], blockSize); nCur += blockSize; } } else memset(&mp->DataArray[0], 0, mp->DataArray.GetSize()); } } } } if (fp) { iSystem->GetIPak()->FClose(fp); fp = NULL; } } } return ti; } #ifdef WIN64 #pragma warning( pop ) //AMD Port #endif //========================================================================= bool CTexMan::CreateCacheFile() { STexCacheFileHeader fh; STexCacheMipHeader mh; bool bValid = true; #ifdef DIRECT3D8 CResFile *rf = new CResFile("Textures\\TexturesD3D8.cache", eFSD_name); #elif DIRECT3D9 CResFile *rf = new CResFile("Textures\\TexturesD3D9.cache", eFSD_name); #elif OPENGL CResFile *rf = new CResFile("Textures\\TexturesOGL.cache", eFSD_name); #else CResFile *rf = new CResFile("Textures\\Textures.cache", eFSD_name); #endif rf->mfOpen(RA_READ); if (rf->mfGetError()) { rf->mfClose(); rf->mfOpen(RA_CREATE); bValid = false; } else { rf->mfFileSeek(0, 0, SEEK_SET); rf->mfFileRead2(0, sizeof(STexCacheFileHeader), &fh); if (fh.m_SizeOf != sizeof(STexCacheFileHeader) || fh.m_Version != TEXCACHE_VERSION) bValid = false; else { rf->mfFileRead2(0, sizeof(STexCacheMipHeader), &mh); if (mh.m_SizeOf != sizeof(STexCacheMipHeader)) bValid = false; else if (rf->mfGetHolesSize()/rf->mfGetResourceSize()*100 > 20) bValid = false; } if (bValid) { rf->mfClose(); rf->mfOpen(RA_READ|RA_WRITE); } else { iLog->LogToConsole("Texture cache file '%s' isn't valid (creating new one...)\n", "Textures\\TexturesD3D8.cache"); rf->mfClose(); rf->mfOpen(RA_CREATE); } } gRenDev->m_TexMan->m_TexCache = rf; return bValid; }