Files
renderdoc/renderdoc/serialise/zstdio.cpp
T
2023-02-01 12:23:32 +00:00

360 lines
9.8 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2023 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#define ZSTD_STATIC_LINKING_ONLY
#include "zstdio.h"
static const uint64_t zstdBlockSize = 128 * 1024;
static const uint64_t compressBlockSize = ZSTD_compressBound(zstdBlockSize);
ZSTDCompressor::ZSTDCompressor(StreamWriter *write, Ownership own) : Compressor(write, own)
{
m_Page = AllocAlignedBuffer(zstdBlockSize);
m_CompressBuffer = AllocAlignedBuffer(compressBlockSize);
m_PageOffset = 0;
m_Stream = ZSTD_createCStream();
}
ZSTDCompressor::~ZSTDCompressor()
{
ZSTD_freeCStream(m_Stream);
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
}
bool ZSTDCompressor::Write(const void *data, uint64_t numBytes)
{
// if we encountered a stream error this will be NULL
if(!m_CompressBuffer)
return false;
if(numBytes == 0)
return true;
// this is largely similar to LZ4Compressor, so check the comments there for more details.
// The only difference is that the lz4 streaming compression assumes a history of 64kb, where
// here we use a larger block size but no history must be maintained.
if(m_PageOffset + numBytes <= zstdBlockSize)
{
// simplest path, no page wrapping/spanning at all
memcpy(m_Page + m_PageOffset, data, (size_t)numBytes);
m_PageOffset += numBytes;
return true;
}
else
{
const byte *src = (const byte *)data;
// copy whatever will fit on this page
{
uint64_t firstBytes = zstdBlockSize - m_PageOffset;
memcpy(m_Page + m_PageOffset, src, (size_t)firstBytes);
m_PageOffset += firstBytes;
numBytes -= firstBytes;
src += firstBytes;
}
bool success = true;
while(success && numBytes > 0)
{
// flush page
success &= FlushPage();
if(!success)
return success;
// how many bytes can we copy in this page?
uint64_t partialBytes = RDCMIN(zstdBlockSize, numBytes);
memcpy(m_Page, src, (size_t)partialBytes);
// advance the source pointer, dest offset, and remove the bytes we read
m_PageOffset += partialBytes;
numBytes -= partialBytes;
src += partialBytes;
}
return success;
}
}
bool ZSTDCompressor::Finish()
{
// This function just writes the current page and closes zstd. Since we assume all blocks are
// precisely 64kb in size
// only the last one can be smaller, so we only write a partial page when finishing.
// Calling Write() after Finish() is illegal
return FlushPage();
}
bool ZSTDCompressor::FlushPage()
{
// if we encountered a stream error this will be NULL
if(!m_CompressBuffer)
return false;
ZSTD_inBuffer in = {m_Page, (size_t)m_PageOffset, 0};
ZSTD_outBuffer out = {m_CompressBuffer, ZSTD_CStreamOutSize(), 0};
bool success = true;
success &= CompressZSTDFrame(in, out);
// if there was an error, bail
if(!m_CompressBuffer)
return false;
// a bit redundant to write this but it means we can read the entire frame without
// doing multiple reads
success &= m_Write->Write((uint32_t)out.pos);
success &= m_Write->Write(m_CompressBuffer, out.pos);
// start writing to the start of the page again
m_PageOffset = 0;
return success;
}
bool ZSTDCompressor::CompressZSTDFrame(ZSTD_inBuffer &in, ZSTD_outBuffer &out)
{
size_t err = ZSTD_initCStream(m_Stream, 7);
if(ZSTD_isError(err))
{
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
m_Page = m_CompressBuffer = NULL;
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed, "ZSTD compression failed: %s",
ZSTD_getErrorName(err));
return false;
}
// keep calling compressStream until everything is consumed
while(in.pos < in.size)
{
size_t inpos = in.pos;
size_t outpos = out.pos;
err = ZSTD_compressStream(m_Stream, &out, &in);
if(ZSTD_isError(err) || (inpos == in.pos && outpos == out.pos))
{
if(ZSTD_isError(err))
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed, "ZSTD compression failed: %s",
ZSTD_getErrorName(err));
else
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed,
"ZSTD compression failed, no progress made");
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
m_Page = m_CompressBuffer = NULL;
return false;
}
}
err = ZSTD_endStream(m_Stream, &out);
if(ZSTD_isError(err) || err != 0)
{
if(ZSTD_isError(err))
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed, "ZSTD compression failed: %s",
ZSTD_getErrorName(err));
else
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed,
"Error compressing, couldn't end stream");
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
m_Page = m_CompressBuffer = NULL;
return false;
}
return true;
}
ZSTDDecompressor::ZSTDDecompressor(StreamReader *read, Ownership own) : Decompressor(read, own)
{
m_Page = AllocAlignedBuffer(zstdBlockSize);
m_CompressBuffer = AllocAlignedBuffer(compressBlockSize);
m_PageOffset = 0;
m_PageLength = 0;
m_Stream = ZSTD_createDStream();
}
ZSTDDecompressor::~ZSTDDecompressor()
{
ZSTD_freeDStream(m_Stream);
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
}
bool ZSTDDecompressor::Recompress(Compressor *comp)
{
bool success = true;
while(success && !m_Read->AtEnd())
{
success &= FillPage();
if(success)
{
success &= comp->Write(m_Page, m_PageLength);
if(!success)
m_Error = comp->GetError();
}
}
success &= comp->Finish();
return success;
}
bool ZSTDDecompressor::Read(void *data, uint64_t numBytes)
{
// if we encountered a stream error this will be NULL
if(!m_CompressBuffer)
return false;
if(numBytes == 0)
return true;
// this is simpler than the ZstdWriter::Write() implementation.
// At any point, m_Page contains the current window with uncompressed bytes.
// If we can satisfy a read from it, then we just memcpy and increment m_PageOffset.
// When we wrap around, we do a partial memcpy from m_Page, then decompress more bytes.
// if we already have all the data in-memory, just copy and return
uint64_t available = m_PageLength - m_PageOffset;
if(numBytes <= available)
{
memcpy(data, m_Page + m_PageOffset, (size_t)numBytes);
m_PageOffset += numBytes;
return true;
}
byte *dst = (byte *)data;
// copy what remains in m_Page
memcpy(dst, m_Page + m_PageOffset, (size_t)available);
// adjust what needs to be copied
dst += available;
numBytes -= available;
bool success = true;
while(success && numBytes > 0)
{
success &= FillPage();
if(!success)
return success;
// if we can now satisfy the remainder of the read, do so and return
if(numBytes <= m_PageLength)
{
memcpy(dst, m_Page, (size_t)numBytes);
m_PageOffset += numBytes;
return success;
}
// otherwise copy this page in and continue
memcpy(dst, m_Page, (size_t)m_PageLength);
dst += m_PageLength;
numBytes -= m_PageLength;
}
return success;
}
bool ZSTDDecompressor::FillPage()
{
uint32_t compSize = 0;
bool success = true;
success &= m_Read->Read(compSize);
success &= m_Read->Read(m_CompressBuffer, compSize);
if(!success)
{
m_Error = m_Read->GetError();
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
m_Page = m_CompressBuffer = NULL;
return false;
}
size_t err = ZSTD_initDStream(m_Stream);
if(ZSTD_isError(err))
{
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed, "ZSTD decompression failed: %s",
ZSTD_getErrorName(err));
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
m_Page = m_CompressBuffer = NULL;
return false;
}
ZSTD_inBuffer in = {m_CompressBuffer, compSize, 0};
ZSTD_outBuffer out = {m_Page, zstdBlockSize, 0};
// keep calling compressStream until everything is consumed
while(in.pos < in.size)
{
size_t inpos = in.pos;
size_t outpos = out.pos;
err = ZSTD_decompressStream(m_Stream, &out, &in);
if(ZSTD_isError(err) || (inpos == in.pos && outpos == out.pos))
{
if(ZSTD_isError(err))
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed, "ZSTD decompression failed: %s",
ZSTD_getErrorName(err));
else
SET_ERROR_RESULT(m_Error, ResultCode::CompressionFailed,
"ZSTD decompression failed, no progress made");
FreeAlignedBuffer(m_Page);
FreeAlignedBuffer(m_CompressBuffer);
m_Page = m_CompressBuffer = NULL;
return false;
}
}
m_PageOffset = 0;
m_PageLength = out.pos;
return success;
}