Skip to content

Commit 03e6652

Browse files
committed
Improve texture loading system with fixes that include addendums for 53945f2, c637d39, and individually fix texture loss, memory leaks in stale states, and stacked replacement corruption (Accidentally destroying textures still used by another replacement)
1 parent 937bc63 commit 03e6652

File tree

1 file changed

+165
-4
lines changed

1 file changed

+165
-4
lines changed

Client/game_sa/CRenderWareSA.TextureReplacing.cpp

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,38 @@ namespace
3333
{
3434
using TextureSwapMap = std::unordered_map<RwTexture*, RwTexture*>;
3535

36+
void CleanupStalePerTxd(SReplacementTextures::SPerTxd& perTxdInfo, const std::unordered_set<RwTexture*>& texturesToKeep)
37+
{
38+
// TXD is dead/stale. Perform safe cleanup.
39+
for (RwTexture* pOldTexture : perTxdInfo.usingTextures)
40+
{
41+
if (pOldTexture && SharedUtil::IsReadablePointer(pOldTexture, sizeof(RwTexture)))
42+
{
43+
pOldTexture->txd = nullptr; // Detach from dead dictionary
44+
if (perTxdInfo.bTexturesAreCopies)
45+
{
46+
std::exchange(pOldTexture->raster, nullptr);
47+
RwTextureDestroy(pOldTexture);
48+
}
49+
}
50+
}
51+
perTxdInfo.usingTextures.clear();
52+
53+
for (RwTexture* pReplacedTexture : perTxdInfo.replacedOriginals)
54+
{
55+
if (pReplacedTexture && SharedUtil::IsReadablePointer(pReplacedTexture, sizeof(RwTexture)))
56+
{
57+
pReplacedTexture->txd = nullptr; // Detach from dead dictionary
58+
// Destroy only the textures that are not present in texturesToKeep (originals or owned by other replacements)
59+
if (texturesToKeep.find(pReplacedTexture) == texturesToKeep.end())
60+
{
61+
RwTextureDestroy(pReplacedTexture);
62+
}
63+
}
64+
}
65+
perTxdInfo.replacedOriginals.clear();
66+
}
67+
3668
void ReplaceTextureInGeometry(RpGeometry* pGeometry, const TextureSwapMap& swapMap)
3769
{
3870
if (!pGeometry || swapMap.empty())
@@ -119,8 +151,99 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(ushort usModelId)
119151

120152
const ushort usTxdId = pModelInfo->GetTextureDictionaryID();
121153

122-
if (auto it = ms_ModelTexturesInfoMap.find(usTxdId); it != ms_ModelTexturesInfoMap.end())
123-
return &it->second;
154+
auto it = ms_ModelTexturesInfoMap.find(usTxdId);
155+
if (it != ms_ModelTexturesInfoMap.end())
156+
{
157+
// Validate the cached TXD pointer
158+
CModelTexturesInfo& info = it->second;
159+
RwTexDictionary* pCurrentTxd = CTxdStore_GetTxd(usTxdId);
160+
161+
if (info.pTxd != pCurrentTxd)
162+
{
163+
// The TXD has been reloaded or unloaded by the game
164+
165+
// Clear old replacement references when swapping TXDs
166+
std::unordered_set<RwTexture*> texturesToKeep;
167+
texturesToKeep.insert(info.originalTextures.begin(), info.originalTextures.end());
168+
169+
// Collect all textures currently in use by all replacements for this TXD
170+
for (SReplacementTextures* pReplacement : info.usedByReplacements)
171+
{
172+
auto itPerTxd = std::find_if(pReplacement->perTxdList.begin(), pReplacement->perTxdList.end(),
173+
[usTxdId](const SReplacementTextures::SPerTxd& item) { return item.usTxdId == usTxdId; });
174+
175+
if (itPerTxd != pReplacement->perTxdList.end())
176+
{
177+
texturesToKeep.insert(itPerTxd->usingTextures.begin(), itPerTxd->usingTextures.end());
178+
}
179+
}
180+
181+
for (SReplacementTextures* pReplacement : info.usedByReplacements)
182+
{
183+
// Find the per-TXD info for this stale TXD
184+
auto itPerTxd = std::find_if(pReplacement->perTxdList.begin(), pReplacement->perTxdList.end(),
185+
[usTxdId](const SReplacementTextures::SPerTxd& item) { return item.usTxdId == usTxdId; });
186+
187+
if (itPerTxd != pReplacement->perTxdList.end())
188+
{
189+
CleanupStalePerTxd(*itPerTxd, texturesToKeep);
190+
191+
// Remove this TXD from the replacement's list
192+
pReplacement->perTxdList.erase(itPerTxd);
193+
}
194+
195+
// Remove from usedInTxdIds
196+
ListRemove(pReplacement->usedInTxdIds, usTxdId);
197+
198+
// Remove associated models from usedInModelIds to allow re-application
199+
for (auto itModel = pReplacement->usedInModelIds.begin(); itModel != pReplacement->usedInModelIds.end(); )
200+
{
201+
ushort modelId = *itModel;
202+
CModelInfoSA* pModInfo = dynamic_cast<CModelInfoSA*>(pGame->GetModelInfo(modelId));
203+
if (pModInfo && pModInfo->GetTextureDictionaryID() == usTxdId)
204+
{
205+
itModel = pReplacement->usedInModelIds.erase(itModel);
206+
}
207+
else
208+
{
209+
++itModel;
210+
}
211+
}
212+
}
213+
info.usedByReplacements.clear();
214+
215+
if (pCurrentTxd)
216+
{
217+
// Reloaded: Update our pointer and refresh original textures
218+
info.pTxd = pCurrentTxd;
219+
info.originalTextures.clear();
220+
GetTxdTextures(info.originalTextures, pCurrentTxd);
221+
CTxdStore_RemoveRef(usTxdId);
222+
CTxdStore_AddRef(usTxdId);
223+
}
224+
else
225+
{
226+
// Unloaded: We need to reload it
227+
pModelInfo->Request(BLOCKING, "CRenderWareSA::GetModelTexturesInfo");
228+
CTxdStore_RemoveRef(usTxdId);
229+
CTxdStore_AddRef(usTxdId);
230+
pCurrentTxd = CTxdStore_GetTxd(usTxdId);
231+
232+
if (pCurrentTxd)
233+
{
234+
info.pTxd = pCurrentTxd;
235+
info.originalTextures.clear();
236+
GetTxdTextures(info.originalTextures, pCurrentTxd);
237+
}
238+
else
239+
{
240+
// Failed to load
241+
return nullptr;
242+
}
243+
}
244+
}
245+
return &info;
246+
}
124247

125248
// Get txd
126249
RwTexDictionary* pTxd = CTxdStore_GetTxd(usTxdId);
@@ -147,8 +270,8 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(ushort usModelId)
147270
return nullptr;
148271

149272
// Add new info
150-
auto [it, inserted] = ms_ModelTexturesInfoMap.emplace(usTxdId, CModelTexturesInfo{});
151-
auto& newInfo = it->second;
273+
auto [iter, inserted] = ms_ModelTexturesInfoMap.emplace(usTxdId, CModelTexturesInfo{});
274+
auto& newInfo = iter->second;
152275
newInfo.usTxdId = usTxdId;
153276
newInfo.pTxd = pTxd;
154277

@@ -371,6 +494,44 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
371494
dassert(MapFind(ms_ModelTexturesInfoMap, usTxdId));
372495
dassert(ListContains(pInfo->usedByReplacements, pReplacementTextures));
373496

497+
// Check for stale TXD
498+
RwTexDictionary* pCurrentTxd = CTxdStore_GetTxd(usTxdId);
499+
bool bTxdIsValid = (pInfo->pTxd == pCurrentTxd) && (pInfo->pTxd != nullptr);
500+
501+
if (!bTxdIsValid)
502+
{
503+
std::unordered_set<RwTexture*> texturesToKeep;
504+
texturesToKeep.insert(pInfo->originalTextures.begin(), pInfo->originalTextures.end());
505+
506+
// Collect textures from other replacements
507+
for (SReplacementTextures* pOtherReplacement : pInfo->usedByReplacements)
508+
{
509+
if (pOtherReplacement == pReplacementTextures)
510+
continue;
511+
512+
auto itPerTxd = std::find_if(pOtherReplacement->perTxdList.begin(), pOtherReplacement->perTxdList.end(),
513+
[usTxdId](const SReplacementTextures::SPerTxd& item) { return item.usTxdId == usTxdId; });
514+
515+
if (itPerTxd != pOtherReplacement->perTxdList.end())
516+
{
517+
texturesToKeep.insert(itPerTxd->usingTextures.begin(), itPerTxd->usingTextures.end());
518+
}
519+
}
520+
521+
CleanupStalePerTxd(perTxdInfo, texturesToKeep);
522+
523+
ListRemove(pInfo->usedByReplacements, pReplacementTextures);
524+
ListRemove(pReplacementTextures->usedInTxdIds, usTxdId);
525+
526+
if (pInfo->usedByReplacements.empty())
527+
{
528+
pInfo->originalTextures.clear();
529+
CTxdStore_RemoveRef(pInfo->usTxdId);
530+
MapRemove(ms_ModelTexturesInfoMap, usTxdId);
531+
}
532+
continue;
533+
}
534+
374535
TextureSwapMap swapMap;
375536
swapMap.reserve(perTxdInfo.usingTextures.size());
376537

0 commit comments

Comments
 (0)