#include #include #include "TextureAtlas.h" #include "util/Log.h" #include "util/Util.h" #include "game/atlas/asset/AtlasTexture.h" TextureAtlas::TextureAtlas(uvec2 size) : canvasSize(size), canvasTileSize(size / 16u), tilesTotal(canvasTileSize.x * canvasTileSize.y), tiles(canvasTileSize.x * canvasTileSize.y, false) { // Get GPU texture capabilites and log it. i32 maxTexSize, texUnits; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); std::cout << Log::info << "This GPU's max texture size is: " << maxTexSize << "px^2." << Log::endl; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &texUnits); std::cout << Log::info << "This GPU supports " << texUnits << " texture units." << Log::endl; // Initialize the texture atlas. texture.loadFromBytes(&vec(size.x * size.y * 4)[0], size.x, size.y); createMissingTexture(); } void TextureAtlas::update() { if (texturesUnused.empty()) return; std::cout << texturesUnused.size() << std::endl; for (let& identifier : texturesUnused) { let& texture = textures.at(identifier); if (texture.shouldBeRemoved()) { std::cout << "removing " << identifier << std::endl; removeTexture(texture); } } texturesUnused.clear(); } vec TextureAtlas::addDirectory(const std::filesystem::path& path, bool base) { vec refs {}; for (let file : std::filesystem::recursive_directory_iterator(path)) if (file.path().extension() == ".png") refs.push_back(addFile(file.path(), base)); return refs; } AtlasTexture TextureAtlas::addFile(const std::filesystem::path& path, bool base) { i32 width, height; u8* rawData = stbi_load(path.string().data(), &width, &height, nullptr, 4); vec data(rawData, rawData + width * height * 4); free(rawData); let ref = addBytes(path.stem().string(), base, u16vec2(width, height), data); return ref; } AtlasTexture TextureAtlas::addBytes(const string& identifier, bool persistent, u16vec2 size, vec data) { let tileSize = u16vec2(glm::ceil(vec2(size) / 16.f)); let posOpt = findAtlasSpace(tileSize); std::cout << tileSize << ", " << identifier << std::endl; if (!posOpt) throw std::runtime_error("Failed to find space in the dynamic definition atlas."); u16vec2 pos = *posOpt * static_cast(16); tilesUsed += tileSize.x * tileSize.y; textures.erase(identifier); AtlasTexture ref(*this, identifier, pos, size, data); ref.setPersistent(persistent); if (!persistent) texturesUnused.insert(identifier); addTexture(ref); return ref; } AtlasTexture TextureAtlas::operator[](const string& identifier) { const let tex = get(identifier); if (tex.getIdentifier() != "_missing") return tex; const let data = texBuilder.build(identifier); let texture = addBytes(identifier, false, data.size, data.data); if (data.tintMask) texture.setTintData(*data.tintInd, addBytes(identifier + ":tint", false, data.size, *data.tintMask)); return texture; } AtlasTexture TextureAtlas::get(const string& identifier) const { if (textures.count(identifier)) return textures.at(identifier); return textures.at("_missing"); } const uvec2 TextureAtlas::getCanvasSize() { return canvasSize; } optional TextureAtlas::findAtlasSpace(u16vec2 tileSize) { for (u16 j = 0; j < canvasTileSize.y - (tileSize.y - 1); j++) { for (u16 i = 0; i < canvasTileSize.x - (tileSize.x - 1); i++) { bool space = true; for (u16 k = 0; k < tileSize.y; k++) { for (u16 l = 0; l < tileSize.x; l++) { if (tiles[(j + k) * canvasTileSize.x + (i + l)]) { space = false; break; } } if (!space) break; } if (space) { for (u16 k = 0; k < tileSize.y; k++) for (u16 l = 0; l < tileSize.x; l++) tiles[(j + k) * canvasTileSize.x + (i + l)] = true; return u16vec2(i, j); } } } return {}; } void TextureAtlas::createMissingTexture() { let data = vec(16 * 4 * 16); for (u16 i = 0; i < 16 * 16; i++) { u8 m = 0; if ((i % 16 < 8) ^ ((i / 16) < 8)) m = 255; data[i * 4 + 0] = m; data[i * 4 + 1] = 0; data[i * 4 + 2] = m; data[i * 4 + 3] = 255; } addBytes("_missing", true, u16vec2(16), data); } void TextureAtlas::addTexture(const AtlasTexture& tex) { let pos = tex.getPos(); let size = tex.getSize(); textures.insert({ tex.getIdentifier(), tex }); texture.updateTexture(pos.x, pos.y, size.x, size.y, tex.getBytes().data()); } void TextureAtlas::removeTexture(const AtlasTexture& tex) { textures.erase(tex.getIdentifier()); let tilePos = tex.getTilePos(); let tileSize = tex.getTileSize(); tilesUsed -= tileSize.x * tileSize.y; for (u16 x = tilePos.x; x < tilePos.x + tileSize.x; x++) for (u16 y = tilePos.y; y < tilePos.y + tileSize.y; y++) tiles[y * canvasTileSize.x + x] = false; // For debugging vec clear(tileSize.x * 16 * tileSize.y * 16 * 4); texture.updateTexture(tilePos.x * 16, tilePos.y * 16, tileSize.x * 16, tileSize.y * 16, clear.data()); } const u32 TextureAtlas::getTilesUsed() { return tilesUsed; } const u32 TextureAtlas::getTilesTotal() { return tilesTotal; } const Texture& TextureAtlas::getTexture() { return texture; } void TextureAtlas::alertUnused(const string& identifier) { texturesUnused.insert(identifier); }