Normalize texture cache keys to prevent duplicate GPU textures

This commit is contained in:
Kelsi 2026-02-12 16:15:25 -08:00
parent 3eda342b87
commit 46c672d1c2
4 changed files with 53 additions and 14 deletions

View file

@ -312,15 +312,23 @@ GLuint CharacterRenderer::loadTexture(const std::string& path) {
} }
if (allWhitespace) return whiteTexture; if (allWhitespace) return whiteTexture;
auto normalizeKey = [](std::string key) {
std::replace(key.begin(), key.end(), '/', '\\');
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return key;
};
std::string key = normalizeKey(path);
// Check cache // Check cache
auto it = textureCache.find(path); auto it = textureCache.find(key);
if (it != textureCache.end()) return it->second; if (it != textureCache.end()) return it->second;
if (!assetManager || !assetManager->isInitialized()) { if (!assetManager || !assetManager->isInitialized()) {
return whiteTexture; return whiteTexture;
} }
auto blpImage = assetManager->loadTexture(path); auto blpImage = assetManager->loadTexture(key);
if (!blpImage.isValid()) { if (!blpImage.isValid()) {
core::Logger::getInstance().warning("Failed to load texture: ", path); core::Logger::getInstance().warning("Failed to load texture: ", path);
// Do not cache failures as white. Some asset reads can fail transiently and // Do not cache failures as white. Some asset reads can fail transiently and
@ -341,7 +349,7 @@ GLuint CharacterRenderer::loadTexture(const std::string& path) {
applyAnisotropicFiltering(); applyAnisotropicFiltering();
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
textureCache[path] = texId; textureCache[key] = texId;
core::Logger::getInstance().info("Loaded character texture: ", path, " (", blpImage.width, "x", blpImage.height, ")"); core::Logger::getInstance().info("Loaded character texture: ", path, " (", blpImage.width, "x", blpImage.height, ")");
return texId; return texId;
} }

View file

@ -2674,14 +2674,22 @@ void M2Renderer::cleanupUnusedModels() {
} }
GLuint M2Renderer::loadTexture(const std::string& path) { GLuint M2Renderer::loadTexture(const std::string& path) {
auto normalizeKey = [](std::string key) {
std::replace(key.begin(), key.end(), '/', '\\');
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return key;
};
std::string key = normalizeKey(path);
// Check cache // Check cache
auto it = textureCache.find(path); auto it = textureCache.find(key);
if (it != textureCache.end()) { if (it != textureCache.end()) {
return it->second; return it->second;
} }
// Load BLP texture // Load BLP texture
pipeline::BLPImage blp = assetManager->loadTexture(path); pipeline::BLPImage blp = assetManager->loadTexture(key);
if (!blp.isValid()) { if (!blp.isValid()) {
LOG_WARNING("M2: Failed to load texture: ", path); LOG_WARNING("M2: Failed to load texture: ", path);
// Don't cache failures — transient StormLib thread contention can // Don't cache failures — transient StormLib thread contention can
@ -2706,7 +2714,7 @@ GLuint M2Renderer::loadTexture(const std::string& path) {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
textureCache[path] = textureID; textureCache[key] = textureID;
LOG_DEBUG("M2: Loaded texture: ", path, " (", blp.width, "x", blp.height, ")"); LOG_DEBUG("M2: Loaded texture: ", path, " (", blp.width, "x", blp.height, ")");
return textureID; return textureID;

View file

@ -223,14 +223,22 @@ TerrainChunkGPU TerrainRenderer::uploadChunk(const pipeline::ChunkMesh& chunk) {
} }
GLuint TerrainRenderer::loadTexture(const std::string& path) { GLuint TerrainRenderer::loadTexture(const std::string& path) {
auto normalizeKey = [](std::string key) {
std::replace(key.begin(), key.end(), '/', '\\');
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return key;
};
std::string key = normalizeKey(path);
// Check cache first // Check cache first
auto it = textureCache.find(path); auto it = textureCache.find(key);
if (it != textureCache.end()) { if (it != textureCache.end()) {
return it->second; return it->second;
} }
// Load BLP texture // Load BLP texture
pipeline::BLPImage blp = assetManager->loadTexture(path); pipeline::BLPImage blp = assetManager->loadTexture(key);
if (!blp.isValid()) { if (!blp.isValid()) {
LOG_WARNING("Failed to load texture: ", path); LOG_WARNING("Failed to load texture: ", path);
// Do not cache failure as white: MPQ/file reads can fail transiently // Do not cache failure as white: MPQ/file reads can fail transiently
@ -261,7 +269,7 @@ GLuint TerrainRenderer::loadTexture(const std::string& path) {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// Cache texture // Cache texture
textureCache[path] = textureID; textureCache[key] = textureID;
LOG_DEBUG("Loaded texture: ", path, " (", blp.width, "x", blp.height, ")"); LOG_DEBUG("Loaded texture: ", path, " (", blp.width, "x", blp.height, ")");
@ -269,9 +277,16 @@ GLuint TerrainRenderer::loadTexture(const std::string& path) {
} }
void TerrainRenderer::uploadPreloadedTextures(const std::unordered_map<std::string, pipeline::BLPImage>& textures) { void TerrainRenderer::uploadPreloadedTextures(const std::unordered_map<std::string, pipeline::BLPImage>& textures) {
auto normalizeKey = [](std::string key) {
std::replace(key.begin(), key.end(), '/', '\\');
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return key;
};
for (const auto& [path, blp] : textures) { for (const auto& [path, blp] : textures) {
std::string key = normalizeKey(path);
// Skip if already cached // Skip if already cached
if (textureCache.find(path) != textureCache.end()) continue; if (textureCache.find(key) != textureCache.end()) continue;
if (!blp.isValid()) { if (!blp.isValid()) {
// Don't poison cache with white on invalid preload; allow fallback // Don't poison cache with white on invalid preload; allow fallback
// path to retry loading this texture later. // path to retry loading this texture later.
@ -292,7 +307,7 @@ void TerrainRenderer::uploadPreloadedTextures(const std::unordered_map<std::stri
applyAnisotropicFiltering(); applyAnisotropicFiltering();
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
textureCache[path] = textureID; textureCache[key] = textureID;
} }
} }

View file

@ -1615,14 +1615,22 @@ GLuint WMORenderer::loadTexture(const std::string& path) {
return whiteTexture; return whiteTexture;
} }
auto normalizeKey = [](std::string key) {
std::replace(key.begin(), key.end(), '/', '\\');
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return key;
};
std::string key = normalizeKey(path);
// Check cache first // Check cache first
auto it = textureCache.find(path); auto it = textureCache.find(key);
if (it != textureCache.end()) { if (it != textureCache.end()) {
return it->second; return it->second;
} }
// Load BLP texture // Load BLP texture
pipeline::BLPImage blp = assetManager->loadTexture(path); pipeline::BLPImage blp = assetManager->loadTexture(key);
if (!blp.isValid()) { if (!blp.isValid()) {
core::Logger::getInstance().warning("WMO: Failed to load texture: ", path); core::Logger::getInstance().warning("WMO: Failed to load texture: ", path);
// Do not cache failures as white. MPQ reads can fail transiently // Do not cache failures as white. MPQ reads can fail transiently
@ -1654,7 +1662,7 @@ GLuint WMORenderer::loadTexture(const std::string& path) {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// Cache it // Cache it
textureCache[path] = textureID; textureCache[key] = textureID;
core::Logger::getInstance().debug("WMO: Loaded texture: ", path, " (", blp.width, "x", blp.height, ")"); core::Logger::getInstance().debug("WMO: Loaded texture: ", path, " (", blp.width, "x", blp.height, ")");
return textureID; return textureID;