265 lines
9.0 KiB
C++
265 lines
9.0 KiB
C++
#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;
|
|
};
|
|
}; |