Zepha/src/game/atlas/TextureAtlas.cpp

178 lines
5.1 KiB
C++

#include <algorithm>
#include <stb_image.h>
#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<u8>(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<AtlasTexture> TextureAtlas::addDirectory(const std::filesystem::path& path, bool base) {
vec<AtlasTexture> 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<u8> 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<u8> 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<u16>(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<u16vec2> 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<u8>(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<u8> 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);
}