#pragma once #include "CompressedTexture.h" #include "BasicTexture.h" #include "../Material/Block.h" #include "../../inc/tbb/parallel_for.h" #include "BlockHashers.h" #include #include "../../core/Serializer.h" template> class BlockCompressedTexture : public CompressedTexture { private: size_t mActualSize; std::vector mBlockPointers; BlockTextureType mBlocks; unsigned mBlockSize; public: BlockCompressedTexture(unsigned blockSize) : mActualSize(0), mBlockPointers(std::vector()), 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 GetTexture(size_t fromIndex = 0) override { assert(fromIndex <= mActualSize); std::vector 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& materialPointers, size_t fromIndex = 0) override { // TODO: only copy if necessary std::vector 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, unsigned> blockIndices; if (fromIndex >= mBlockSize) { std::vector uncompressedBlocks = mBlocks.GetTexture(); for (size_t i = 0; i < highestIndex; i++) { Block cur(uncompressedBlocks, i * mBlockSize, i * mBlockSize + mBlockSize); blockIndices.insert(std::pair, 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 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 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 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, 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& 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, unsigned64>::Deserialize(mBlockPointers, file); // Read the blocks mBlocks.ReadFromFile(file); } void WriteToFile(std::ostream& file) const override { // Write the block pointers Serializer, unsigned64>::Serialize(mBlockPointers, file); // Write the blocks mBlocks.WriteToFile(file); } // Texture pool is taken from how the blocks are stored std::vector 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 GetAdditionalTexturePool() const { size_t textureSize = GetAdditionalTexturePoolSize(); std::vector 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 GetAdditionalProperties() const override { std::map res; res.insert(std::pair("blockSize", std::to_string(mBlockSize))); // Get the additional properties from the block texture std::map blockProperties = mBlocks.GetAdditionalProperties(); for (auto prop : blockProperties) res.insert(prop); return res; }; };