Initial commit: Final state of the master project

This commit is contained in:
2017-09-16 09:41:37 +02:00
commit 696180d43b
832 changed files with 169717 additions and 0 deletions

View 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>(); }
};

View 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;
};
};

View 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;
}
};
}

View 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;
};

View 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>(); }
};

View 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>(); }
};

File diff suppressed because it is too large Load Diff

View 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;
};
};