406 lines
11 KiB
C++
406 lines
11 KiB
C++
// virtualtex.cpp
|
|
//
|
|
// Copyright (C) 2003, Chris Laurel
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
|
|
#include <cmath>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <celutil/debug.h>
|
|
#include <GL/glew.h>
|
|
#include <celutil/debug.h>
|
|
#include <celcompat/filesystem.h>
|
|
#include <celutil/filetype.h>
|
|
#include "parser.h"
|
|
#include "tokenizer.h"
|
|
#include "virtualtex.h"
|
|
|
|
|
|
using namespace std;
|
|
|
|
static const int MaxResolutionLevels = 13;
|
|
|
|
|
|
// Virtual textures are composed of tiles that are loaded from the hard drive
|
|
// as they become visible. Hidden tiles may be evicted from graphics memory
|
|
// to make room for other tiles when they become visible.
|
|
//
|
|
// The virtual texture consists of one or more levels of detail. Each level
|
|
// of detail is twice as wide and twice as high as the previous one, therefore
|
|
// having four times as many tiles. The height and width of each LOD must be
|
|
// a power of two, with width = 2 * height. The baseSplit determines the
|
|
// number of tiles at the lowest LOD. It is the log base 2 of the width in
|
|
// tiles of LOD zero. Though it's not required
|
|
|
|
static bool isPow2(int x)
|
|
{
|
|
return ((x & (x - 1)) == 0);
|
|
}
|
|
|
|
|
|
#if 0
|
|
// Useful if we want to use a packed quadtree to store tiles instead of
|
|
// the currently implemented tree structure.
|
|
static inline unsigned int lodOffset(unsigned int lod)
|
|
{
|
|
return ((1 << (lod << 1)) - 1) & 0xaaaaaaaa;
|
|
}
|
|
#endif
|
|
|
|
|
|
VirtualTexture::VirtualTexture(const fs::path& _tilePath,
|
|
unsigned int _baseSplit,
|
|
unsigned int _tileSize,
|
|
const string& _tilePrefix,
|
|
const string& _tileType) :
|
|
Texture(_tileSize << (_baseSplit + 1), _tileSize << _baseSplit),
|
|
tilePath(_tilePath),
|
|
tilePrefix(_tilePrefix),
|
|
baseSplit(_baseSplit),
|
|
tileSize(_tileSize),
|
|
ticks(0),
|
|
nResolutionLevels(0)
|
|
{
|
|
assert(tileSize != 0 && isPow2(tileSize));
|
|
tileTree[0] = new TileQuadtreeNode();
|
|
tileTree[1] = new TileQuadtreeNode();
|
|
tileExt = fmt::sprintf(".%s", _tileType);
|
|
populateTileTree();
|
|
|
|
if (DetermineFileType(tileExt) == Content_DXT5NormalMap)
|
|
setFormatOptions(Texture::DXT5NormalMap);
|
|
}
|
|
|
|
|
|
const TextureTile VirtualTexture::getTile(int lod, int u, int v)
|
|
{
|
|
tilesRequested++;
|
|
#if 0
|
|
cout << "getTile(" << lod << ", " << u << ", " << v << ")\n";
|
|
#endif
|
|
|
|
lod += baseSplit;
|
|
|
|
if (lod < 0 || (unsigned int) lod >= nResolutionLevels ||
|
|
u < 0 || u >= (2 << lod) ||
|
|
v < 0 || v >= (1 << lod))
|
|
{
|
|
return TextureTile(0);
|
|
}
|
|
|
|
TileQuadtreeNode* node = tileTree[u >> lod];
|
|
Tile* tile = node->tile;
|
|
unsigned int tileLOD = 0;
|
|
|
|
for (int n = 0; n < lod; n++)
|
|
{
|
|
unsigned int mask = 1 << (lod - n - 1);
|
|
unsigned int child = (((v & mask) << 1) | (u & mask)) >> (lod - n - 1);
|
|
//int child = (((v << 1) | u) >> (lod - n - 1)) & 3;
|
|
if (!node->children[child])
|
|
break;
|
|
|
|
node = node->children[child];
|
|
if (node->tile != nullptr)
|
|
{
|
|
tile = node->tile;
|
|
tileLOD = n + 1;
|
|
}
|
|
}
|
|
|
|
// No tile was found at all--not even the base texture was found
|
|
if (!tile)
|
|
return TextureTile(0);
|
|
|
|
// Make the tile resident.
|
|
unsigned int tileU = u >> (lod - tileLOD);
|
|
unsigned int tileV = v >> (lod - tileLOD);
|
|
makeResident(tile, tileLOD, tileU, tileV);
|
|
|
|
// It's possible that we failed to make the tile resident, either
|
|
// because the texture file was bad, or there was an unresolvable
|
|
// out of memory situation. In that case there is nothing else to
|
|
// do but return a texture tile with a null texture name.
|
|
if (!tile->tex)
|
|
return TextureTile(0);
|
|
|
|
// Set up the texture subrect to be the entire texture
|
|
float texU = 0.0f;
|
|
float texV = 0.0f;
|
|
float texDU = 1.0f;
|
|
float texDV = 1.0f;
|
|
|
|
// If the tile came from a lower LOD than the requested one,
|
|
// we'll only use a subsection of it.
|
|
unsigned int lodDiff = lod - tileLOD;
|
|
texDU = texDV = 1.0f / (float) (1 << lodDiff);
|
|
texU = (u & ((1 << lodDiff) - 1)) * texDU;
|
|
texV = (v & ((1 << lodDiff) - 1)) * texDV;
|
|
|
|
#if 0
|
|
cout << "Tile: " << tile->tex->getName() << ", " <<
|
|
texU << ", " << texV << ", " << texDU << ", " << texDV << '\n';
|
|
#endif
|
|
return TextureTile(tile->tex->getName(), texU, texV, texDU, texDV);
|
|
}
|
|
|
|
|
|
void VirtualTexture::bind()
|
|
{
|
|
// Treating a virtual texture like an ordinary one will not work; this is a
|
|
// weakness in the class hierarchy.
|
|
}
|
|
|
|
|
|
int VirtualTexture::getLODCount() const
|
|
{
|
|
return nResolutionLevels - baseSplit;
|
|
}
|
|
|
|
|
|
int VirtualTexture::getUTileCount(int lod) const
|
|
{
|
|
return 2 << (lod + baseSplit);
|
|
}
|
|
|
|
|
|
int VirtualTexture::getVTileCount(int lod) const
|
|
{
|
|
return 1 << (lod + baseSplit);
|
|
}
|
|
|
|
|
|
void VirtualTexture::beginUsage()
|
|
{
|
|
ticks++;
|
|
tilesRequested = 0;
|
|
}
|
|
|
|
|
|
void VirtualTexture::endUsage()
|
|
{
|
|
}
|
|
|
|
|
|
#if 0
|
|
unsigned int VirtualTexture::tileIndex(unsigned int lod,
|
|
unsigned int u, unsigned int v)
|
|
{
|
|
unsigned int lodBase = lodOffset(lod + baseSplit) - lodOffset(baseSplit);
|
|
return lodBase + (v << (lod << 1)) + u;
|
|
}
|
|
#endif
|
|
|
|
|
|
ImageTexture* VirtualTexture::loadTileTexture(unsigned int lod, unsigned int u, unsigned int v)
|
|
{
|
|
lod >>= baseSplit;
|
|
assert(lod < (unsigned)MaxResolutionLevels);
|
|
|
|
auto path = tilePath /
|
|
fmt::sprintf("level%d", lod) /
|
|
fmt::sprintf("%s%d_%d%s", tilePrefix, u, v, tileExt.string());
|
|
|
|
Image* img = LoadImageFromFile(path);
|
|
if (img == nullptr)
|
|
return nullptr;
|
|
|
|
ImageTexture* tex = nullptr;
|
|
|
|
// Only use mip maps for the LOD 0; for higher LODs, the function of mip
|
|
// mapping is built into the texture.
|
|
MipMapMode mipMapMode = lod == 0 ? DefaultMipMaps : NoMipMaps;
|
|
|
|
if (isPow2(img->getWidth()) && isPow2(img->getHeight()))
|
|
tex = new ImageTexture(*img, EdgeClamp, mipMapMode);
|
|
|
|
// TODO: Virtual textures can have tiles in different formats, some
|
|
// compressed and some not. The compression flag doesn't make much
|
|
// sense for them.
|
|
compressed = img->isCompressed();
|
|
|
|
delete img;
|
|
|
|
return tex;
|
|
}
|
|
|
|
|
|
void VirtualTexture::makeResident(Tile* tile, unsigned int lod, unsigned int u, unsigned int v)
|
|
{
|
|
if (tile->tex == nullptr && !tile->loadFailed)
|
|
{
|
|
// Potentially evict other tiles in order to make this one fit
|
|
tile->tex = loadTileTexture(lod, u, v);
|
|
if (tile->tex == nullptr)
|
|
{
|
|
// cout << "Texture load failed!\n";
|
|
tile->loadFailed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void VirtualTexture::populateTileTree()
|
|
{
|
|
// Count the number of resolution levels present
|
|
unsigned int maxLevel = 0;
|
|
|
|
// Crash potential if the tile prefix contains a %, so disallow it
|
|
string pattern;
|
|
if (tilePrefix.find('%') == string::npos)
|
|
pattern = tilePrefix + "%d_%d.";
|
|
|
|
for (int i = 0; i < MaxResolutionLevels; i++)
|
|
{
|
|
fs::path path = tilePath / fmt::sprintf("level%d", i);
|
|
if (fs::is_directory(path))
|
|
{
|
|
maxLevel = i + baseSplit;
|
|
int uLimit = 2 << maxLevel;
|
|
int vLimit = 1 << maxLevel;
|
|
|
|
for (auto& d : fs::directory_iterator(path))
|
|
{
|
|
int u = -1, v = -1;
|
|
if (sscanf(d.path().filename().string().c_str(), pattern.c_str(), &u, &v) == 2)
|
|
{
|
|
if (u >= 0 && v >= 0 && u < uLimit && v < vLimit)
|
|
{
|
|
// Found a tile, so add it to the quadtree
|
|
Tile* tile = new Tile();
|
|
addTileToTree(tile, maxLevel, (unsigned int) u, (unsigned int) v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nResolutionLevels = maxLevel + 1;
|
|
}
|
|
|
|
|
|
void VirtualTexture::addTileToTree(Tile* tile, unsigned int lod, unsigned int u, unsigned int v)
|
|
{
|
|
TileQuadtreeNode* node = tileTree[u >> lod];
|
|
|
|
for (unsigned int i = 0; i < lod; i++)
|
|
{
|
|
unsigned int mask = 1 << (lod - i - 1);
|
|
unsigned int child = (((v & mask) << 1) | (u & mask)) >> (lod - i - 1);
|
|
if (!node->children[child])
|
|
node->children[child] = new TileQuadtreeNode();
|
|
node = node->children[child];
|
|
}
|
|
#if 0
|
|
clog << "addTileToTree: " << node << ", " << lod << ", " << u << ", " << v << '\n';
|
|
#endif
|
|
|
|
// Verify that the tile doesn't already exist
|
|
if (!node->tile)
|
|
node->tile = tile;
|
|
}
|
|
|
|
|
|
static VirtualTexture* CreateVirtualTexture(Hash* texParams,
|
|
const fs::path& path)
|
|
{
|
|
string imageDirectory;
|
|
if (!texParams->getString("ImageDirectory", imageDirectory))
|
|
{
|
|
DPRINTF(LOG_LEVEL_ERROR, "ImageDirectory missing in virtual texture.\n");
|
|
return nullptr;
|
|
}
|
|
|
|
double baseSplit = 0.0;
|
|
if (!texParams->getNumber("BaseSplit", baseSplit) ||
|
|
baseSplit < 0.0 || baseSplit != floor(baseSplit))
|
|
{
|
|
DPRINTF(LOG_LEVEL_ERROR, "BaseSplit in virtual texture missing or has bad value\n");
|
|
return nullptr;
|
|
}
|
|
|
|
double tileSize = 0.0;
|
|
if (!texParams->getNumber("TileSize", tileSize))
|
|
{
|
|
DPRINTF(LOG_LEVEL_ERROR, "TileSize is missing from virtual texture\n");
|
|
return nullptr;
|
|
}
|
|
|
|
if (tileSize != floor(tileSize) ||
|
|
tileSize < 64.0 ||
|
|
!isPow2((int) tileSize))
|
|
{
|
|
DPRINTF(LOG_LEVEL_ERROR, "Virtual texture tile size must be a power of two >= 64\n");
|
|
return nullptr;
|
|
}
|
|
|
|
string tileType = "dds";
|
|
texParams->getString("TileType", tileType);
|
|
|
|
string tilePrefix = "tx_";
|
|
texParams->getString("TilePrefix", tilePrefix);
|
|
|
|
// if absolute directory notation for ImageDirectory used,
|
|
// don't prepend the current add-on path.
|
|
fs::path directory(imageDirectory);
|
|
|
|
if (directory.is_relative())
|
|
directory = path / directory;
|
|
return new VirtualTexture(directory,
|
|
(unsigned int) baseSplit,
|
|
(unsigned int) tileSize,
|
|
tilePrefix,
|
|
tileType);
|
|
}
|
|
|
|
|
|
static VirtualTexture* LoadVirtualTexture(istream& in, const fs::path& path)
|
|
{
|
|
Tokenizer tokenizer(&in);
|
|
Parser parser(&tokenizer);
|
|
|
|
if (tokenizer.nextToken() != Tokenizer::TokenName)
|
|
return nullptr;
|
|
|
|
string virtTexString = tokenizer.getNameValue();
|
|
if (virtTexString != "VirtualTexture")
|
|
return nullptr;
|
|
|
|
Value* texParamsValue = parser.readValue();
|
|
if (texParamsValue == nullptr || texParamsValue->getType() != Value::HashType)
|
|
{
|
|
DPRINTF(LOG_LEVEL_ERROR, "Error parsing virtual texture\n");
|
|
delete texParamsValue;
|
|
return nullptr;
|
|
}
|
|
|
|
Hash* texParams = texParamsValue->getHash();
|
|
|
|
VirtualTexture* virtualTex = CreateVirtualTexture(texParams, path);
|
|
delete texParamsValue;
|
|
|
|
return virtualTex;
|
|
}
|
|
|
|
|
|
VirtualTexture* LoadVirtualTexture(const fs::path& filename)
|
|
{
|
|
ifstream in(filename.string(), ios::in);
|
|
|
|
if (!in.good())
|
|
{
|
|
//DPRINTF(LOG_LEVEL_ERROR, "Error opening virtual texture file: %s\n", filename.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
return LoadVirtualTexture(in, filename.parent_path());
|
|
}
|