Files
CDAG/Research/scene/TextureCompressor/PaletteBlockTexture.h

1002 lines
38 KiB
C++

#pragma once
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <math.h>
#include "CompressedTexture.h"
#include "BasicTexture.h"
#include "BlockHashers.h"
#include "../Material/Block.h"
#include "../../core/CollectionHelper.h"
#include "../../core/Util/BoolArray.h"
#include "../../core/Serializer.h"
#include "../../inc/tbb/parallel_for.h"
#include "../../inc/tbb/concurrent_queue.h"
#include "../../inc/tbb/parallel_for_each.h"
template<typename T>
class PaletteBlockTexture : public CompressedTexture < T >
{
private:
const size_t MAX_STEP_SIZE = 20000000;
unsigned64 mOriginalSize;
std::unordered_map<unsigned32, unsigned32> mFirstBlockPointers;
unsigned8 mFirstBlockPointerSize;
// The pointers to the location in mBlockPalettePointers where the current block starts (one per block)
std::vector<unsigned64> mBlockPointers;
// The pointers to the palettes (one per block)
std::vector<unsigned32> mPalettePointers;
// All palettes
std::vector<Block<T>> mBlockPalettes;
// Dictionary for quick finding of palette indices
std::unordered_map<Block<T>, unsigned32> mPaletteIndices;
// All palette pointers (as big as the original texture). Use block pointers to find out the index of the block, and therefore the accompanying palette
std::vector<std::vector<unsigned8>> mPackedBlockPalettePointers;
// Mask used for all original materials
unsigned32 mPaletteMask;
// Bitmap corresponding to the current paletteMask
std::vector<unsigned8> mPaletteBitMap;
void RebuildPaletteIndices()
{
mPaletteIndices.clear();
for (unsigned32 i = 0; i < mBlockPalettes.size(); i++)
mPaletteIndices.insert(std::pair<Block<T>, unsigned32>(mBlockPalettes[i], i));
}
void SetPaletteMask(unsigned32 mask)
{
mPaletteMask = mask;
mPaletteBitMap = BitHelper::GetBitMapHS(mask);
}
// Adds the palette to the block palettes (and dictionary)
// If the palette already exists, the pointer to the existing palette is returned
unsigned32 AddPalette(const Block<T>& palette)
{
// Check if the palette already exists and return a pointer to it if it does
auto existingPalette = mPaletteIndices.find(palette);
if (existingPalette != mPaletteIndices.end())
return existingPalette->second;
// Otherwise, add the palette
unsigned32 index = (unsigned32)mBlockPalettes.size();
mBlockPalettes.push_back(palette);
mPaletteIndices.insert(std::pair<Block<T>, unsigned32>(palette, index));
return index;
}
inline float AverageBitSize(unsigned8 pointerBits, size_t blockSize)
{
return
((float)(64 + // Bits for the palette and block pointers (32 + 32)
(GetBitsPerFullMaterial() * BitHelper::Exp2(pointerBits)) + // Bits for the palette
BitHelper::RoundToBytes(blockSize * pointerBits))) / // Bits for the palette pointers (round to bytes since pointers should be available)
((float)blockSize); // Divide by the total number of elements in the block
}
inline unsigned8 OptimalBitCount(const std::vector<size_t>& blockSizes)
{
// Optimal palette size is calculated greedily, by whatever size requires the least bits per material
unsigned8 bitsPerFullMaterial = GetBitsPerFullMaterial();
float minBitsPerMaterial = (float)bitsPerFullMaterial; // Set it to the bits per full material first. Note that in this case, the size for the palette and block pointers are ignored.
unsigned8 optimalSize = bitsPerFullMaterial;
for (unsigned8 i = 0; i < (unsigned8)blockSizes.size(); i++)
{
unsigned8 bitCount = i + 1;
float requiredBits = AverageBitSize(bitCount, blockSizes[i]);
if (requiredBits < minBitsPerMaterial)
{
optimalSize = bitCount;
minBitsPerMaterial = requiredBits;
}
}
return optimalSize;
}
inline unsigned8 GetBitsPerFullMaterial() const
{
return std::max((unsigned8)1, BitHelper::GetSet(mPaletteMask));
}
inline size_t GetBlockPalettesSize() const
{
size_t res = 0;
for (auto it = mBlockPalettes.begin() + 1; it != mBlockPalettes.end(); ++it)
if (it->size() > 1)
res += (*it).size();
return res;
}
inline size_t GetPalettesByteSize() const
{
size_t palettesByteSize = GetBlockPalettesSize() * GetBitsPerFullMaterial();
palettesByteSize = BitHelper::RoundToBytes(palettesByteSize);
return palettesByteSize / 8;
}
inline unsigned8 GetPaletteBitSize(const size_t& i) const
{
// The first palette is always the "full color" palette. Calculate the actual bitsize for all other palettes
return (i == 0) ? GetBitsPerFullMaterial() : BitHelper::Log2Ceil(mBlockPalettes[i].size());
}
inline unsigned8 GetBlockPalettePointerBitSize(const size_t& i) const
{
return GetPaletteBitSize(mPalettePointers[i]);
}
// Returns the index of the block in which the material at materialIndex is
inline unsigned64 GetBlockIndex(const unsigned64& materialIndex) const
{
if (mBlockPointers.empty()) return 0;
//if (materialIndex == 0) return 0;
auto curBlockIt = std::upper_bound(mBlockPointers.begin(), mBlockPointers.end(), materialIndex);
return (curBlockIt - mBlockPointers.begin()) - 1;
}
// Returns the end material index of block i (e.g. the index where the next block starts)
inline unsigned64 GetBlockEndIndex(const unsigned64& i) const
{
return ((i + 1 >= mBlockPointers.size()) ? size() : mBlockPointers[i + 1]);
}
// Returns the size of block i
inline unsigned64 GetBlockSize(const unsigned64& i) const
{
return GetBlockEndIndex(i) - mBlockPointers[i];
}
// Returns the size of block i in terms of bytes (e.g. blockSize * blockBitSize)
inline unsigned64 GetBlockByteSize(const unsigned64& i) const
{
unsigned64 res = GetBlockSize(i) * GetBlockPalettePointerBitSize(i);
res = BitHelper::RoundToBytes(res);
return res >> 3;
}
// Returns the sum of all block sizes in bytes
inline unsigned64 GetBlocksByteSize() const
{
size_t res = 0;
for (size_t i = 0; i < mBlockPointers.size(); i++)
res += GetBlockByteSize(i);
return res;
}
inline unsigned8 GetOriginalPointerSize() const
{
return BitHelper::RoundToBytes(BitHelper::Log2Ceil(mOriginalSize)) / 8;
}
inline unsigned8 GetPalettePointerSize() const
{
size_t palettesSize = GetBlockPalettesSize();
return BitHelper::RoundToBytes(BitHelper::Log2Ceil(palettesSize) + 3) / 8;
}
inline unsigned8 GetBlockPointerSize() const
{
size_t paletteByteSize = GetPalettesByteSize();
size_t blocksByteSize = GetBlocksByteSize();
return BitHelper::RoundToBytes(BitHelper::Log2Ceil(paletteByteSize + blocksByteSize)) / 8;
}
void RebuildFirstBlock(const std::vector<T>& materialPointers, size_t fromIndex = 0)
{
// The first block should contain all colors, so that it can be picked whenever storing a palette takes up too much memory.
// First copy the existing first block
std::vector<T> firstBlock;
if (!mBlockPalettes.empty() && fromIndex > 0)
{
const std::vector<T>& curFirstBlock = mBlockPalettes[0].GetData();
firstBlock.resize(curFirstBlock.size());
std::copy(curFirstBlock.begin(), curFirstBlock.end(), firstBlock.begin());
// Remove existing value from palette indices
mPaletteIndices.erase(mBlockPalettes[0]);
}
// TODO: Do something faster then another full scan of the material pointers...
// Add the new material pointers to the new first block
size_t existingFirstBlockSize = firstBlock.size();
firstBlock.resize(firstBlock.size() + materialPointers.size());
std::copy(materialPointers.begin(), materialPointers.end(), firstBlock.begin() + existingFirstBlockSize);
// Build the new first block by taking all unique materials
CollectionHelper::Unique(firstBlock, [](const T& x, const T& y) { return ((unsigned)x) < ((unsigned)y); });
mFirstBlockPointerSize = BitHelper::Log2Ceil(firstBlock.size());
// Build a dictionary to quickly find materials in the new first block
mFirstBlockPointers.clear();
for (unsigned32 i = 0; i < firstBlock.size(); i++)
mFirstBlockPointers.insert(std::make_pair((unsigned32)firstBlock[i], i));
// Add the firstblock as the first entry in the blockpalettes
Block<T> firstBlockPalette = Block<T>(firstBlock);
if (mBlockPalettes.empty())
mBlockPalettes.push_back(firstBlockPalette);
else
{
// If the firstblock changes, all blocks that used the first block (before fromIndex) should be updated accordingly
// To do this, we build a dictionary that maps the firstBlock indices from the old to the new firstblock
std::unordered_map<unsigned32, unsigned32> oldToNewFirstBlock;
const Block<T>& oldFirstBlock = mBlockPalettes[0];
for (unsigned32 i = 0; i < (unsigned32)oldFirstBlock.size(); i++)
oldToNewFirstBlock.insert(std::make_pair(i, mFirstBlockPointers[oldFirstBlock[i]]));
unsigned32 oldFirstBlockPointerMask = BitHelper::GetLSMask<unsigned32>(0, BitHelper::Log2Ceil(oldFirstBlock.size()));
std::vector<unsigned8> oldFirstBlockBitMap = BitHelper::GetBitMapHS(oldFirstBlockPointerMask);
unsigned32 newFirstBlockPointerMask = BitHelper::GetLSMask<unsigned32>(0, mFirstBlockPointerSize);
std::vector<unsigned8> newFirstBlockBitMap = BitHelper::GetBitMapHS(newFirstBlockPointerMask);
// And then replace all pointers to the old first block to pointers to the new first block
for (size_t i = 0; i < mBlockPointers.size(); i++)
{
if (mPalettePointers[i] != 0) continue;
if (mBlockPointers[i] >= fromIndex) break;
unsigned64 blockSize = std::min(GetBlockEndIndex(i), (unsigned64)fromIndex) - mBlockPointers[i];
std::vector<unsigned32> unpackedOldBlock(blockSize);
// Unpack the block:
for (size_t j = 0; j < blockSize; j++)
unpackedOldBlock[j] = oldToNewFirstBlock[BitHelper::UnpackTightAt<unsigned8, unsigned32>(mPackedBlockPalettePointers[i], j, oldFirstBlockBitMap)];
mPackedBlockPalettePointers[i] = BitHelper::PackTight<unsigned8, unsigned32>(unpackedOldBlock, newFirstBlockBitMap);
}
// Replace the first block palette by the new block palette
mBlockPalettes[0] = firstBlockPalette;
}
// Update the palette indices
mPaletteIndices.insert(std::pair<Block<T>, unsigned32>(firstBlockPalette, 0));
// Calculate the first block mask
unsigned32 firstBlockMask = 0;
for (T v : firstBlock)
firstBlockMask |= (unsigned32)v;
SetPaletteMask(firstBlockMask);
}
struct TextureTreeNode
{
unsigned64 blockStart;
unsigned64 blockEnd;
std::vector<T> palette;
TextureTreeNode(unsigned64 start, unsigned64 end, T material)
{
blockStart = start;
blockEnd = end;
palette.push_back(material);
}
TextureTreeNode(unsigned64 start, unsigned64 end, std::vector<TextureTreeNode*> children)
{
blockStart = start;
blockEnd = end;
//this->children = children;
for (auto child : children)
palette.insert(palette.end(), child->palette.begin(), child->palette.end());
CollectionHelper::UniqueSerial(palette, [](const T& a, const T& b) { return (unsigned32)a < (unsigned32)b; });
}
TextureTreeNode(unsigned64 start, unsigned64 end, std::unordered_set<T> palette)
{
blockStart = start;
blockEnd = end;
//this->children = children;
for (auto mat : palette)
this->palette.push_back(mat);
}
unsigned64 Size() const { return blockEnd - blockStart; }
void AddChild(TextureTreeNode& child)
{
palette.insert(palette.end(), child.palette.begin(), child.palette.end());
CollectionHelper::UniqueSerial(palette);
}
};
struct PaletteBlock
{
unsigned64 blockStart;
unsigned64 blockEnd;
Block<T> palette;
bool fullPalette;
PaletteBlock() {}
PaletteBlock(unsigned64 start, unsigned64 end, const Block<T>& palette)
{
blockStart = start;
blockEnd = end;
this->palette = palette;
fullPalette = false;
}
PaletteBlock(unsigned64 start, unsigned64 end)
{
blockStart = start;
blockEnd = end;
fullPalette = true;
}
unsigned64 Size() const { return blockEnd - blockStart; }
};
void AddBlocks(const std::vector<PaletteBlock>& blocks, const std::vector<T>& fullMaterialPointers, unsigned64 fromIndex)
{
unsigned64 coveredBlocks = 0, coveredCells = 0, uncoveredBlocks = 0, uncoveredCells = 0;
// Analyze the blocks:
for (const PaletteBlock& block : blocks)
{
if (block.fullPalette)
{
uncoveredBlocks++;
uncoveredCells += block.Size();
}
else
{
coveredBlocks++;
coveredCells += block.Size();
}
}
printf("Covered blocks: %llu, cells: %llu, uncovered blocks: %llu, cells: %llu\n", coveredBlocks, coveredCells, uncoveredBlocks, uncoveredCells);
std::unordered_map<unsigned32, unsigned32> blockInPalettePointers;
for (auto block : blocks)
{
unsigned8 blockBitCount = block.fullPalette ? BitHelper::Log2Ceil(mBlockPalettes[0].size()) : BitHelper::Log2Ceil(block.palette.size());
// Create the block palette (if necessary)
unsigned32 paletteIdx = 0;
if (!block.fullPalette)
{
Block<T> palette = block.palette;
palette.Sort([](const T& x, const T& y) { return ((unsigned)x) < ((unsigned)y); });
paletteIdx = AddPalette(palette);
}
// Add the block and palette pointers
mBlockPointers.push_back(block.blockStart + fromIndex);
mPalettePointers.push_back((unsigned32)paletteIdx);
// Add the in-palette pointers to the materials to mPackedBlockPalettePointers
// First build a dictionary that maps materials to in-palette pointers
std::unordered_map<unsigned32, unsigned32>* inPalettePointers = &mFirstBlockPointers;
if (!block.fullPalette)
{
const Block<T>& palette = mBlockPalettes[paletteIdx];
blockInPalettePointers.clear();
for (unsigned32 j = 0; j < palette.size(); j++)
blockInPalettePointers.insert(std::pair<unsigned32, unsigned32>((unsigned32)palette[j], j));
inPalettePointers = &blockInPalettePointers;
}
// Then find the correct in-palette pointer for each material and build an (unpacked) vector containing it
if (blockBitCount > 0)
{
std::vector<unsigned32> unpackedBlockPointers(block.blockEnd - block.blockStart);
for (size_t j = block.blockStart; j < block.blockEnd; j++)
unpackedBlockPointers[j - block.blockStart] = inPalettePointers->operator[]((unsigned32)fullMaterialPointers[j]);
unsigned32 paletteMask = BitHelper::GetLSMask<unsigned32>(0, blockBitCount);
mPackedBlockPalettePointers.push_back(BitHelper::PackTight<unsigned8, unsigned32>(unpackedBlockPointers, paletteMask));
}
else
{
// If the palette pointers take 0 bits, just push an empty array.
mPackedBlockPalettePointers.push_back(std::vector<unsigned8>());
}
}
}
std::vector<PaletteBlock> BuildBlocks(std::vector<T>& fullMaterials, unsigned8 greedyUntilBits = 3)
{
// Build the texture tree leaf nodes, which are all blocks that can be made with a single material
std::vector<TextureTreeNode*> nodesToProcess;
unsigned64 curStartIndex = 0;
T curMaterial = fullMaterials[0];
for (size_t i = 0; i < fullMaterials.size(); i++)
{
if (fullMaterials[i] != curMaterial)
{
nodesToProcess.push_back(new TextureTreeNode(curStartIndex, i, curMaterial));
curMaterial = fullMaterials[i];
curStartIndex = i;
}
}
nodesToProcess.push_back(new TextureTreeNode(curStartIndex, fullMaterials.size(), curMaterial));
std::vector<PaletteBlock> blocks;
BoolArray covered(fullMaterials.size());
unsigned8 bitsPerFullMaterial = GetBitsPerFullMaterial();
unsigned8 maxSizeToTry = std::min<unsigned8>(greedyUntilBits, bitsPerFullMaterial - 1); // bits
std::vector<TextureTreeNode*> curBitNodes;
std::vector<size_t> blocksPerBitsize(maxSizeToTry + 1);
size_t steps = (nodesToProcess.size() / MAX_STEP_SIZE) + (nodesToProcess.size() % MAX_STEP_SIZE != 0 ? 1 : 0);
size_t stepSize = nodesToProcess.size() / steps;
// Calculate the sizes of the blocks to process
for (size_t step = 0; step < steps; step++)
{
size_t stepStart = step * stepSize;
size_t stepEnd = std::min(nodesToProcess.size(), (step + 1) * stepSize);
std::vector<TextureTreeNode*> nodesLeft(nodesToProcess.begin() + stepStart, nodesToProcess.begin() + stepEnd);
printf("stepSize: %llu, stepStart: %llu, stepEnd: %llu, totalSize: %llu.\n", (unsigned64)stepSize, (unsigned64)stepStart, (unsigned64)stepEnd, (unsigned64)nodesToProcess.size());
for (unsigned8 bitSize = 0; bitSize <= maxSizeToTry; bitSize++)
{
if (nodesLeft.empty()) break;
unsigned64 bucketSize = BitHelper::Exp2(bitSize);
// Result of solving the average size so that it is smaller than bitSize + 1 bits.
// This means solving the following equation for z
// (96 + (x * 2^y) + (z * y)) / z = y + 1
// where x = bitsPerFullMaterial, y = bitSize, z = blockSize
// which gives:
// z = 96 + (x * 2^y)
// This makes sense: since each element in a block will take 1 bit more with the next bitsize, the critical size is reached when
// the number of block pointers is equal to the header of the block in bits.
unsigned64 criticalSize = 96 + bitsPerFullMaterial * bucketSize;
curBitNodes.clear();
// Build all possible sets of nodes that fit in the current bit size
if (bitSize > 0)
{
unsigned64 checkUntil = nodesLeft.size();
unsigned64 lastBlockStart = ~((unsigned64)0);
// Find all (unique) block starts:
std::vector<unsigned64> blockStarts;
for (unsigned64 i = 0; i < checkUntil; i++)
{
unsigned64 blockStart = nodesLeft[i]->blockStart;
if (lastBlockStart == blockStart) continue;
lastBlockStart = blockStart;
blockStarts.push_back(i);
}
nodesLeft.resize(nodesLeft.size() + blockStarts.size());
tbb::parallel_for((unsigned64)0, (unsigned64)blockStarts.size(), [&](const unsigned64& i)
//for (size_t i = 0; i < blockStarts.size(); i++)
{
TextureTreeNode* curNode = nodesLeft[blockStarts[i]];
std::unordered_set<T> curPalette;
for (T material : curNode->palette)
curPalette.insert(material);
unsigned64 curBlockEnd = curNode->blockEnd;
unsigned64 blockStartIdx = i + 1;
while (blockStartIdx < blockStarts.size())
{
unsigned64 j = blockStarts[blockStartIdx];
unsigned64 nextBlockStart = blockStartIdx + 1 < blockStarts.size() ? blockStarts[blockStartIdx + 1] : checkUntil;
// If there is a gap between this block and the next block, break;
if (nodesLeft[j]->blockStart > curBlockEnd) break;
while (j < nextBlockStart)
{
TextureTreeNode* potential = nodesLeft[j];
std::vector<T> newMaterials;
for (T mat : potential->palette)
if (curPalette.find(mat) == curPalette.end())
newMaterials.push_back(mat);
if (curPalette.size() + newMaterials.size() <= bucketSize)
{
for (auto newMaterial : newMaterials)
curPalette.insert(newMaterial);
curBlockEnd = potential->blockEnd;
blockStartIdx++;
break;
}
// If the palette of this block is too big, try the next one, it has a smaller palette and might fit
j++;
}
blockStartIdx++;
}
nodesLeft[checkUntil + i] = new TextureTreeNode(curNode->blockStart, curBlockEnd, /*children,*/ curPalette);
});
}
// Now that we've build all possible blocks, find all blocks that are bigger than the criticalSize, and create them
// Sort blocks, first on palette size, then on block size
tbb::parallel_sort(nodesLeft.begin(), nodesLeft.end(), [](TextureTreeNode* a, TextureTreeNode* b)
{
unsigned64 sizeA = a->Size();
unsigned64 sizeB = b->Size();
if (sizeA != sizeB) return sizeA > sizeB;
return a->blockStart < b->blockStart;
});
TextureTreeNode* curNode = nodesLeft[0];
size_t i = 0;
while (curNode->Size() >= criticalSize && i < nodesLeft.size())
{
curNode = nodesLeft[i];
// If the curNode isn't covered yet, build a block out of it and add it to the palette blocks
if (!covered.Any(curNode->blockStart, curNode->blockEnd))
{
blocksPerBitsize[bitSize]++;
blocks.push_back(PaletteBlock(curNode->blockStart, curNode->blockEnd, Block<T>(curNode->palette)));
covered.SetRange(curNode->blockStart, curNode->blockEnd, true);
}
i++;
}
// Remove all covered (and partly covered) nodes
std::vector<TextureTreeNode*> newNodesLeft;
for (size_t i = 0; i < nodesLeft.size(); i++)
{
curNode = nodesLeft[i];
if (covered.Any(curNode->blockStart, curNode->blockEnd))
delete curNode;
else
newNodesLeft.push_back(curNode);
}
nodesLeft = newNodesLeft;
// Sort the nodes, first on block start (ascending), then on palette size (descending)
tbb::parallel_sort(nodesLeft.begin(), nodesLeft.end(), [](TextureTreeNode* a, TextureTreeNode* b)
{
if (a->blockStart != b->blockStart) return a->blockStart < b->blockStart;
return a->Size() > b->Size();
});
}
CollectionHelper::PrintContents(blocksPerBitsize);
tbb::parallel_for_each(nodesLeft.begin(), nodesLeft.end(), [](TextureTreeNode* node) { delete node; });
nodesLeft.clear();
nodesLeft.shrink_to_fit();
}
// Create blocks for the uncovered parts of the scene using the serial block builder algorithm
size_t blockStart = 0;
bool inBlock = false;
for (size_t i = 0; i < fullMaterials.size(); i++)
{
if (!covered.Get(i))
{
if (!inBlock)
{
blockStart = i;
inBlock = true;
}
}
else
{
if (inBlock)
{
std::vector<PaletteBlock> fillerBlocks = SerialBuildBlocks(fullMaterials, blockStart, i, false);
unsigned64 fillerBlocksIndex = blocks.size();
blocks.resize(fillerBlocksIndex + fillerBlocks.size());
std::move(fillerBlocks.begin(), fillerBlocks.end(), blocks.begin() + fillerBlocksIndex);
inBlock = false;
}
}
}
if (inBlock)
{
std::vector<PaletteBlock> fillerBlocks = SerialBuildBlocks(fullMaterials, blockStart, fullMaterials.size(), false);
unsigned64 fillerBlocksIndex = blocks.size();
blocks.resize(fillerBlocksIndex + fillerBlocks.size());
std::move(fillerBlocks.begin(), fillerBlocks.end(), blocks.begin() + fillerBlocksIndex);
}
// Sort the blocks
tbb::parallel_sort(blocks.begin(), blocks.end(), [](const PaletteBlock& a, const PaletteBlock& b) { return a.blockStart < b.blockStart; });
return blocks;
}
std::vector<PaletteBlock> SerialBuildBlocks(std::vector<T>& fullMaterialPointers, unsigned64 start = 0, unsigned64 end = ~(unsigned64)0, bool verbose = true)
{
// Algorithm:
// Check for palette sizes of 2, 4, 8, 16, 32, 64, 128, 256 colors how big the block can be.
// Somehow determine the most efficient size pointer (1 - 8 bits)
// Create a block of that size.
if (end > fullMaterialPointers.size()) end = fullMaterialPointers.size();
std::vector<PaletteBlock> blocks;
unsigned64 curBlockStart = start;
unsigned64 lastBlockStart = curBlockStart;
unsigned8 bitsPerFullMaterial = GetBitsPerFullMaterial();
unsigned8 maxSizeToTry = std::min(8, bitsPerFullMaterial - 1); // bits
if (maxSizeToTry == 0) maxSizeToTry = 1;
size_t maxMaterialsPerBlock = size_t(1) << maxSizeToTry;
std::vector<size_t> blocksPerBitsize(maxSizeToTry);
while (curBlockStart < end)
{
if (verbose)
{
size_t curPercentage = (size_t)((curBlockStart - start) * 100) / (end - start);
size_t lastPercentage = (size_t)((lastBlockStart - start) * 100) / (end - start);
if (curPercentage > lastPercentage) printf("%u %%\n", (unsigned32)curPercentage);
}
lastBlockStart = curBlockStart;
std::vector<size_t> blockSizes; // Store the block sizes per amount of bits in the palette pointer
std::vector<T> materialsInBlock;
std::unordered_set<unsigned32> materialsInBlockSet;
std::vector<Block<T>> blockSizePalettes; // The palettes for each of the block counts
size_t i = curBlockStart;
unsigned8 lastFilledBitCount = 0;
// Keep moving through the material pointers until the maximum number of materials has been reached
while (materialsInBlock.size() <= maxMaterialsPerBlock && i < end)
{
T& curMaterial = fullMaterialPointers[i];
unsigned32 curMaterialInt = (unsigned32)curMaterial;
// Check if this material is already in the materials
bool materialAlreadyInBlock = materialsInBlockSet.find(curMaterialInt) != materialsInBlockSet.end();
if (!materialAlreadyInBlock)
{
// Check if the limit for a certain number of bits is reached when the new material is added
for (unsigned8 bitCount = 1; bitCount <= maxSizeToTry; bitCount++)
if (materialsInBlock.size() == (((size_t)1) << bitCount))
{
lastFilledBitCount = bitCount;
blockSizes.push_back(i - curBlockStart);
blockSizePalettes.push_back(Block<T>(materialsInBlock));
}
// Add the new material
materialsInBlock.push_back(curMaterial);
materialsInBlockSet.insert((unsigned32)curMaterial);
}
i++;
}
// If we're at the end of the material, fill the rest of the blockSizes with the current block size
if (lastFilledBitCount < maxSizeToTry)
{
// Add some dummy materials to the block so that it is big enough
materialsInBlock.resize(((size_t)1) << maxSizeToTry);
for (unsigned8 bitCount = lastFilledBitCount + 1; bitCount <= maxSizeToTry; bitCount++)
{
blockSizes.push_back(i - curBlockStart);
blockSizePalettes.push_back(Block<T>(materialsInBlock, 0, ((size_t)1) << bitCount));
}
}
unsigned8 optimalBitCount = OptimalBitCount(blockSizes);
// If the optimalBitCount gives no compression, pick the halfway blocksize (so as not to skip to much indexes than can potentially be compressed better)
size_t blockSize;
if (optimalBitCount == bitsPerFullMaterial)
blockSize = blockSizes[blockSizes.size() / 2];
else
blockSize = blockSizes[optimalBitCount - 1];
if (optimalBitCount == bitsPerFullMaterial)
blocks.push_back(PaletteBlock(curBlockStart, curBlockStart + blockSize));
else
blocks.push_back(PaletteBlock(curBlockStart, curBlockStart + blockSize, blockSizePalettes[optimalBitCount - 1]));
// Update the block starts
curBlockStart += blockSize;
}
return blocks;
}
public:
PaletteBlockTexture() :
mOriginalSize(0),
mFirstBlockPointers(std::unordered_map<unsigned32, unsigned32>()),
mFirstBlockPointerSize(0),
mBlockPointers(std::vector<unsigned64>()),
mPalettePointers(std::vector<unsigned32>()),
mBlockPalettes(std::vector<Block<T>>()),
mPaletteIndices(std::unordered_map<Block<T>, unsigned32>()),
mPackedBlockPalettePointers(std::vector<std::vector<unsigned8>>()),
mPaletteMask(0),
mPaletteBitMap(std::vector<unsigned8>())
{}
~PaletteBlockTexture() override {}
T operator[](size_t i) const override
{
unsigned64 blockId = GetBlockIndex(i);
const Block<T>& palette = mBlockPalettes[mPalettePointers[blockId]];
unsigned8 bitSize = BitHelper::Log2Ceil(palette.size());
unsigned32 blockPaletteId = 0;
if (bitSize > 0) // if bitSize == 0, then the palette is only one color big, so the blockPaletteId is always 0.
{
unsigned32 paletteMask = BitHelper::GetLSMask<unsigned32>(0, bitSize);
blockPaletteId = BitHelper::UnpackTightAt<unsigned8, unsigned32>(mPackedBlockPalettePointers[blockId], i - mBlockPointers[blockId], paletteMask);
}
return palette[blockPaletteId];
}
unsigned64 size() const override { return mOriginalSize; }
// Returns the (unpacked) material indices from the given index
std::vector<T> GetTexture(size_t fromIndex = 0) override
{
//printf("fromIndex=%u, size=%u \n", fromIndex, size());
assert(fromIndex <= size());
std::vector<T> res(size() - fromIndex);
tbb::parallel_for(fromIndex, (size_t)size(), [&](size_t i)
{
res[i - fromIndex] = this->operator[](i);
});
return res;
}
void SetTexture(const std::vector<T>& materialPointers, size_t fromIndex = 0) override
{
//// Debug: just repack everything if fromIndex != 0
//if (fromIndex != 0)
//{
// std::vector<T> existingMaterials = GetTexture();
// std::vector<T> allMaterialPointers(fromIndex + materialPointers.size());
// std::move(existingMaterials.begin(), existingMaterials.begin() + fromIndex, allMaterialPointers.begin());
// std::copy(materialPointers.begin(), materialPointers.end(), allMaterialPointers.begin() + fromIndex);
// SetTexture(allMaterialPointers);
// return;
//}
// Rebuild the first block to also contain all new materials
RebuildFirstBlock(materialPointers, fromIndex);
// Remove all data after fromIndex
if (!mBlockPointers.empty())
{
unsigned64 switchBlockId = GetBlockIndex(fromIndex);
size_t switchBlockWantedSize = fromIndex - mBlockPointers[switchBlockId];
if (switchBlockWantedSize == 0)
switchBlockId--;
else
{
size_t switchBlockBitSize = BitHelper::Log2Ceil(mBlockPalettes[mPalettePointers[switchBlockId]].size());
mPackedBlockPalettePointers[switchBlockId].resize(BitHelper::RoundToBytes(switchBlockWantedSize * switchBlockBitSize) / 8);
}
// Remove the block pointers and block palette pointers that point to locations after fromIndex
unsigned64 blockPointerEndIndex = switchBlockId + 1;
if (blockPointerEndIndex < mBlockPointers.size())
{
mBlockPointers.resize(blockPointerEndIndex);
mPalettePointers.resize(blockPointerEndIndex);
mPackedBlockPalettePointers.resize(blockPointerEndIndex);
// Remove all palettes that aren't used anymore
if (blockPointerEndIndex == 0)
{
mBlockPalettes.resize(1); // Only keep the first block, because it should always exist
}
else
{
unsigned32 finalPalette = *std::max_element(mPalettePointers.begin(), mPalettePointers.end());
mBlockPalettes.resize(finalPalette + 1);
}
RebuildPaletteIndices();
}
}
std::vector<T> fullMaterialPointers = materialPointers;
std::vector<PaletteBlock> blocks = BuildBlocks(fullMaterialPointers);
//std::vector<PaletteBlock> blocks = SerialBuildBlocks(fullMaterialPointers);
AddBlocks(blocks, fullMaterialPointers, fromIndex);
mOriginalSize = fromIndex + fullMaterialPointers.size();
}
void ReplaceMaterials(const std::unordered_map<T, T>& replacementMap) override
{
bool replacersEqual = true;
for (auto replacer : replacementMap)
if (replacer.first != replacer.second)
{
replacersEqual = false;
break;
}
if (replacersEqual)
return;
// Replace the colors in all palettes:
tbb::parallel_for_each(mBlockPalettes.begin(), mBlockPalettes.end(), [&](Block<T>& block)
{
for (unsigned i = 0; i < block.size(); i++)
{
auto replacer = replacementMap.find(block[i]);
if (replacer != replacementMap.end())
block.Set(i, replacer->second);
}
});
RebuildPaletteIndices();
}
void Recompress() override
{
SetTexture(GetTexture());
}
void ReadFromFile(std::istream& file)
{
// Read the original size
Serializer<unsigned64>::Deserialize(mOriginalSize, file);
Serializer<unsigned8>::Deserialize(mFirstBlockPointerSize, file);
unsigned32 paletteMask;
Serializer<unsigned32>::Deserialize(paletteMask, file);
SetPaletteMask(paletteMask);
// Read the block pointers
unsigned64 blockPointerSize = 0;
Serializer<unsigned64>::Deserialize(blockPointerSize, file);
mBlockPointers.resize(blockPointerSize);
Serializer<unsigned64*>::Deserialize(&mBlockPointers[0], blockPointerSize, file);
// Also read the palette pointers
mPalettePointers.resize(blockPointerSize);
Serializer<unsigned32*>::Deserialize(&mPalettePointers[0], blockPointerSize, file);
// Read the block palettes
unsigned64 blockPalettesSize = 0;
Serializer<unsigned64>::Deserialize(blockPalettesSize, file);
mBlockPalettes.resize(blockPalettesSize);
std::vector<T> blockCache;
for (size_t i = 0; i < (size_t)blockPalettesSize; i++)
{
Serializer<std::vector<T>, unsigned32>::Deserialize(blockCache, file);
mBlockPalettes[i] = Block<T>(blockCache);
}
// Read the block palette pointers
Serializer<std::vector<std::vector<unsigned8>>, unsigned32>::Deserialize(mPackedBlockPalettePointers, file);
}
void WriteToFile(std::ostream& file) const override
{
// Write the original size
Serializer<unsigned64>::Serialize(mOriginalSize, file);
Serializer<unsigned8>::Serialize(mFirstBlockPointerSize, file);
// Write the palette mask
Serializer<unsigned32>::Serialize(mPaletteMask, file);
// Write the block pointers
unsigned64 blockPointerSize = (unsigned64)mBlockPointers.size();
Serializer<unsigned64>::Serialize(blockPointerSize, file);
Serializer<unsigned64*>::Serialize(&mBlockPointers[0], blockPointerSize, file);
Serializer<unsigned32*>::Serialize(&mPalettePointers[0], blockPointerSize, file);
// Write the block palettes
unsigned64 blockPalettesSize = mBlockPalettes.size();
Serializer<unsigned64>::Serialize(blockPalettesSize, file);
for (unsigned64 i = 0; i < blockPalettesSize; i++)
Serializer<std::vector<T>, unsigned32>::Serialize(mBlockPalettes[i].GetData(), file);
// Write the block palette pointers
Serializer<std::vector<std::vector<unsigned8>>, unsigned32>::Serialize(mPackedBlockPalettePointers, file);
}
// Use this method to output the compressed texture as a vector of one byte characters
std::vector<unsigned8> GetTexturePool() const override {
// Texture pool should first contain all palettes, followed by the paletteBlockPointers
size_t poolSize = GetTexturePoolSize();
std::vector<unsigned8> pool(poolSize);
size_t curByteIndex = 0;
// Write all palettes (tightly packed), except the first one!
{
std::vector<unsigned32> palettes(GetBlockPalettesSize());
size_t i = 0;
for (auto palette = mBlockPalettes.begin() + 1; palette != mBlockPalettes.end(); ++palette)
// Only write palettes with more than 1 color, palettes with one color will be replaced by the material directly.
if (palette->size() > 1)
for (size_t j = 0; j < palette->size(); j++)
palettes[i++] = (unsigned32)palette->Get(j);
std::vector<unsigned8> tightPalettes = BitHelper::PackTight<unsigned8, unsigned32>(palettes, mPaletteBitMap);
std::move(tightPalettes.begin(), tightPalettes.end(), pool.begin());
curByteIndex += tightPalettes.size();
}
std::vector<unsigned8> firstBlockBitMap = BitHelper::GetBitMapHS(BitHelper::GetLSMask<unsigned32>(0, mFirstBlockPointerSize));
// Now write all mPaletteBlockPointers. These need to be packed tightly according to the size of the palette.
for (size_t i = 0; i < mBlockPointers.size(); i++) {
size_t blockSize = GetBlockSize(i);
std::vector<unsigned8> packedPalettePointers;
if (mPalettePointers[i] == 0)
{
// If we should use the "zero block", then pack the materials directly (the zero block is not an actual palette as it just contains all materials)
std::vector<unsigned32> blockContents(blockSize);
for (size_t j = 0; j < blockSize; j++)
blockContents[j] = (unsigned32)(mBlockPalettes[0].Get(BitHelper::UnpackTightAt<unsigned8, unsigned32>(mPackedBlockPalettePointers[i], j, firstBlockBitMap)));
packedPalettePointers = BitHelper::PackTight<unsigned8, unsigned32>(blockContents, mPaletteBitMap);
}
else
{
packedPalettePointers = mPackedBlockPalettePointers[i];
}
std::move(packedPalettePointers.begin(), packedPalettePointers.end(), pool.begin() + curByteIndex);
curByteIndex += packedPalettePointers.size();
//assert(packedPalettePointers.size() == GetBlockByteSize(i));
}
return pool;
}
size_t GetTexturePoolSize() const override {
// Calculate the size required for all palettes. These will be packed tightly
return GetPalettesByteSize() + GetBlocksByteSize();
}
// Use this method to output any addition information that might be needed.
std::vector<unsigned8> GetAdditionalTexturePool() const
{
unsigned8 originalPointerSize = GetOriginalPointerSize();
unsigned8 palettePointerSize = GetPalettePointerSize();
unsigned8 blockPointerSize = GetBlockPointerSize();
size_t textureSize = GetAdditionalTexturePoolSize();
std::vector<unsigned8> additionalPool(textureSize);
additionalPool[0] = originalPointerSize;
additionalPool[1] = palettePointerSize;
additionalPool[2] = blockPointerSize;
// Build the actual palette pointers, that point to the start of the palette
std::vector<unsigned64> actualPalettePointers(mBlockPalettes.size());
actualPalettePointers[0] = -1; // Palette 0 contains all materials, so we store them directly in this case. This is indicating by a 0xFFFFFFFF mask (==-1)
unsigned64 curActualPalettePointer = 0;
for (size_t i = 1; i < mBlockPalettes.size(); i++)
{
unsigned64 actualPalettePointer = curActualPalettePointer;
unsigned64 paletteBitSize = GetPaletteBitSize(i);
if (paletteBitSize == 0) // Meaning the palette is one color, so palette is not stored. This is signaled with mask 0xFFFFFFFE.
actualPalettePointer = -2;
else
actualPalettePointer |= (paletteBitSize - 1) << (palettePointerSize * 8 - 3); // Add mask indicating the size of the palette
actualPalettePointers[i] = actualPalettePointer;
// Skip palettes of 0 bits (0 or 1 materials)
if (paletteBitSize > 0)
curActualPalettePointer += mBlockPalettes[i].size();
}
assert(curActualPalettePointer == GetBlockPalettesSize());
unsigned64 curActualBlockPointer = GetPalettesByteSize();
// Write them to the pool
for (size_t i = 0; i < mBlockPointers.size(); i++)
{
size_t curByte = 3 + i * (originalPointerSize + palettePointerSize + blockPointerSize);
size_t bitSize = GetBlockPalettePointerBitSize(i);
unsigned64 blockByteSize = GetBlockByteSize(i);
unsigned64 blockPointer = curActualBlockPointer;
curActualBlockPointer += blockByteSize;
if (blockByteSize == 0)
{ // If blockByteSize = 0, just write the actual color of all nodes in this block as the block pointer
T mat = mBlockPalettes[mPalettePointers[i]][0];
blockPointer = (unsigned32)mat;
}
BitHelper::SplitInBytesAndMove(mBlockPointers[i], additionalPool, curByte, originalPointerSize);
BitHelper::SplitInBytesAndMove(actualPalettePointers[mPalettePointers[i]], additionalPool, curByte + originalPointerSize, palettePointerSize);
BitHelper::SplitInBytesAndMove(blockPointer, additionalPool, curByte + originalPointerSize + palettePointerSize, blockPointerSize);
}
assert(curActualBlockPointer == GetPalettesByteSize() + GetBlocksByteSize());
return additionalPool;
}
size_t GetAdditionalTexturePoolSize() const
{
// Assume palette pointers and block pointers are both 32 bits
size_t bytesPerBlock = GetOriginalPointerSize() + GetPalettePointerSize() + GetBlockPointerSize();
// Calculate the total number of required byte
return 3 + mBlockPointers.size() * bytesPerBlock;
}
std::map<std::string, std::string> GetAdditionalProperties() const override
{
std::map<std::string, std::string> res;
// Write the mask used for the palette pointers
res.insert(std::pair<std::string, std::string>("mask", std::to_string(mPaletteMask)));
// Write the blockCount
size_t blockCount = mBlockPointers.size();
res.insert(std::pair<std::string, std::string>("blockCount", std::to_string(blockCount)));
return res;
};
};