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

202 lines
7.0 KiB
C++

#pragma once
#include "CompressedTexture.h"
#include "BasicTexture.h"
#include "../Material/Block.h"
#include "../../inc/tbb/parallel_for.h"
#include "BlockHashers.h"
#include <algorithm>
#include "../../core/Serializer.h"
template<typename T, typename BlockTextureType = BasicTexture<T>>
class BlockCompressedTexture : public CompressedTexture<T>
{
private:
size_t mActualSize;
std::vector<unsigned> mBlockPointers;
BlockTextureType mBlocks;
unsigned mBlockSize;
public:
BlockCompressedTexture(unsigned blockSize) :
mActualSize(0),
mBlockPointers(std::vector<unsigned>()),
mBlocks(BlockTextureType()),
mBlockSize(blockSize)
{}
~BlockCompressedTexture() override {}
T operator[](size_t i) const override
{
size_t blockPointerIndex = i / mBlockSize;
size_t blockIndex = mBlockPointers[blockPointerIndex] * mBlockSize;
blockIndex += i % mBlockSize;
return mBlocks[blockIndex];
}
unsigned64 size() const override { return mActualSize; }
// Returns the (unpacked) material indices from the given index
std::vector<T> GetTexture(size_t fromIndex = 0) override
{
assert(fromIndex <= mActualSize);
std::vector<T> res(mActualSize - fromIndex);
if (res.size() == 0) return res;
size_t startBlockIndex = fromIndex / mBlockSize;
size_t indexInsideStartBlock = fromIndex % mBlockSize;
size_t indexFromStartBlock = mBlockSize - indexInsideStartBlock;
// Unpack the part of the first block from the "fromIndex"
for (size_t j = indexInsideStartBlock; j < mBlockSize && j < res.size(); j++)
res[j - indexInsideStartBlock] = mBlocks[mBlockPointers[startBlockIndex] * mBlockSize + j];
// Unpack the rest of the blocks
for (size_t i = startBlockIndex + 1; i < mBlockPointers.size(); i++)
{
size_t blockResIndex = indexFromStartBlock + ((i - startBlockIndex - 1) * mBlockSize);
size_t blocksIndex = mBlockPointers[i] * mBlockSize;
for (size_t j = 0; j < mBlockSize; j++)
{
if (blockResIndex + j >= res.size()) break;
res[blockResIndex + j] = mBlocks[blocksIndex + j];
}
}
return res;
}
void SetTexture(const std::vector<T>& materialPointers, size_t fromIndex = 0) override
{
// TODO: only copy if necessary
std::vector<T> nodeMaterialPointers;
mActualSize = fromIndex + materialPointers.size();
unsigned fromBlockIndex = (unsigned)fromIndex / mBlockSize;
unsigned highestIndex = mBlockPointers.empty() ? 0 : *std::max_element(mBlockPointers.begin(), mBlockPointers.begin() + fromBlockIndex);
// Build a map from blocks to indices that already exist (only necessary if fromIndex > 0)
std::unordered_map<Block<T>, unsigned> blockIndices;
if (fromIndex >= mBlockSize)
{
std::vector<T> uncompressedBlocks = mBlocks.GetTexture();
for (size_t i = 0; i < highestIndex; i++)
{
Block<T> cur(uncompressedBlocks, i * mBlockSize, i * mBlockSize + mBlockSize);
blockIndices.insert(std::pair<Block<T>, unsigned>(cur, (unsigned)i));
}
}
// Add what was already in the block in which the new nodepointers should be added to nodeMaterialPointers
unsigned switchIndex = (unsigned)fromIndex % mBlockSize;
if (switchIndex != 0)
{
std::vector<T> switchBlockStart(switchIndex);
auto switchBlockIndex = mBlockPointers[fromBlockIndex] * mBlockSize;
for (unsigned j = 0; j < switchIndex; j++)
switchBlockStart[j] = mBlocks[switchBlockIndex + j];
nodeMaterialPointers.resize(switchIndex + materialPointers.size());
std::copy(switchBlockStart.begin(), switchBlockStart.end(), nodeMaterialPointers.begin());
std::copy(materialPointers.begin(), materialPointers.end(), nodeMaterialPointers.begin() + switchIndex);
}
else
{
nodeMaterialPointers.resize(materialPointers.size());
std::copy(materialPointers.begin(), materialPointers.end(), nodeMaterialPointers.begin());
}
if (nodeMaterialPointers.size() % mBlockSize != 0)
{
unsigned emptyNodesToAdd = mBlockSize - nodeMaterialPointers.size() % mBlockSize;
nodeMaterialPointers.resize(nodeMaterialPointers.size() + emptyNodesToAdd);
}
std::vector<T> newBlocks;
mBlockPointers.resize(mActualSize / mBlockSize + (((mActualSize % mBlockSize) == 0) ? 0 : 1));
// Compress the material pointers so that material pointers that are the same won't be reused
size_t reuseCount = 0;
unsigned newBlockPointer = highestIndex;
unsigned blockId = fromBlockIndex;
for (size_t i = 0; i < nodeMaterialPointers.size(); i += mBlockSize)
{
// Build the current block
Block<T> current(nodeMaterialPointers, i, i + mBlockSize);
// Check if the current block is already in the texture
auto existingCurrent = blockIndices.find(current);
unsigned curBlockPointer;
if (existingCurrent == blockIndices.end())
{
// If it isn't, copy the current block to the block texture.
for (size_t j = 0; j < mBlockSize; j++)
newBlocks.push_back(current.Get(j));
curBlockPointer = newBlockPointer;
blockIndices.insert(std::pair<Block<T>, unsigned>(current, newBlockPointer));
newBlockPointer++;
}
else
{
reuseCount++;
curBlockPointer = existingCurrent->second;
}
mBlockPointers[blockId++] = curBlockPointer;
}
mBlocks.SetTexture(newBlocks, highestIndex * mBlockSize);
printf("%llu Blocks were reused during compression.\n", (unsigned64)reuseCount);
}
void ReplaceMaterials(const std::unordered_map<T, T>& replacementMap) override
{
mBlocks.ReplaceMaterials(replacementMap);
}
void Recompress() override
{
// TODO: make this more efficient
SetTexture(GetTexture());
}
void ReadFromFile(std::istream& file)
{
// Read the block pointers
Serializer<std::vector<unsigned32>, unsigned64>::Deserialize(mBlockPointers, file);
// Read the blocks
mBlocks.ReadFromFile(file);
}
void WriteToFile(std::ostream& file) const override
{
// Write the block pointers
Serializer<std::vector<unsigned32>, unsigned64>::Serialize(mBlockPointers, file);
// Write the blocks
mBlocks.WriteToFile(file);
}
// Texture pool is taken from how the blocks are stored
std::vector<unsigned8> GetTexturePool() const override { return mBlocks.GetTexturePool(); }
size_t GetTexturePoolSize() const override { return mBlocks.GetTexturePoolSize(); }
// Additional texture pool contains for each pixels which block from the blocktexture it is from.
std::vector<unsigned8> GetAdditionalTexturePool() const
{
size_t textureSize = GetAdditionalTexturePoolSize();
std::vector<unsigned8> additionalPool(textureSize);
tbb::parallel_for(size_t(0), mBlockPointers.size(), [&](const size_t& i)
{
BitHelper::SplitInBytesAndMove(mBlockPointers[i], additionalPool, i * 4);
});
return additionalPool;
}
size_t GetAdditionalTexturePoolSize() const
{
return mBlockPointers.size() * 4;
}
std::map<std::string, std::string> GetAdditionalProperties() const override
{
std::map<std::string, std::string> res;
res.insert(std::pair<std::string, std::string>("blockSize", std::to_string(mBlockSize)));
// Get the additional properties from the block texture
std::map<std::string, std::string> blockProperties = mBlocks.GetAdditionalProperties();
for (auto prop : blockProperties)
res.insert(prop);
return res;
};
};