Initial commit: Final state of the master project
This commit is contained in:
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