Initial commit: Final state of the master project
This commit is contained in:
79
Research/scene/TextureCompressor/BasicTexture.h
Normal file
79
Research/scene/TextureCompressor/BasicTexture.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "CompressedTexture.h"
|
||||
#include "../../core/Serializer.h"
|
||||
#include "../../inc/tbb/parallel_for.h"
|
||||
#include "../../core/BitHelper.h"
|
||||
|
||||
template<typename T>
|
||||
class BasicTexture : public CompressedTexture<T>
|
||||
{
|
||||
private:
|
||||
std::vector<T> mData;
|
||||
public:
|
||||
BasicTexture() : mData(std::vector<T>()) {}
|
||||
~BasicTexture() {}
|
||||
|
||||
// Uncompress and retrieve the texture (possibly from a certain index)
|
||||
T operator[](size_t i) const override { return mData[i]; }
|
||||
unsigned64 size() const override { return mData.size(); }
|
||||
|
||||
std::vector<T> GetTexture(size_t fromIndex = 0) override {
|
||||
std::vector<T> res(size() - fromIndex);
|
||||
std::copy(mData.begin() + fromIndex, mData.end(), res.begin());
|
||||
return res;
|
||||
}
|
||||
// Compresses the texture, replacing everything after fromIndex by whatever is in texture
|
||||
void SetTexture(const std::vector<T>& texture, size_t fromIndex = 0) override
|
||||
{
|
||||
mData.resize(fromIndex + texture.size());
|
||||
std::copy(texture.begin(), texture.end(), mData.begin() + fromIndex);
|
||||
}
|
||||
// Replace all materials by other colors. This could be useful as some methods don't need to fully decompress and compress the whole texture to do this.
|
||||
void ReplaceMaterials(const std::unordered_map<T, T>& replacers) override
|
||||
{
|
||||
bool replacersEqual = true;
|
||||
for (auto replacer : replacers)
|
||||
if (replacer.first != replacer.second)
|
||||
{
|
||||
replacersEqual = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!replacersEqual)
|
||||
{
|
||||
tbb::parallel_for(size_t(0), mData.size(), [&](size_t i)
|
||||
{
|
||||
auto replacer = replacers.find(mData[i]);
|
||||
if (replacer != replacers.end())
|
||||
mData[i] = replacer->second;
|
||||
});
|
||||
}
|
||||
}
|
||||
void Recompress() override { return; }
|
||||
|
||||
void ReadFromFile(std::istream& file) override
|
||||
{
|
||||
Serializer<std::vector<T>, unsigned64>::Deserialize(mData, file);
|
||||
}
|
||||
virtual void WriteToFile(std::ostream& file) const override
|
||||
{
|
||||
Serializer<std::vector<T>, unsigned64>::Serialize(mData, file);
|
||||
}
|
||||
|
||||
std::vector<unsigned8> GetTexturePool() const override
|
||||
{
|
||||
size_t poolSize = GetTexturePoolSize();
|
||||
std::vector<unsigned8> pool = std::vector<unsigned8>(poolSize);
|
||||
tbb::parallel_for(size_t(0), mData.size(), [&](size_t i)
|
||||
{
|
||||
// Convert all values to 32 bit integers and split them into bytes
|
||||
unsigned32 value = (unsigned32)mData[i];
|
||||
BitHelper::SplitInBytesAndMove(value, pool, i * 4);
|
||||
});
|
||||
return pool;
|
||||
}
|
||||
size_t GetTexturePoolSize() const override { return mData.size() * 4; }
|
||||
|
||||
virtual std::map<std::string, std::string> GetAdditionalProperties() const override { return std::map<std::string, std::string>(); }
|
||||
};
|
||||
202
Research/scene/TextureCompressor/BlockCompressedTexture.h
Normal file
202
Research/scene/TextureCompressor/BlockCompressedTexture.h
Normal file
@@ -0,0 +1,202 @@
|
||||
#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;
|
||||
};
|
||||
};
|
||||
34
Research/scene/TextureCompressor/BlockHashers.h
Normal file
34
Research/scene/TextureCompressor/BlockHashers.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include "../../core/Defines.h"
|
||||
#include "../../core/BitHelper.h"
|
||||
#include "../Material/Block.h"
|
||||
#include "../Material/MaterialLibraryPointer.h"
|
||||
|
||||
namespace std
|
||||
{
|
||||
//template<>
|
||||
//struct hash<Block<MaterialLibraryPointer>>
|
||||
//{
|
||||
// size_t operator()(const Block<MaterialLibraryPointer> &value) const
|
||||
// {
|
||||
// size_t res = 0;
|
||||
// for (size_t i = 0; i < value.size(); i++)
|
||||
// res ^= BitHelper::CircularShiftLeft<size_t>(hash<MaterialLibraryPointer>()(value.Get(i)), i);
|
||||
// return res;
|
||||
// }
|
||||
//};
|
||||
|
||||
template<typename T>
|
||||
struct hash<Block<T>>
|
||||
{
|
||||
size_t operator()(const Block<T> &value) const
|
||||
{
|
||||
size_t res = 0;
|
||||
for (size_t i = 0; i < value.size(); i++)
|
||||
res ^= BitHelper::CircularShiftLeft<size_t>(hash<T>()(value.Get(i)), i);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
39
Research/scene/TextureCompressor/CompressedTexture.h
Normal file
39
Research/scene/TextureCompressor/CompressedTexture.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "../../inc/gl/glew.h"
|
||||
|
||||
template<typename T>
|
||||
class CompressedTexture
|
||||
{
|
||||
public:
|
||||
virtual ~CompressedTexture() {}
|
||||
|
||||
// Uncompress and retrieve the texture (possibly from a certain index)
|
||||
virtual T operator[](size_t i) const = 0;
|
||||
virtual unsigned64 size() const = 0;
|
||||
|
||||
virtual std::vector<T> GetTexture(size_t fromIndex = 0) = 0;
|
||||
// Compresses the texture, replacing everything after fromIndex by whatever is in texture
|
||||
virtual void SetTexture(const std::vector<T>& texture, size_t fromIndex = 0) = 0;
|
||||
// Replace all materials by other colors. This could be useful as some methods don't need to fully decompress and compress the whole texture to do this.
|
||||
virtual void ReplaceMaterials(const std::unordered_map<T, T>& replacers) = 0;
|
||||
virtual void Recompress() { SetTexture(GetTexture()); }
|
||||
|
||||
virtual void ReadFromFile(std::istream& file) = 0;
|
||||
virtual void WriteToFile(std::ostream& file) const = 0;
|
||||
|
||||
// Use this method to output the compressed texture as a vector of one byte characters
|
||||
virtual std::vector<unsigned8> GetTexturePool() const = 0;
|
||||
virtual size_t GetTexturePoolSize() const = 0;
|
||||
|
||||
// Use this method to output any addition information that might be needed.
|
||||
virtual std::vector<unsigned8> GetAdditionalTexturePool() const { return std::vector<unsigned8>(); };
|
||||
virtual size_t GetAdditionalTexturePoolSize() const { return 0; };
|
||||
|
||||
virtual std::map<std::string, std::string> GetAdditionalProperties() const = 0;
|
||||
};
|
||||
75
Research/scene/TextureCompressor/DagBasedTexture.h
Normal file
75
Research/scene/TextureCompressor/DagBasedTexture.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "CompressedTexture.h"
|
||||
#include "../../core/Util/BinaryTree.h"
|
||||
#include "../../core/Serializer.h"
|
||||
|
||||
template<typename T>
|
||||
class DagBasedTexture : public CompressedTexture<T>
|
||||
{
|
||||
private:
|
||||
BinaryTree<T> mData;
|
||||
unsigned64 mOriginalSize;
|
||||
public:
|
||||
DagBasedTexture() :
|
||||
mData(BinaryTree<T>()),
|
||||
mOriginalSize(0)
|
||||
{}
|
||||
~DagBasedTexture() {}
|
||||
|
||||
// Uncompress and retrieve the texture (possibly from a certain index)
|
||||
T operator[](size_t i) const override { return mData.GetValueAtLeaf(i); }
|
||||
unsigned64 size() const override { return mOriginalSize; }
|
||||
|
||||
std::vector<T> GetTexture(size_t fromIndex = 0) override {
|
||||
std::vector<T> data(mOriginalSize);
|
||||
for (size_t i = 0; i < mOriginalSize; i++) { data[i] = operator[](i); }
|
||||
return data;
|
||||
}
|
||||
// Compresses the texture, replacing everything after fromIndex by whatever is in texture
|
||||
void SetTexture(const std::vector<T>& texture, size_t fromIndex = 0) override
|
||||
{
|
||||
// Calculate the required tree depth
|
||||
unsigned8 requiredDepth = BitHelper::Log2Ceil(fromIndex + texture.size());
|
||||
mData.SetDepth(requiredDepth, true);
|
||||
// Set all leaf nodes correctly:
|
||||
for (size_t i = 0; i < texture.size(); i++)
|
||||
mData.SetValueAtLeaf(fromIndex + i, texture[i]);
|
||||
mData.ToDAG();
|
||||
|
||||
mOriginalSize = fromIndex + texture.size();
|
||||
}
|
||||
|
||||
// Replace all materials by other colors. This could be useful as some methods don't need to fully decompress and compress the whole texture to do this.
|
||||
void ReplaceMaterials(const std::unordered_map<T, T>& replacers) override
|
||||
{
|
||||
bool replacersEqual = true;
|
||||
for (auto replacer : replacers)
|
||||
if (replacer.first != replacer.second)
|
||||
{
|
||||
replacersEqual = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (replacersEqual) return;
|
||||
mData.ReplaceValues(replacers);
|
||||
}
|
||||
void Recompress() override { return; }
|
||||
|
||||
void ReadFromFile(std::istream& file) override
|
||||
{
|
||||
Serializer<unsigned64>::Deserialize(mOriginalSize, file);
|
||||
mData.Deserialize(file);
|
||||
}
|
||||
virtual void WriteToFile(std::ostream& file) const override
|
||||
{
|
||||
Serializer<unsigned64>::Serialize(mOriginalSize, file);
|
||||
mData.Serialize(file);
|
||||
}
|
||||
|
||||
// Use this method to output the compressed texture as a vector of one byte characters
|
||||
std::vector<unsigned char> GetTexturePool() const override { return mData.Serialize(true); }
|
||||
size_t GetTexturePoolSize() const override { return mData.GetSerializedByteCount(true); }
|
||||
|
||||
virtual std::map<std::string, std::string> GetAdditionalProperties() const override { return std::map<std::string, std::string>(); }
|
||||
};
|
||||
175
Research/scene/TextureCompressor/MultiRootBasedTexture.h
Normal file
175
Research/scene/TextureCompressor/MultiRootBasedTexture.h
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include "CompressedTexture.h"
|
||||
#include "../Octree/MultiRootTree.h"
|
||||
#include "../../core/Serializer.h"
|
||||
#include "../../core/MathHelper.h"
|
||||
#include "../PoolBuilder/StandardPoolBuilder.h"
|
||||
|
||||
template<typename T>
|
||||
class MultiRootBasedTexture : public CompressedTexture<T>
|
||||
{
|
||||
private:
|
||||
MultiRootTree<>* mData;
|
||||
std::vector<unsigned8> mBitMap;
|
||||
unsigned32 mMask;
|
||||
unsigned64 mOriginalSize;
|
||||
|
||||
glm::uvec3 GetTextureCoord(size_t i) const
|
||||
{
|
||||
size_t nodeIndex = i / 8;
|
||||
unsigned8 depth = mData->GetMaxLevel();
|
||||
size_t mask = BitHelper::GetLSMask<size_t>(0, depth - 1);
|
||||
glm::uvec3 leafIndex(
|
||||
(nodeIndex & mask) << 1,
|
||||
((nodeIndex >> (depth - 1)) & mask) << 1,
|
||||
((nodeIndex >> ((depth - 1) * 2)) & mask) << 1);
|
||||
if (BitHelper::GetLS(i, 2)) leafIndex.z++;
|
||||
if (BitHelper::GetLS(i, 1)) leafIndex.y++;
|
||||
if (BitHelper::GetLS(i, 0)) leafIndex.x++;
|
||||
return leafIndex;
|
||||
}
|
||||
|
||||
static unsigned8 GetRequiredDepth(size_t nodeCount)
|
||||
{
|
||||
unsigned8 indexBits = BitHelper::Log2Ceil(nodeCount);
|
||||
return indexBits / 3 + ((indexBits % 3 == 0) ? 0 : 1);
|
||||
}
|
||||
public:
|
||||
MultiRootBasedTexture() :
|
||||
mData(NULL),
|
||||
mBitMap(std::vector<unsigned8>()),
|
||||
mMask(0),
|
||||
mOriginalSize(0)
|
||||
{}
|
||||
|
||||
~MultiRootBasedTexture() { delete mData; }
|
||||
|
||||
T operator[](size_t i) const override {
|
||||
assert(i < mOriginalSize);
|
||||
glm::uvec3 textureCoord = GetTextureCoord(i);
|
||||
unsigned32 res = 0;
|
||||
for (unsigned32 bit = 0; bit < (unsigned32)mBitMap.size(); bit++)
|
||||
{
|
||||
bool bitSet;
|
||||
if (bit == 0)
|
||||
bitSet = mData->HasLeaf(textureCoord);
|
||||
else
|
||||
bitSet = mData->SlaveHasLeaf(textureCoord, bit - 1);
|
||||
if (bitSet) BitHelper::SetHS(res, mBitMap[bit]);
|
||||
}
|
||||
T resVal;
|
||||
resVal = res;
|
||||
return resVal;
|
||||
}
|
||||
unsigned64 size() const override { return mOriginalSize; }
|
||||
|
||||
std::vector<T> GetTexture(size_t fromIndex = 0) override {
|
||||
std::vector<T> data(mOriginalSize - fromIndex);
|
||||
for (size_t i = fromIndex; i < mOriginalSize; i++) { data[i - fromIndex] = operator[](i); }
|
||||
return data;
|
||||
}
|
||||
// Compresses the texture, replacing everything after fromIndex by whatever is in texture
|
||||
void SetTexture(const std::vector<T>& texture, size_t fromIndex = 0) override
|
||||
{
|
||||
// TODO: correctly handle the fromIndex
|
||||
if (fromIndex != 0)
|
||||
{
|
||||
// Hack to handle fromindex: just unpack and repack the whole texture
|
||||
std::vector<T>& oldTexture = GetTexture();
|
||||
// Copy the new texture
|
||||
oldTexture.resize(fromIndex + texture.size());
|
||||
std::copy(texture.begin(), texture.end(), oldTexture.begin() + fromIndex);
|
||||
// Build the DAG
|
||||
SetTexture(oldTexture);
|
||||
return;
|
||||
}
|
||||
else if (mData != NULL)
|
||||
{
|
||||
delete mData;
|
||||
mData = NULL;
|
||||
}
|
||||
|
||||
assert(mData == NULL); // Make sure we are working with a fresh tree, since appending does not work correctly yet
|
||||
|
||||
// Figure out which bits are set:
|
||||
unsigned32 mask = 0;
|
||||
for (auto mat : texture)
|
||||
mask |= (unsigned32)mat;
|
||||
|
||||
mMask = mask;
|
||||
unsigned8 bitCount = BitHelper::GetSet(mask);
|
||||
mBitMap = BitHelper::GetBitMapHS(mask);
|
||||
|
||||
unsigned8 depth = GetRequiredDepth(fromIndex + texture.size());
|
||||
|
||||
if (mData == NULL)
|
||||
{
|
||||
mData = new MultiRootTree<>(depth, bitCount - 1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < texture.size(); i++)
|
||||
{
|
||||
glm::uvec3 texCoord = GetTextureCoord(i);
|
||||
unsigned32 texel = (unsigned32)texture[i];
|
||||
for (unsigned32 bit = 0; bit < (unsigned32)mBitMap.size(); bit++)
|
||||
{
|
||||
if (BitHelper::GetHS(texel, mBitMap[bit]))
|
||||
{
|
||||
if (bit == 0) mData->AddLeafNode(texCoord);
|
||||
else mData->AddLeafNode(texCoord, bit - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
mData->ToDAG();
|
||||
|
||||
mOriginalSize = fromIndex + texture.size();
|
||||
}
|
||||
|
||||
// Replace all materials by other colors. This could be useful as some methods don't need to fully decompress and compress the whole texture to do this.
|
||||
void ReplaceMaterials(const std::unordered_map<T, T>& replacers) override
|
||||
{
|
||||
// TODO: implement this...
|
||||
std::vector<T> texture = GetTexture();
|
||||
delete mData;
|
||||
mData = NULL;
|
||||
mOriginalSize = 0;
|
||||
mMask = 0;
|
||||
mBitMap.clear();
|
||||
SetTexture(texture);
|
||||
}
|
||||
void Recompress() override { return; }
|
||||
|
||||
void ReadFromFile(std::istream& file) override
|
||||
{
|
||||
Serializer<unsigned64>::Deserialize(mOriginalSize, file);
|
||||
Serializer<unsigned32>::Deserialize(mMask, file);
|
||||
unsigned8 depth = GetRequiredDepth(mOriginalSize);
|
||||
unsigned8 slaveRootCount = BitHelper::GetSet(mMask) - 1;
|
||||
if (mData != NULL) delete mData;
|
||||
mData = new MultiRootTree<>(depth, slaveRootCount);
|
||||
mData->Deserialize(file);
|
||||
}
|
||||
virtual void WriteToFile(std::ostream& file) const override
|
||||
{
|
||||
Serializer<unsigned64>::Serialize(mOriginalSize, file);
|
||||
Serializer<unsigned32>::Serialize(mMask, file);
|
||||
mData->Serialize(file);
|
||||
}
|
||||
|
||||
// Use this method to output the compressed texture as a vector of one byte characters
|
||||
std::vector<unsigned8> GetTexturePool() const override
|
||||
{
|
||||
StandardPoolBuilder builder;
|
||||
std::vector<unsigned8> res;
|
||||
builder.BuildPool(mData, res);
|
||||
return res;
|
||||
}
|
||||
size_t GetTexturePoolSize() const override
|
||||
{
|
||||
StandardPoolBuilder builder;
|
||||
return builder.GetPoolSize(mData);
|
||||
}
|
||||
|
||||
virtual std::map<std::string, std::string> GetAdditionalProperties() const override { return std::map<std::string, std::string>(); }
|
||||
};
|
||||
1002
Research/scene/TextureCompressor/PaletteBlockTexture.h
Normal file
1002
Research/scene/TextureCompressor/PaletteBlockTexture.h
Normal file
File diff suppressed because it is too large
Load Diff
265
Research/scene/TextureCompressor/TightlyPackedTexture.h
Normal file
265
Research/scene/TextureCompressor/TightlyPackedTexture.h
Normal file
@@ -0,0 +1,265 @@
|
||||
#pragma once
|
||||
#include "CompressedTexture.h"
|
||||
#include "../../inc/tbb/parallel_reduce.h"
|
||||
#include "../../core/BitHelper.h"
|
||||
|
||||
template<typename T>
|
||||
class TightlyPackedTexture : public CompressedTexture<T>
|
||||
{
|
||||
private:
|
||||
std::vector<unsigned8> mData;
|
||||
// Mask containing bits that are set in any of the items
|
||||
size_t mOriginalSize;
|
||||
unsigned mMask;
|
||||
std::vector<unsigned8> mBitMap;
|
||||
|
||||
void SetMask(unsigned32 mask)
|
||||
{
|
||||
mMask = mask;
|
||||
mBitMap = BitHelper::GetBitMapHS(mask);
|
||||
}
|
||||
|
||||
inline unsigned32 GetValueAt(size_t i) const { return BitHelper::UnpackTightAt<unsigned8, unsigned32>(mData, i, mBitMap); }
|
||||
public:
|
||||
TightlyPackedTexture() :
|
||||
mData(std::vector<unsigned8>()),
|
||||
mOriginalSize(0),
|
||||
mMask(0),
|
||||
mBitMap(std::vector<unsigned8>())
|
||||
{
|
||||
static_assert(sizeof(T) == 4, "Only types of size 4 can be tightly packed");
|
||||
}
|
||||
|
||||
~TightlyPackedTexture() override {}
|
||||
|
||||
T operator[](size_t i) const override
|
||||
{
|
||||
T value;
|
||||
value = GetValueAt(i);
|
||||
return value;
|
||||
}
|
||||
|
||||
unsigned64 size() const override { return mOriginalSize; }
|
||||
|
||||
std::vector<T> GetTexture(size_t fromIndex = 0) override
|
||||
{
|
||||
assert(fromIndex <= size());
|
||||
//fromIndex = std::min(fromIndex, size());
|
||||
std::vector<T> res(mOriginalSize - fromIndex);
|
||||
//for (size_t i = fromIndex; i < mOriginalSize; i++)
|
||||
tbb::parallel_for(fromIndex, mOriginalSize, [&](size_t i)
|
||||
{
|
||||
unsigned value = GetValueAt(i);
|
||||
res[i - fromIndex] = value;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
void SetTexture(const std::vector<T>& nodeMaterialPointers, size_t fromIndex = 0) override
|
||||
{
|
||||
if (nodeMaterialPointers.empty()) return;
|
||||
if (fromIndex != 0)
|
||||
{
|
||||
auto curTexture = GetTexture();
|
||||
curTexture.resize(fromIndex);
|
||||
curTexture.insert(curTexture.end(), nodeMaterialPointers.begin(), nodeMaterialPointers.end());
|
||||
SetTexture(curTexture, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find out which bits are set in both the existing and new texture:
|
||||
unsigned mask = 0;
|
||||
size_t setBits = BitHelper::GetSet(mMask);
|
||||
for (T value : nodeMaterialPointers)
|
||||
mask |= static_cast<unsigned32>(value);
|
||||
for (size_t i = 0; i < fromIndex; i++)
|
||||
mask |= GetValueAt(i);
|
||||
// Make sure at least one bit is set (preventing divide by zero and similar problems)
|
||||
if (mask == 0) mask = 1;
|
||||
|
||||
// If the mask changed, we need to repack the whole texture
|
||||
if (mask != mMask && !mData.empty() && fromIndex != 0)
|
||||
{
|
||||
auto curTexture = GetTexture();
|
||||
curTexture.resize(fromIndex);
|
||||
curTexture.insert(curTexture.end(), nodeMaterialPointers.begin(), nodeMaterialPointers.end());
|
||||
SetTexture(curTexture, 0);
|
||||
}
|
||||
|
||||
// Update the current mask
|
||||
SetMask(mask);
|
||||
setBits = BitHelper::GetSet(mMask);
|
||||
printf("Current texture requires %u bits per pointer\n", (unsigned32)setBits);
|
||||
|
||||
// Calculate how many bits are needed each part
|
||||
size_t existingBits = fromIndex * setBits;
|
||||
size_t newBits = nodeMaterialPointers.size() * setBits;
|
||||
size_t totalBits = existingBits + newBits;
|
||||
|
||||
// If the existing bits don't fit in a fixed number of bytes, we need to repack the first byte of the new set
|
||||
unsigned8 bitOffset = (unsigned8)(existingBits & 0x07);
|
||||
unsigned8 switchBytes = (bitOffset == 0) ? 0 : 1;
|
||||
|
||||
// The existing- and newbyte counts don't include the switchbyte
|
||||
size_t existingBytes = existingBits >> 3;
|
||||
size_t totalBytes = (totalBits >> 3) + (((totalBits & 0x07) == 0) ? 0 : 1);
|
||||
|
||||
size_t switchByteIndex = existingBytes;
|
||||
|
||||
// Compress the new data
|
||||
std::vector<unsigned8> packed;
|
||||
{
|
||||
std::vector<unsigned32> unpacked(nodeMaterialPointers.size());
|
||||
tbb::parallel_for(size_t(0), nodeMaterialPointers.size(), [&](size_t i) { unpacked[i] = (unsigned32)nodeMaterialPointers[i]; });
|
||||
packed = BitHelper::PackTight<unsigned8, unsigned32>(unpacked, mBitMap, bitOffset);
|
||||
}
|
||||
|
||||
mData.resize(totalBytes);
|
||||
// If the edge between the existing and new materials is not on the edge of a byte,
|
||||
// we need to construct a "switchByte", which contains part of the last existing materials,
|
||||
// and part of the first new material:
|
||||
if (switchBytes != 0)
|
||||
mData[switchByteIndex] = (mData[switchByteIndex] & BitHelper::GetHSMask<unsigned8>(0, bitOffset)) | packed[0];
|
||||
// Move the packed data over to mData
|
||||
std::move(packed.begin() + switchBytes, packed.end(), mData.begin() + existingBytes + ((size_t)switchBytes));
|
||||
mOriginalSize = fromIndex + nodeMaterialPointers.size();
|
||||
}
|
||||
|
||||
// Replace all materials. Note that for this to work, all materials currently in the texture need to have a key in the dictionary
|
||||
void ReplaceMaterials(const std::unordered_map<T, T>& replacementMap) override
|
||||
{
|
||||
// Check if the replacements aren't the same as the originals:
|
||||
bool replacersEqual = true;
|
||||
for (auto replacer : replacementMap)
|
||||
if (replacer.first != replacer.second)
|
||||
{
|
||||
replacersEqual = false; break;
|
||||
}
|
||||
if (replacersEqual) return;
|
||||
|
||||
//TODO: Check if the same amount of bits is set
|
||||
// If that is the case, we can just replace the data. Otherwise, a full repacking is needed
|
||||
unsigned newMask = 0;
|
||||
for (auto replacer : replacementMap)
|
||||
newMask |= (unsigned)replacer.second;
|
||||
if (newMask == mMask)
|
||||
{
|
||||
// If the mask doesn't change, we can keep using the same bits, only replacing their values
|
||||
unsigned8 setBits = BitHelper::GetSet(mMask);
|
||||
std::unordered_map<unsigned, unsigned> bitReplacementMap;
|
||||
for (auto value : replacementMap)
|
||||
{
|
||||
unsigned source = 0;
|
||||
unsigned dest = 0;
|
||||
for (unsigned8 j = 0; j < setBits; j++)
|
||||
{
|
||||
if (BitHelper::GetHS((unsigned32)value.first, mBitMap[j])) BitHelper::SetLS(source, setBits - j - 1);
|
||||
if (BitHelper::GetHS((unsigned32)value.second, mBitMap[j])) BitHelper::SetLS(dest, setBits - j - 1);
|
||||
}
|
||||
bitReplacementMap.insert(std::pair<unsigned, unsigned>(source, dest));
|
||||
}
|
||||
for (size_t i = 0; i < mOriginalSize; i++)
|
||||
{
|
||||
unsigned source = 0;
|
||||
// Find the current value of material i, and replace it with it's replacement map counterpart.
|
||||
unsigned8 j = 0;
|
||||
while (j < setBits)
|
||||
{
|
||||
size_t bit = i * setBits + j;
|
||||
size_t byte = bit >> 3;
|
||||
unsigned8 startBitInByte = bit & 0x07;
|
||||
unsigned8 endBitInByte = std::min(startBitInByte + setBits - j, 8);
|
||||
unsigned8 bitsInByte = endBitInByte - startBitInByte;
|
||||
source |= (unsigned)((mData[byte] & BitHelper::GetHSMask<unsigned8>(startBitInByte, endBitInByte)) >> (8 - endBitInByte)) << (setBits - bitsInByte - j);
|
||||
j += bitsInByte;
|
||||
}
|
||||
// Find the value to replace it with:
|
||||
auto replacer = bitReplacementMap.find(source);
|
||||
//assert(replacer != bitReplacementMap.end());
|
||||
if (replacer != bitReplacementMap.end())
|
||||
{
|
||||
unsigned dest = replacer->second;
|
||||
j = 0;
|
||||
while (j < setBits)
|
||||
{
|
||||
size_t startBit = i * setBits + j;
|
||||
size_t byte = startBit >> 3;
|
||||
unsigned8 startBitInByte = startBit & 0x07;
|
||||
unsigned8 endBitInByte = std::min(startBitInByte + setBits - j, 8);
|
||||
unsigned8 bitsInByte = endBitInByte - startBitInByte;
|
||||
|
||||
unsigned8 bitsToCopy = (unsigned8)(dest >> (setBits - (j + bitsInByte)));
|
||||
unsigned8 inPlaceBitsToCopy = bitsToCopy << (8 - endBitInByte);
|
||||
|
||||
unsigned8 mask1 = BitHelper::GetHSMask<unsigned8>(0, startBitInByte) | BitHelper::GetHSMask<unsigned8>(endBitInByte, 8);
|
||||
unsigned8 mask2 = BitHelper::GetHSMask<unsigned8>(startBitInByte, endBitInByte);
|
||||
mData[byte] =
|
||||
(mask1 & mData[byte]) | // Copy the original bits
|
||||
(mask2 & inPlaceBitsToCopy); // Insert the new bits
|
||||
|
||||
j += bitsInByte;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the mask changes, we need to replace the whole texture
|
||||
std::vector<T> cur = GetTexture();
|
||||
//for (size_t i = 0; i < cur.size(); i++)
|
||||
tbb::parallel_for(size_t(0), cur.size(), [&](size_t i)
|
||||
{
|
||||
auto item = replacementMap.find(cur[i]);
|
||||
//assert(item != replacementMap.end());
|
||||
if (item != replacementMap.end())
|
||||
cur[i] = item->second;
|
||||
});
|
||||
SetTexture(cur);
|
||||
}
|
||||
}
|
||||
void Recompress() override
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void ReadFromFile(std::istream& file)
|
||||
{
|
||||
mData.clear();
|
||||
unsigned64 originalSize;
|
||||
Serializer<unsigned64>::Deserialize(originalSize, file);
|
||||
mOriginalSize = originalSize;
|
||||
unsigned32 mask;
|
||||
Serializer<unsigned32>::Deserialize(mask, file);
|
||||
SetMask(mask);
|
||||
unsigned64 bitCount = ((unsigned64)BitHelper::GetSet(mMask)) * mOriginalSize;
|
||||
unsigned64 byteCount = BitHelper::RoundToBytes(bitCount) >> 3;
|
||||
mData.resize(byteCount);
|
||||
Serializer<unsigned8*>::Deserialize(&mData[0], mData.size(), file);
|
||||
}
|
||||
void WriteToFile(std::ostream& file) const override
|
||||
{
|
||||
unsigned64 originalSize = mOriginalSize;
|
||||
Serializer<unsigned64>::Serialize(originalSize, file);
|
||||
Serializer<unsigned32>::Serialize(mMask, file);
|
||||
Serializer<unsigned8*>::Serialize(&mData[0], mData.size(), file);
|
||||
}
|
||||
|
||||
// Use this method to output the compressed texture as a vector of one byte characters
|
||||
std::vector<unsigned8> GetTexturePool() const override {
|
||||
size_t poolSize = GetTexturePoolSize();
|
||||
std::vector<unsigned8> res(poolSize);
|
||||
std::copy(mData.begin(), mData.end(), res.begin());
|
||||
return res;
|
||||
}
|
||||
size_t GetTexturePoolSize() const
|
||||
{
|
||||
return mData.size();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> GetAdditionalProperties() const override
|
||||
{
|
||||
std::map<std::string, std::string> res;
|
||||
res.insert(std::pair<std::string, std::string>("mask", std::to_string(mMask)));
|
||||
return res;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user