#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#define USE_SYSTEM_ZLIB
#include "compression_utils_portable.h"
#include "common/mathutil.h"
#include "frame_capture_binary_data.h"
#include <array>
#include <string>
namespace angle
{
size_t FrameCaptureBinaryData::totalSize() const
{
return ((mBlockCount - 1) * mDataBlockSize) + mCurrentBlockOffset;
}
bool FrameCaptureBinaryData::isSwapMode() const
{
return (mStoredBlocks > 0);
}
void FrameCaptureBinaryData::storeResidentBlocks()
{
if (!isSwapMode())
{
while (mData.size() > 1)
{
storeBlock();
mData.erase(mData.begin());
}
}
storeBlock();
}
void FrameCaptureBinaryData::updateGetDataCache(size_t blockId)
{
const ReplayBlockDescription &desc = mReplayBlockDescriptions[blockId];
mCacheBlockId = blockId;
mCacheBlockBeginOffset = desc.beginDataOffset;
mCacheBlockEndOffset = desc.endDataOffset;
mCacheBlockBaseAddress = desc.residentAddress;
if (blockId >= mMaxResidentBlockIndex)
{
mCurrentTransientLoadedBlockId = blockId;
}
}
bool FrameCaptureBinaryData::isBlockResident(size_t blockId) const
{
return (mReplayBlockDescriptions[blockId].residentAddress != nullptr);
}
void FrameCaptureBinaryData::setBlockResident(size_t blockId, uint8_t *address)
{
mReplayBlockDescriptions[blockId].residentAddress = address;
}
void FrameCaptureBinaryData::setBlockNonResident(size_t blockId)
{
mReplayBlockDescriptions[blockId].residentAddress = nullptr;
}
void FrameCaptureBinaryData::setBlockSize(size_t blockSize)
{
if (!gl::isPow2(blockSize))
{
FATAL() << "Binary Data File Blocksize specified is not a power of 2: " << blockSize;
}
mDataBlockSize = blockSize;
}
void FrameCaptureBinaryData::setBinaryDataSize(size_t binaryDataSize)
{
if (!gl::isPow2(binaryDataSize))
{
FATAL() << "Binary Data File Binary Data Size specified is not a power of 2: "
<< binaryDataSize;
}
mMaxResidentBinarySize = binaryDataSize;
}
std::vector<uint8_t> &FrameCaptureBinaryData::prepareStoreBlock(size_t blockId)
{
if (!isSwapMode())
{
mData.resize(mData.size() + 1);
}
mBlockCount = blockId + 1;
mData.back().resize(mDataBlockSize);
mCurrentBlockOffset = 0;
return mData.back();
}
std::vector<uint8_t> &FrameCaptureBinaryData::prepareLoadBlock(size_t blockId)
{
size_t destBlockIndex = std::min(blockId, mMaxResidentBlockIndex);
if (destBlockIndex >= mData.size())
{
mData.resize(destBlockIndex + 1);
}
if (isSwapBlock(destBlockIndex))
{
if (blockId != mCurrentTransientLoadedBlockId)
{
setBlockNonResident(mCurrentTransientLoadedBlockId);
}
mCurrentTransientLoadedBlockId = blockId;
}
mData.back().resize(mDataBlockSize);
mCurrentBlockOffset = 0;
return mData.back();
}
BinaryFileIndexInfo FrameCaptureBinaryData::appendFileIndex()
{
BinaryFileIndexInfo indexInfo;
indexInfo.version = kLongTraceVersionId;
indexInfo.blockSize = mDataBlockSize;
indexInfo.blockCount = mBlockCount;
indexInfo.residentSize = mMaxResidentBinarySize;
indexInfo.indexOffset = 0;
if (mIsBinaryDataCompressed)
{
size_t indexDataOffset = mFileStream->getPosition();
for (auto &entry : mFileIndex)
{
mFileStream->write(reinterpret_cast<const uint8_t *>(&entry), sizeof(FileBlockInfo));
}
indexInfo.indexOffset = indexDataOffset;
}
return indexInfo;
}
void FrameCaptureBinaryData::constructBlockDescIndex(size_t indexOffset)
{
if (mIsBinaryDataCompressed)
{
mFileStream->seek(indexOffset, kSeekBegin);
for (size_t i = 0; i < mBlockCount; i++)
{
FileBlockInfo blockInfo;
mFileStream->read(reinterpret_cast<uint8_t *>(&blockInfo), sizeof(FileBlockInfo));
ReplayBlockDescription blockDesc = {};
blockDesc.fileOffset = blockInfo.fileOffset;
blockDesc.beginDataOffset = blockInfo.dataOffset;
blockDesc.endDataOffset = blockInfo.dataOffset + blockInfo.dataSize - 1;
blockDesc.dataSize = blockInfo.dataSize;
mReplayBlockDescriptions.push_back(blockDesc);
}
}
else
{
mFileStream->seek(0, kSeekEnd);
size_t size = mFileStream->getPosition();
mFileStream->seek(0, kSeekBegin);
size_t remaining = size;
while (remaining > 0)
{
size_t dataSize = std::min(remaining, mDataBlockSize);
size_t offset = size - remaining;
ReplayBlockDescription blockDesc = {};
blockDesc.fileOffset = offset;
blockDesc.beginDataOffset = offset;
blockDesc.endDataOffset = offset + dataSize - 1;
blockDesc.dataSize = dataSize;
mReplayBlockDescriptions.push_back(blockDesc);
remaining -= dataSize;
}
}
}
size_t FrameCaptureBinaryData::append(const void *data, size_t size)
{
if (mData.empty())
{
prepareStoreBlock(0);
mBlockCount = 1;
}
ASSERT(totalSize() % kBinaryAlignment == 0);
size_t startingOffset = totalSize();
const size_t sizeToIncrease = rx::roundUpPow2(size, kBinaryAlignment);
if (mCurrentBlockOffset + sizeToIncrease > mDataBlockSize)
{
size_t newBlockId = (startingOffset + sizeToIncrease) / mDataBlockSize;
if (!isSwapMode())
{
if (newBlockId > mMaxResidentBlockIndex)
{
storeResidentBlocks();
}
else
{
}
}
else
{
storeBlock();
}
prepareStoreBlock(newBlockId);
startingOffset = totalSize();
}
memcpy(mData.back().data() + mCurrentBlockOffset, data, size);
mCurrentBlockOffset += sizeToIncrease;
return startingOffset;
}
const uint8_t *FrameCaptureBinaryData::getData(size_t offset)
{
if (offset >= mCacheBlockBeginOffset && offset < mCacheBlockEndOffset)
{
return (mCacheBlockBaseAddress + (offset - mCacheBlockBeginOffset));
}
size_t newBlockId = offset / mDataBlockSize;
if (!isBlockResident(newBlockId))
{
loadBlock(newBlockId);
}
updateGetDataCache(newBlockId);
return (mCacheBlockBaseAddress + (offset - mCacheBlockBeginOffset));
}
void FrameCaptureBinaryData::clear()
{
mCurrentBlockOffset = 0;
mFileIndex.clear();
mReplayBlockDescriptions.clear();
mData.clear();
}
class ZLibHelper
{
static constexpr int kZlibMemoryLevel = 8;
static constexpr int kWindowBitsToGetGzipHeader = 16;
public:
ZLibHelper(FrameCaptureBinaryData::Mode mode) : mMode(mode), mStream(), mInitialized(false)
{
int ret = 0;
mStream.zalloc = Z_NULL;
mStream.zfree = Z_NULL;
mStream.opaque = Z_NULL;
mStream.avail_in = 0;
mStream.next_in = Z_NULL;
if (mMode == FrameCaptureBinaryData::Mode::Load)
{
ret = inflateInit2(&mStream, MAX_WBITS + kWindowBitsToGetGzipHeader);
}
else if (mMode == FrameCaptureBinaryData::Mode::Store)
{
ret = deflateInit2(&mStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
MAX_WBITS + kWindowBitsToGetGzipHeader, kZlibMemoryLevel,
Z_DEFAULT_STRATEGY);
}
else
{
FATAL() << "Invalid Mode Enum in ZLibHelper";
}
if (ret != Z_OK)
{
FATAL() << "Zlib helper initialization failed: " << ret;
}
mInitialized = true;
}
~ZLibHelper()
{
if (mInitialized)
{
if (mMode == FrameCaptureBinaryData::Mode::Load)
{
inflateEnd(&mStream);
}
else if (mMode == FrameCaptureBinaryData::Mode::Store)
{
deflateEnd(&mStream);
}
else
{
FATAL() << "Invalid Mode Enum in ZLibHelper";
}
}
}
z_stream *getStream() { return &mStream; }
ZLibHelper(const ZLibHelper &) = delete;
ZLibHelper &operator=(const ZLibHelper &) = delete;
ZLibHelper(ZLibHelper &&) = delete;
ZLibHelper &operator=(ZLibHelper &&) = delete;
private:
FrameCaptureBinaryData::Mode mMode;
z_stream mStream;
bool mInitialized;
};
void FrameCaptureBinaryData::initializeBinaryDataStore(bool compression,
const std::string &outDir,
const std::string &fileName)
{
std::string binaryDataFileName = outDir + fileName;
mStoredBlocks = 0;
mIsBinaryDataCompressed = compression;
if ((mMaxResidentBinarySize / mDataBlockSize) <= 1)
{
FATAL() << "Error,insufficient resident memory specified or available";
}
mMaxResidentBlockIndex = (mMaxResidentBinarySize / mDataBlockSize) - 1;
mFileStream = new FileStream(binaryDataFileName, Mode::Store);
}
void FrameCaptureBinaryData::storeBlock()
{
std::vector<uint8_t> &storeBlock = mData.front();
if (mCaptureComplete && mData.size() == 1)
{
storeBlock.resize(mCurrentBlockOffset);
}
if (mIsBinaryDataCompressed)
{
ZLibHelper compressor(Mode::Store);
z_stream *zStream = compressor.getStream();
int deflateStatus = 0;
using ZlibBuffer = std::array<unsigned char, kZlibBufferSize>;
std::unique_ptr<ZlibBuffer> compressBuffer(new ZlibBuffer());
FileBlockInfo fileIndexEntry;
fileIndexEntry.fileOffset = mFileStream->getPosition();
fileIndexEntry.dataOffset = mStoredBlocks * mDataBlockSize;
fileIndexEntry.dataSize = storeBlock.size();
mFileIndex.push_back(fileIndexEntry);
const unsigned char *uncompressedDataPtr = storeBlock.data();
size_t remainingBytesToCompress = storeBlock.size();
while (remainingBytesToCompress > 0)
{
size_t bytesToCompress =
std::min(remainingBytesToCompress, static_cast<size_t>(kZlibBufferSize));
zStream->avail_in = static_cast<uInt>(bytesToCompress);
zStream->next_in = const_cast<unsigned char *>(uncompressedDataPtr);
do
{
zStream->avail_out = kZlibBufferSize;
zStream->next_out = compressBuffer->data();
int flushMode = Z_NO_FLUSH;
if (remainingBytesToCompress <= kZlibBufferSize)
{
flushMode = Z_FINISH;
}
deflateStatus = deflate(zStream, flushMode);
if (deflateStatus == Z_STREAM_ERROR)
{
FATAL() << "Error during deflate: Z_STREAM_ERROR";
}
unsigned bytesCompressed = kZlibBufferSize - zStream->avail_out;
mFileStream->write(compressBuffer->data(), bytesCompressed);
} while (zStream->avail_out == 0);
uncompressedDataPtr += bytesToCompress;
remainingBytesToCompress -= bytesToCompress;
}
}
else
{
mFileStream->write(storeBlock.data(), storeBlock.size());
}
mStoredBlocks++;
}
BinaryFileIndexInfo FrameCaptureBinaryData::closeBinaryDataStore()
{
mCaptureComplete = true;
storeResidentBlocks();
BinaryFileIndexInfo indexInfo;
indexInfo = appendFileIndex();
clear();
return indexInfo;
}
void FrameCaptureBinaryData::configureBinaryDataLoader(bool compression,
size_t blockCount,
size_t blockSize,
size_t residentSize,
size_t indexOffset,
const std::string &fileName)
{
mIsBinaryDataCompressed = compression;
mFileName = fileName;
mMaxResidentBinarySize = residentSize;
mDataBlockSize = blockSize;
mBlockCount = blockCount;
mMaxResidentBlockIndex = (mMaxResidentBinarySize / mDataBlockSize) - 1;
mCurrentTransientLoadedBlockId = mMaxResidentBlockIndex;
mIndexOffset = indexOffset;
}
void FrameCaptureBinaryData::initializeBinaryDataLoader()
{
mFileStream = new FileStream(mFileName.c_str(), Mode::Load);
constructBlockDescIndex(mIndexOffset);
size_t blocksToPreload =
std::min(mReplayBlockDescriptions.size(), (mMaxResidentBlockIndex + 1));
for (size_t i = 0; i < blocksToPreload; i++)
{
loadBlock(i);
}
updateGetDataCache(0);
}
void FrameCaptureBinaryData::loadBlock(size_t blockId)
{
std::vector<uint8_t> &uncompressedDataBlock = prepareLoadBlock(blockId);
mFileStream->seek(mReplayBlockDescriptions[blockId].fileOffset, kSeekBegin);
if (mIsBinaryDataCompressed)
{
ZLibHelper decompressor(Mode::Load);
z_stream *zStream = decompressor.getStream();
int inflateStatus = 0;
size_t bytesDecompressed = 0;
using ZlibBuffer = std::array<unsigned char, kZlibBufferSize>;
std::unique_ptr<ZlibBuffer> compressedDataBuffer(new ZlibBuffer());
zStream->avail_out = static_cast<uInt>(mDataBlockSize);
zStream->next_out = uncompressedDataBlock.data();
do
{
if (zStream->avail_in == 0)
{
zStream->avail_in = static_cast<uInt>(
mFileStream->read(compressedDataBuffer->data(), kZlibBufferSize));
zStream->next_in = compressedDataBuffer->data();
}
do
{
int availableOutputSpace = static_cast<int>(mDataBlockSize - mCurrentBlockOffset);
zStream->avail_out = availableOutputSpace;
zStream->next_out = uncompressedDataBlock.data() + mCurrentBlockOffset;
inflateStatus = inflate(zStream, Z_NO_FLUSH);
ASSERT(inflateStatus != Z_STREAM_ERROR);
if (inflateStatus == Z_NEED_DICT || inflateStatus == Z_DATA_ERROR ||
inflateStatus == Z_MEM_ERROR)
{
FATAL() << "Zlib inflate failed: " << inflateStatus;
}
bytesDecompressed = availableOutputSpace - zStream->avail_out;
mCurrentBlockOffset += bytesDecompressed;
} while (zStream->avail_out == 0 && mCurrentBlockOffset < mDataBlockSize);
} while (inflateStatus != Z_STREAM_END && mCurrentBlockOffset != mDataBlockSize);
}
else
{
mCurrentBlockOffset = mFileStream->read(uncompressedDataBlock.data(), mDataBlockSize);
}
uncompressedDataBlock.resize(mCurrentBlockOffset);
setBlockResident(blockId, uncompressedDataBlock.data());
}
void FrameCaptureBinaryData::closeBinaryDataLoader()
{
clear();
}
int FileStreamSeek(FILE *stream, long long offset, int whence)
{
#if defined(ANGLE_PLATFORM_WINDOWS)
return _fseeki64(stream, offset, whence);
#else
return fseeko(stream, static_cast<off_t>(offset), whence);
#endif
}
long long FileStreamTell(FILE *stream)
{
#if defined(ANGLE_PLATFORM_WINDOWS)
return _ftelli64(stream);
#else
return ftello(stream);
#endif
}
void FileStream::write(const uint8_t *data, size_t size)
{
if (fwrite(data, 1, size, mFile) != size)
{
if (ferror(mFile))
{
FATAL() << "Error writing " << size << " bytes to binary data file.";
}
}
if (fflush(mFile) != 0)
{
FATAL() << "Error flushing data to binary data file.";
}
}
size_t FileStream::read(uint8_t *buffer, size_t size)
{
size_t readBytes = fread(buffer, 1, size, mFile);
if (readBytes < size && ferror(mFile))
{
FATAL() << "Error reading from binary data file.";
}
return readBytes;
}
void FileStream::seek(long long offset, int whence)
{
if (FileStreamSeek(mFile, offset, whence) != 0)
{
FATAL() << "Error seeking in binary data file with offset " << offset << " and whence "
<< whence;
}
}
size_t FileStream::getPosition()
{
long long offset = FileStreamTell(mFile);
if (offset == -1)
{
FATAL() << "Error getting position in binary data file " << mFilePath;
}
angle::CheckedNumeric<size_t> checkedOffset(offset);
size_t safeOffset = 0;
if (!checkedOffset.AssignIfValid(&safeOffset))
{
FATAL() << "ANGLE file seek position offset out of range";
}
return safeOffset;
}
}