#include "texture.hpp" #include #include #include #include #include "../log.hpp" using namespace glm; using namespace game::util; namespace game::resource { struct CachedTexture { std::weak_ptr idShared{}; glm::ivec2 size{}; int channels{}; }; static std::unordered_map textureCache{}; static bool cache_get(const std::string& key, std::shared_ptr& idShared, GLuint& id, ivec2& size, int& channels) { auto it = textureCache.find(key); if (it == textureCache.end()) return false; auto shared = it->second.idShared.lock(); if (!shared) { textureCache.erase(it); return false; } idShared = shared; id = *shared; size = it->second.size; channels = it->second.channels; return true; } static void cache_set(const std::string& key, const std::shared_ptr& idShared, ivec2 size, int channels) { if (!idShared) return; textureCache[key] = CachedTexture{.idShared = idShared, .size = size, .channels = channels}; } static std::shared_ptr texture_id_make(GLuint id) { return std::shared_ptr(new GLuint(id), [](GLuint* p) { if (!p) return; if (*p != 0) glDeleteTextures(1, p); delete p; }); } bool Texture::is_valid() const { return id != 0; } Texture::~Texture() { idShared.reset(); id = 0; } Texture::Texture(const Texture& other) { id = other.id; size = other.size; channels = other.channels; idShared = other.idShared; } Texture::Texture(Texture&& other) noexcept { id = other.id; size = other.size; channels = other.channels; idShared = std::move(other.idShared); other.id = 0; other.size = {}; other.channels = 0; } Texture& Texture::operator=(const Texture& other) { if (this != &other) { idShared = other.idShared; id = other.id; size = other.size; channels = other.channels; } return *this; } Texture& Texture::operator=(Texture&& other) noexcept { if (this != &other) { idShared.reset(); id = other.id; size = other.size; channels = other.channels; idShared = std::move(other.idShared); other.id = 0; other.size = {}; other.channels = 0; } return *this; } void Texture::init(const uint8_t* data) { idShared.reset(); id = 0; GLuint newId{}; glGenTextures(1, &newId); glBindTexture(GL_TEXTURE_2D, newId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glBindTexture(GL_TEXTURE_2D, 0); channels = CHANNELS; idShared = texture_id_make(newId); id = newId; } Texture::Texture(const std::filesystem::path& path) { auto pathString = path.string(); auto key = std::string("fs:") + pathString; if (cache_get(key, idShared, id, size, channels)) { logger.info(std::format("Using cached texture: {}", pathString)); return; } if (auto surface = SDL_LoadPNG(pathString.c_str())) { auto rgbaSurface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); SDL_DestroySurface(surface); surface = rgbaSurface; this->size = ivec2(surface->w, surface->h); init((const uint8_t*)surface->pixels); SDL_DestroySurface(surface); cache_set(key, idShared, this->size, channels); logger.info(std::format("Initialized texture: {}", pathString)); } else logger.error(std::format("Failed to initialize texture: {} ({})", pathString, SDL_GetError())); } Texture::Texture(const physfs::Path& path) { if (!path.is_valid()) { logger.error( std::format("Failed to initialize texture from PhysicsFS path: {}", path.c_str(), physfs::error_get())); return; } auto key = std::string("physfs:") + path.c_str(); if (cache_get(key, idShared, id, size, channels)) { logger.info(std::format("Using cached texture: {}", path.c_str())); return; } auto buffer = path.read(); if (buffer.empty()) { logger.error( std::format("Failed to initialize texture from PhysicsFS path: {} ({})", path.c_str(), physfs::error_get())); return; } auto ioStream = SDL_IOFromConstMem(buffer.data(), buffer.size()); if (auto surface = SDL_LoadPNG_IO(ioStream, true)) { auto rgbaSurface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); SDL_DestroySurface(surface); surface = rgbaSurface; this->size = ivec2(surface->w, surface->h); init((const uint8_t*)surface->pixels); SDL_DestroySurface(surface); cache_set(key, idShared, this->size, channels); logger.info(std::format("Initialized texture: {}", path.c_str())); } else logger.error(std::format("Failed to initialize texture: {} ({})", path.c_str(), SDL_GetError())); } }