mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-28 12:51:03 +00:00
6fbb247efd
* renderdoc/api/replay/ contains all the headers for using the replay and analysis side of renderdoc (like in a UI or auto-testing tool) * renderdoc/api/app/ contains the headers if you wanted to write a renderdoc-aware application.
1156 lines
25 KiB
C++
1156 lines
25 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2014 Crytek
|
|
*
|
|
* 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.
|
|
******************************************************************************/
|
|
|
|
|
|
#include "serialiser.h"
|
|
|
|
#include "core/core.h"
|
|
|
|
#include "common/string_utils.h"
|
|
|
|
#if !defined(RELEASE)
|
|
|
|
int64_t Chunk::m_LiveChunks = 0;
|
|
int64_t Chunk::m_TotalMem = 0;
|
|
int64_t Chunk::m_MaxChunks = 0;
|
|
|
|
#endif
|
|
|
|
const uint32_t Serialiser::MAGIC_HEADER = MAKE_FOURCC('R', 'D', 'O', 'C');
|
|
const size_t Serialiser::BufferAlignment = 16;
|
|
|
|
Chunk::Chunk(Serialiser *ser, uint32_t chunkType, bool temporary)
|
|
{
|
|
m_Length = (uint32_t)ser->GetOffset();
|
|
|
|
RDCASSERT(ser->GetOffset() < 0xffffffff);
|
|
|
|
m_ChunkType = chunkType;
|
|
|
|
m_Temporary = temporary;
|
|
|
|
if(ser->HasAlignedData())
|
|
{
|
|
m_Data = Serialiser::AllocAlignedBuffer(m_Length);
|
|
m_AlignedData = true;
|
|
}
|
|
else
|
|
{
|
|
m_Data = new byte[m_Length];
|
|
m_AlignedData = false;
|
|
}
|
|
|
|
memcpy(m_Data, ser->GetRawPtr(0), m_Length);
|
|
|
|
m_DebugStr = ser->GetDebugStr();
|
|
|
|
ser->Rewind();
|
|
|
|
#if !defined(RELEASE)
|
|
int64_t newval = Atomic::Inc64(&m_LiveChunks);
|
|
Atomic::ExchAdd64(&m_TotalMem, m_Length);
|
|
|
|
if(newval > m_MaxChunks)
|
|
{
|
|
int breakpointme=0;
|
|
}
|
|
|
|
m_MaxChunks = RDCMAX(newval, m_MaxChunks);
|
|
#endif
|
|
}
|
|
|
|
Chunk::~Chunk()
|
|
{
|
|
#if !defined(RELEASE)
|
|
Atomic::Dec64(&m_LiveChunks);
|
|
Atomic::ExchAdd64(&m_TotalMem, -int64_t(m_Length));
|
|
#endif
|
|
|
|
if(m_AlignedData)
|
|
{
|
|
if(m_Data)
|
|
Serialiser::FreeAlignedBuffer(m_Data);
|
|
|
|
m_Data = NULL;
|
|
}
|
|
else
|
|
{
|
|
SAFE_DELETE_ARRAY(m_Data);
|
|
}
|
|
}
|
|
|
|
void Serialiser::Reset()
|
|
{
|
|
if(m_ResolverThread != 0)
|
|
{
|
|
m_ResolverThreadKillSignal = true;
|
|
|
|
Threading::JoinThread(m_ResolverThread);
|
|
Threading::CloseThread(m_ResolverThread);
|
|
m_ResolverThread = 0;
|
|
}
|
|
|
|
m_DebugText = "";
|
|
|
|
m_HasError = false;
|
|
m_ErrorCode = eSerError_None;
|
|
|
|
m_Mode = NONE;
|
|
|
|
m_Indent = 0;
|
|
|
|
SAFE_DELETE_ARRAY(m_pCallstack);
|
|
SAFE_DELETE_ARRAY(m_pResolver);
|
|
if(m_Buffer)
|
|
{
|
|
FreeAlignedBuffer(m_Buffer);
|
|
m_Buffer = NULL;
|
|
}
|
|
|
|
m_ChunkLookup = NULL;
|
|
|
|
m_HasResolver = false;
|
|
|
|
m_AlignedData = false;
|
|
|
|
m_ReadFileHandle = NULL;
|
|
|
|
m_Buffer = NULL;
|
|
m_BufferSize = 0;
|
|
m_BufferHead = NULL;
|
|
}
|
|
|
|
Serialiser::Serialiser(size_t length, const byte *memoryBuf, bool fileheader)
|
|
: m_pCallstack(NULL), m_pResolver(NULL), m_Buffer(NULL)
|
|
{
|
|
m_ResolverThread = 0;
|
|
|
|
Reset();
|
|
|
|
m_DebugTextWriting = false;
|
|
|
|
m_Mode = READING;
|
|
m_DebugEnabled = false;
|
|
|
|
if(!fileheader)
|
|
{
|
|
m_HasResolver = false;
|
|
|
|
m_FileStartOffset = 0;
|
|
|
|
m_BufferSize = length;
|
|
m_CurrentBufferSize = (size_t)m_BufferSize;
|
|
m_BufferHead = m_Buffer = AllocAlignedBuffer(m_CurrentBufferSize);
|
|
m_ReadOffset = 0;
|
|
|
|
memcpy(m_Buffer, memoryBuf + m_FileStartOffset, m_CurrentBufferSize);
|
|
return;
|
|
}
|
|
|
|
DebuggerHeader *header = (DebuggerHeader *)memoryBuf;
|
|
|
|
if(length < sizeof(DebuggerHeader))
|
|
{
|
|
RDCERR("Can't read from in-memory buffer, truncated header");
|
|
m_ErrorCode = eSerError_Corrupt;
|
|
m_HasError = true;
|
|
return;
|
|
}
|
|
|
|
if(header->magic != MAGIC_HEADER)
|
|
{
|
|
char magicRef[5] = { 0 };
|
|
char magicFile[5] = { 0 };
|
|
memcpy(magicRef, &MAGIC_HEADER, sizeof(uint32_t));
|
|
memcpy(magicFile, &header->magic, sizeof(uint32_t));
|
|
RDCERR("Invalid in-memory buffer. Expected magic %hs, got %hs", magicRef, magicFile);
|
|
|
|
m_ErrorCode = eSerError_Corrupt;
|
|
m_HasError = true;
|
|
return;
|
|
}
|
|
|
|
if(header->version != SERIALISE_VERSION)
|
|
{
|
|
RDCERR("Capture file from wrong version. This program is on logfile version %llu, file is logfile version %llu", SERIALISE_VERSION, header->version);
|
|
|
|
m_ErrorCode = eSerError_UnsupportedVersion;
|
|
m_HasError = true;
|
|
return;
|
|
}
|
|
|
|
if(header->fileSize < length)
|
|
{
|
|
RDCERR("Overlong in-memory buffer. Expected length 0x016llx, got 0x016llx", header->fileSize, length);
|
|
|
|
m_ErrorCode = eSerError_Corrupt;
|
|
m_HasError = true;
|
|
return;
|
|
}
|
|
|
|
m_HasResolver = header->resolveDBSize > 0;
|
|
|
|
m_FileStartOffset = AlignUp16(sizeof(DebuggerHeader) + header->resolveDBSize);
|
|
|
|
m_BufferSize = length-m_FileStartOffset;
|
|
m_CurrentBufferSize = (size_t)m_BufferSize;
|
|
m_BufferHead = m_Buffer = AllocAlignedBuffer(m_CurrentBufferSize);
|
|
m_ReadOffset = 0;
|
|
|
|
memcpy(m_Buffer, memoryBuf + m_FileStartOffset, m_CurrentBufferSize);
|
|
}
|
|
|
|
Serialiser::Serialiser(const wchar_t *path, Mode mode, bool debugMode)
|
|
: m_pCallstack(NULL), m_pResolver(NULL), m_Buffer(NULL)
|
|
{
|
|
m_ResolverThread = 0;
|
|
|
|
Reset();
|
|
|
|
m_Filename = path ? path : L"";
|
|
|
|
m_DebugTextWriting = false;
|
|
|
|
m_Mode = mode;
|
|
m_DebugEnabled = debugMode;
|
|
|
|
DebuggerHeader header;
|
|
|
|
if(mode == READING)
|
|
{
|
|
m_ReadFileHandle = FileIO::fopen(m_Filename.c_str(), L"rb");
|
|
|
|
if(!m_ReadFileHandle)
|
|
{
|
|
RDCERR("Can't open capture file '%ls' for read - errno %d", m_Filename.c_str(), errno);
|
|
m_ErrorCode = eSerError_FileIO;
|
|
m_HasError = true;
|
|
return;
|
|
}
|
|
|
|
RDCDEBUG("Opened capture file for read");
|
|
|
|
FileIO::fread(&header, 1, sizeof(DebuggerHeader), m_ReadFileHandle);
|
|
|
|
if(header.magic != MAGIC_HEADER)
|
|
{
|
|
char magicRef[5] = { 0 };
|
|
char magicFile[5] = { 0 };
|
|
memcpy(magicRef, &MAGIC_HEADER, sizeof(uint32_t));
|
|
memcpy(magicFile, &header.magic, sizeof(uint32_t));
|
|
RDCERR("Invalid capture file. Expected magic %hs, got %hs", magicRef, magicFile);
|
|
|
|
m_ErrorCode = eSerError_Corrupt;
|
|
m_HasError = true;
|
|
FileIO::fclose(m_ReadFileHandle);
|
|
m_ReadFileHandle = 0;
|
|
return;
|
|
}
|
|
|
|
if(header.version != SERIALISE_VERSION)
|
|
{
|
|
RDCERR("Capture file from wrong version. This program is logfile version %llu, file is logfile version %llu", SERIALISE_VERSION, header.version);
|
|
|
|
m_ErrorCode = eSerError_UnsupportedVersion;
|
|
m_HasError = true;
|
|
FileIO::fclose(m_ReadFileHandle);
|
|
m_ReadFileHandle = 0;
|
|
return;
|
|
}
|
|
|
|
FileIO::fseek64(m_ReadFileHandle, 0, SEEK_END);
|
|
|
|
uint64_t realLength = FileIO::ftell64(m_ReadFileHandle);
|
|
if(header.fileSize != realLength)
|
|
{
|
|
RDCERR("Truncated/overlong capture file. Expected length 0x016llx, got 0x016llx", header.fileSize, realLength);
|
|
|
|
m_ErrorCode = eSerError_Corrupt;
|
|
m_HasError = true;
|
|
FileIO::fclose(m_ReadFileHandle);
|
|
m_ReadFileHandle = 0;
|
|
return;
|
|
}
|
|
|
|
m_HasResolver = header.resolveDBSize > 0;
|
|
|
|
m_FileStartOffset = AlignUp16(sizeof(DebuggerHeader) + header.resolveDBSize);
|
|
|
|
m_BufferSize = realLength-m_FileStartOffset;
|
|
m_CurrentBufferSize = (size_t)RDCMIN(m_BufferSize, (uint64_t)64*1024);
|
|
m_BufferHead = m_Buffer = AllocAlignedBuffer(m_CurrentBufferSize);
|
|
m_ReadOffset = 0;
|
|
|
|
ReadFromFile(0, m_CurrentBufferSize);
|
|
}
|
|
else
|
|
{
|
|
m_pResolver = NULL;
|
|
|
|
if(m_Filename != L"")
|
|
{
|
|
m_BufferSize = 0;
|
|
m_BufferHead = m_Buffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
m_BufferSize = 128*1024;
|
|
m_BufferHead = m_Buffer = AllocAlignedBuffer((size_t)m_BufferSize);
|
|
}
|
|
|
|
m_ReadOffset = 0;
|
|
m_FileStartOffset = 0;
|
|
}
|
|
}
|
|
|
|
void Serialiser::ReadFromFile(uint64_t destOffs, size_t chunkLen)
|
|
{
|
|
RDCASSERT(m_ReadFileHandle);
|
|
|
|
if(m_ReadFileHandle == NULL)
|
|
return;
|
|
|
|
FileIO::fseek64(m_ReadFileHandle, m_FileStartOffset+destOffs, SEEK_SET);
|
|
FileIO::fread(m_Buffer + destOffs - m_ReadOffset, 1, chunkLen, m_ReadFileHandle);
|
|
}
|
|
|
|
byte *Serialiser::AllocAlignedBuffer(size_t size)
|
|
{
|
|
byte *rawAlloc = NULL;
|
|
|
|
#if defined(__EXCEPTIONS) || defined(_CPPUNWIND)
|
|
try
|
|
#endif
|
|
{
|
|
rawAlloc = new byte[size+sizeof(byte*)+16];
|
|
}
|
|
#if defined(__EXCEPTIONS) || defined(_CPPUNWIND)
|
|
catch(std::bad_alloc&)
|
|
{
|
|
rawAlloc = NULL;
|
|
}
|
|
#endif
|
|
|
|
if(rawAlloc == NULL)
|
|
RDCFATAL("Allocation for %llu bytes failed", (uint64_t)size);
|
|
|
|
RDCASSERT(rawAlloc);
|
|
|
|
byte *alignedAlloc = (byte *)AlignUp16((size_t)(rawAlloc+sizeof(byte*)));
|
|
|
|
byte **realPointer = (byte **)alignedAlloc;
|
|
realPointer[-1] = rawAlloc;
|
|
|
|
return alignedAlloc;
|
|
}
|
|
|
|
void Serialiser::FreeAlignedBuffer(byte *buf)
|
|
{
|
|
byte **realPointer = (byte **)buf;
|
|
byte *rawAlloc = realPointer[-1];
|
|
|
|
delete[] rawAlloc;
|
|
}
|
|
|
|
void Serialiser::InitCallstackResolver()
|
|
{
|
|
if(m_pResolver == NULL && m_ResolverThread == 0)
|
|
{
|
|
m_ResolverThreadKillSignal = false;
|
|
m_ResolverThread = Threading::CreateThread(&Serialiser::CreateResolver, (void *)this);
|
|
}
|
|
}
|
|
|
|
void Serialiser::SetCallstack(uint64_t *levels, size_t numLevels)
|
|
{
|
|
SAFE_DELETE(m_pCallstack);
|
|
|
|
if(levels != NULL && numLevels != 0)
|
|
m_pCallstack = Callstack::Load(levels, numLevels);
|
|
}
|
|
|
|
void Serialiser::CreateResolver(void *ths)
|
|
{
|
|
Serialiser *ser = (Serialiser *)ths;
|
|
FILE *binFile = FileIO::fopen(ser->m_Filename.c_str(), L"rb");
|
|
|
|
if(!binFile)
|
|
{
|
|
RDCERR("Can't open capture file '%ls' for read - errno %d", ser->m_Filename.c_str(), errno);
|
|
return;
|
|
}
|
|
|
|
DebuggerHeader header;
|
|
FileIO::fread(&header, 1, sizeof(DebuggerHeader), binFile);
|
|
|
|
if(header.magic != MAGIC_HEADER)
|
|
{
|
|
RDCERR("Invalid capture file. Expected 0x%08lx, got 0x%08llx", MAGIC_HEADER, header.magic);
|
|
FileIO::fclose(binFile);
|
|
return;
|
|
}
|
|
|
|
if(header.version != SERIALISE_VERSION)
|
|
{
|
|
RDCERR("Capture file from wrong version. This program is logfile version %llu, file is logfile version %llu", SERIALISE_VERSION, header.version);
|
|
FileIO::fclose(binFile);
|
|
return;
|
|
}
|
|
|
|
FileIO::fseek64(binFile, 0, SEEK_END);
|
|
|
|
uint64_t realLength = FileIO::ftell64(binFile);
|
|
if(header.fileSize != realLength)
|
|
{
|
|
RDCERR("Truncated/overlong capture file. Expected length 0x08llx, got 0x08lx", header.fileSize, realLength);
|
|
FileIO::fclose(binFile);
|
|
return;
|
|
}
|
|
|
|
if(header.resolveDBSize == 0)
|
|
{
|
|
RDCWARN("Trying to create resolver when no resolve DB is in the capture file");
|
|
FileIO::fclose(binFile);
|
|
return;
|
|
}
|
|
|
|
FileIO::fseek64(binFile, sizeof(DebuggerHeader), SEEK_SET);
|
|
|
|
RDCASSERT(header.resolveDBSize < 0xffffffff);
|
|
|
|
char *resolveDB = new char[(size_t)header.resolveDBSize];
|
|
|
|
FileIO::fread(resolveDB, 1, (size_t)header.resolveDBSize, binFile);
|
|
|
|
FileIO::fclose(binFile);
|
|
|
|
wstring dir = dirname(wstring(ser->m_Filename));
|
|
|
|
Callstack::StackResolver *resolver = Callstack::MakeResolver(resolveDB, (size_t)header.resolveDBSize, dir, &ser->m_ResolverThreadKillSignal);
|
|
|
|
ser->m_pResolver = resolver;
|
|
|
|
SAFE_DELETE_ARRAY(resolveDB);
|
|
}
|
|
|
|
uint64_t Serialiser::FlushToDisk()
|
|
{
|
|
if(m_Filename != L"" && !m_HasError && m_Mode == WRITING)
|
|
{
|
|
RDCDEBUG("writing capture files");
|
|
|
|
if(m_DebugEnabled && !m_DebugText.empty())
|
|
{
|
|
FILE *dbgFile = FileIO::fopen((m_Filename + L".txt").c_str(), L"wb");
|
|
|
|
if(!dbgFile)
|
|
{
|
|
RDCERR("Can't open debug capture file '%ls'", (m_Filename + L".txt").c_str());
|
|
}
|
|
else
|
|
{
|
|
FileIO::fwrite(m_DebugText.c_str(), 1, m_DebugText.length(), dbgFile);
|
|
|
|
FileIO::fclose(dbgFile);
|
|
}
|
|
}
|
|
|
|
FILE *binFile = FileIO::fopen(m_Filename.c_str(), L"wb");
|
|
|
|
if(!binFile)
|
|
{
|
|
RDCERR("Can't open capture file '%ls' for write, errno %d", m_Filename.c_str(), errno);
|
|
m_ErrorCode = eSerError_FileIO;
|
|
m_HasError = true;
|
|
return 0;
|
|
}
|
|
|
|
RDCDEBUG("Opened capture file for write");
|
|
|
|
DebuggerHeader header;
|
|
|
|
char *symbolDB = NULL;
|
|
size_t symbolDBSize = 0;
|
|
|
|
if(RenderDoc::Inst().GetCaptureOptions().CaptureCallstacks ||
|
|
RenderDoc::Inst().GetCaptureOptions().CaptureCallstacksOnlyDraws)
|
|
{
|
|
// get symbol database
|
|
Callstack::GetLoadedModules(symbolDB, symbolDBSize);
|
|
|
|
symbolDB = new char[symbolDBSize];
|
|
symbolDBSize = 0;
|
|
|
|
Callstack::GetLoadedModules(symbolDB, symbolDBSize);
|
|
}
|
|
|
|
header.resolveDBSize = symbolDBSize;
|
|
|
|
// write header
|
|
FileIO::fwrite(&header, 1, sizeof(DebuggerHeader), binFile);
|
|
|
|
// write symbol database
|
|
if(symbolDBSize > 0)
|
|
FileIO::fwrite(symbolDB, 1, symbolDBSize, binFile);
|
|
|
|
static const byte padding[BufferAlignment] = {0};
|
|
|
|
size_t offs = sizeof(DebuggerHeader) + symbolDBSize;
|
|
size_t alignedoffs = AlignUp16(offs);
|
|
|
|
FileIO::fwrite(padding, 1, alignedoffs-offs, binFile);
|
|
|
|
offs = alignedoffs;
|
|
|
|
// write serialise contents
|
|
for(size_t i=0; i < m_Chunks.size(); i++)
|
|
{
|
|
Chunk *chunk = m_Chunks[i];
|
|
|
|
size_t alignedoffs = AlignUp16(offs);
|
|
|
|
if(offs != alignedoffs && chunk->IsAligned())
|
|
{
|
|
uint16_t chunkIdx = 0; // write a '0' chunk that indicates special behaviour
|
|
FileIO::fwrite(&chunkIdx, sizeof(chunkIdx), 1, binFile);
|
|
offs += sizeof(chunkIdx);
|
|
|
|
uint8_t controlByte = 0; // control byte 0 indicates padding
|
|
FileIO::fwrite(&controlByte, sizeof(controlByte), 1, binFile);
|
|
offs += sizeof(controlByte);
|
|
|
|
offs++; // we will have to write out a byte indicating how much padding exists, so add 1
|
|
alignedoffs = AlignUp16(offs);
|
|
|
|
RDCCOMPILE_ASSERT(BufferAlignment < 0x100, "Buffer alignment must be less than 256"); // with a byte at most indicating how many bytes to pad,
|
|
// this is our maximal representable alignment
|
|
|
|
uint8_t padLength = (alignedoffs-offs)&0xff;
|
|
FileIO::fwrite(&padLength, sizeof(padLength), 1, binFile);
|
|
|
|
// we might have padded with the control bytes, so only write some bytes if we need to
|
|
if(padLength > 0)
|
|
{
|
|
FileIO::fwrite(padding, 1, alignedoffs-offs, binFile);
|
|
offs += alignedoffs-offs;
|
|
}
|
|
}
|
|
|
|
FileIO::fwrite(chunk->GetData(), 1, chunk->GetLength(), binFile);
|
|
|
|
offs += chunk->GetLength();
|
|
|
|
if(chunk->IsTemporary())
|
|
SAFE_DELETE(chunk);
|
|
}
|
|
|
|
m_Chunks.clear();
|
|
|
|
header.fileSize = offs;
|
|
|
|
FileIO::fseek64(binFile, 0, SEEK_SET);
|
|
|
|
// write header with correct filesize (accounting for all padding)
|
|
FileIO::fwrite(&header, 1, sizeof(DebuggerHeader), binFile);
|
|
|
|
FileIO::fclose(binFile);
|
|
|
|
SAFE_DELETE_ARRAY(symbolDB);
|
|
|
|
return header.fileSize;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Serialiser::~Serialiser()
|
|
{
|
|
if(m_ResolverThread != 0)
|
|
{
|
|
m_ResolverThreadKillSignal = true;
|
|
Threading::JoinThread(m_ResolverThread);
|
|
Threading::CloseThread(m_ResolverThread);
|
|
m_ResolverThread = 0;
|
|
}
|
|
|
|
if(m_ReadFileHandle)
|
|
{
|
|
FileIO::fclose(m_ReadFileHandle);
|
|
m_ReadFileHandle = 0;
|
|
}
|
|
|
|
for(size_t i=0; i < m_Chunks.size(); i++)
|
|
{
|
|
if(m_Chunks[i]->IsTemporary())
|
|
SAFE_DELETE(m_Chunks[i]);
|
|
}
|
|
|
|
m_Chunks.clear();
|
|
|
|
SAFE_DELETE(m_pResolver);
|
|
SAFE_DELETE(m_pCallstack);
|
|
if(m_Buffer)
|
|
{
|
|
FreeAlignedBuffer(m_Buffer);
|
|
m_Buffer = NULL;
|
|
}
|
|
m_Buffer = NULL;
|
|
m_BufferHead = NULL;
|
|
}
|
|
|
|
void Serialiser::DebugPrint(const char *fmt, ...)
|
|
{
|
|
if(m_HasError)
|
|
{
|
|
RDCERR("Debug printing with error state serialiser");
|
|
return;
|
|
}
|
|
|
|
char tmpBuf[1024];
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
StringFormat::vsnprintf( tmpBuf, 1023, fmt, args );
|
|
tmpBuf[1023] = '\0';
|
|
va_end(args);
|
|
|
|
m_DebugText += GetIndent();
|
|
m_DebugText += tmpBuf;
|
|
|
|
#ifdef DEBUG_TEXT_SERIALISER
|
|
FILE *f = FileIO::fopen(m_Filename.c_str(), L"ab");
|
|
|
|
if(f)
|
|
{
|
|
FileIO::fwrite(tmpBuf, 1, strlen(tmpBuf), f);
|
|
|
|
FileIO::fclose(f);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
uint32_t Serialiser::PushContext(const char *name, uint32_t chunkIdx, bool smallChunk)
|
|
{
|
|
// if writing, and chunkidx isn't 0 (debug non-scope), then either we're nested
|
|
// or we should be writing into the start of the serialiser. A serialiser should
|
|
// only ever have one chunk in it
|
|
RDCASSERT(m_Mode < WRITING || m_Indent > 0 || GetOffset() == 0 || chunkIdx == 0);
|
|
|
|
// we should not be pushing contexts directly into a file serialiser
|
|
RDCASSERT(m_Mode < WRITING || m_Filename.empty());
|
|
|
|
if(m_Mode >= WRITING)
|
|
{
|
|
if(chunkIdx > 0)
|
|
{
|
|
uint16_t c = chunkIdx&0x3fff;
|
|
RDCASSERT(chunkIdx <= 0x3fff);
|
|
|
|
/////////////////
|
|
|
|
Callstack::Stackwalk *call = NULL;
|
|
|
|
if(m_Indent == 0)
|
|
{
|
|
if(RenderDoc::Inst().GetCaptureOptions().CaptureCallstacks &&
|
|
!RenderDoc::Inst().GetCaptureOptions().CaptureCallstacksOnlyDraws)
|
|
{
|
|
call = Callstack::Collect();
|
|
|
|
RDCASSERT(call->NumLevels() < 0xff);
|
|
}
|
|
}
|
|
|
|
if(call)
|
|
c |= 0x8000;
|
|
if(smallChunk)
|
|
c |= 0x4000;
|
|
|
|
WriteFrom(c);
|
|
|
|
if(call)
|
|
{
|
|
uint8_t numLevels = call->NumLevels()&0xff;
|
|
WriteFrom(numLevels);
|
|
|
|
if(call->NumLevels())
|
|
{
|
|
WriteBytes((byte *)call->GetAddrs(), sizeof(uint64_t)*numLevels);
|
|
}
|
|
|
|
SAFE_DELETE(call);
|
|
}
|
|
|
|
// will be fixed up in PopContext
|
|
if(smallChunk)
|
|
{
|
|
uint16_t chunkSize = 0xbeeb;
|
|
m_ChunkFixups.push_back(0x8000000000000000ULL | GetOffset());
|
|
WriteFrom(chunkSize);
|
|
}
|
|
else
|
|
{
|
|
uint32_t chunkSize = 0xbeebfeed;
|
|
m_ChunkFixups.push_back(GetOffset() & ~0x8000000000000000ULL);
|
|
WriteFrom(chunkSize);
|
|
}
|
|
}
|
|
|
|
if(m_DebugTextWriting)
|
|
{
|
|
DebugPrint("%hs (%d)\n", name, chunkIdx);
|
|
DebugPrint("{\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reset debug text
|
|
m_DebugText = "";
|
|
|
|
if(chunkIdx > 0)
|
|
{
|
|
uint16_t c = 0;
|
|
ReadInto(c);
|
|
|
|
// chunk index 0 is not allowed in normal situations.
|
|
// allows us to indicate some control bytes
|
|
while(c == 0)
|
|
{
|
|
uint8_t *controlByte = (uint8_t *)ReadBytes(1);
|
|
|
|
if(*controlByte == 0x0)
|
|
{
|
|
// padding
|
|
uint8_t *padLength = (uint8_t *)ReadBytes(1);
|
|
|
|
// might have padded with these 5 control bytes,
|
|
// so a pad length of 0 IS VALID.
|
|
if(*padLength > 0)
|
|
{
|
|
ReadBytes((size_t)*padLength);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unexpected control byte: %x", (uint32_t)*controlByte);
|
|
}
|
|
|
|
ReadInto(c);
|
|
}
|
|
|
|
chunkIdx = c&0x3fff;
|
|
bool callstack = (c&0x8000) > 0;
|
|
bool smallchunk = (c&0x4000) > 0;
|
|
|
|
/////////////////
|
|
|
|
if(m_Indent == 0)
|
|
{
|
|
if(callstack)
|
|
{
|
|
uint8_t callLen = 0;
|
|
ReadInto(callLen);
|
|
|
|
uint64_t *calls = (uint64_t *)ReadBytes(callLen*sizeof(uint64_t));
|
|
SetCallstack(calls, callLen);
|
|
}
|
|
else
|
|
{
|
|
SetCallstack(NULL, 0);
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
|
|
if(smallchunk)
|
|
{
|
|
uint16_t miniSize = 0xbeeb;
|
|
ReadInto(miniSize);
|
|
|
|
m_LastChunkLen = miniSize;
|
|
}
|
|
else
|
|
{
|
|
uint32_t chunkSize = 0xbeebfeed;
|
|
ReadInto(chunkSize);
|
|
|
|
m_LastChunkLen = chunkSize;
|
|
}
|
|
}
|
|
|
|
if(!name && m_ChunkLookup)
|
|
name = m_ChunkLookup(chunkIdx);
|
|
|
|
if(m_DebugTextWriting)
|
|
{
|
|
DebugPrint("%hs\n", name ? name : "Unknown");
|
|
DebugPrint("{\n");
|
|
}
|
|
}
|
|
|
|
m_Indent++;
|
|
|
|
return chunkIdx;
|
|
}
|
|
|
|
void Serialiser::PopContext(const char *name, uint32_t chunkIdx)
|
|
{
|
|
m_Indent = RDCMAX(m_Indent-1, 0);
|
|
|
|
if(m_Mode >= WRITING)
|
|
{
|
|
if(chunkIdx > 0 && m_Mode == WRITING)
|
|
{
|
|
// fix up the latest PushContext (guaranteed to match this one as Pushes and Pops match)
|
|
RDCASSERT(!m_ChunkFixups.empty());
|
|
|
|
uint64_t chunkOffset = m_ChunkFixups.back();m_ChunkFixups.pop_back();
|
|
|
|
bool smallchunk = (chunkOffset & 0x8000000000000000ULL) > 0;
|
|
chunkOffset &= ~0x8000000000000000ULL;
|
|
|
|
uint64_t curOffset = GetOffset();
|
|
|
|
RDCASSERT(curOffset > chunkOffset);
|
|
|
|
uint64_t chunkLength = (curOffset-chunkOffset) - (smallchunk ? sizeof(uint16_t) : sizeof(uint32_t));
|
|
|
|
RDCASSERT(chunkLength < 0xffffffff);
|
|
|
|
uint32_t chunklen = (uint32_t)chunkLength;
|
|
|
|
byte *head = m_BufferHead;
|
|
SetOffset(chunkOffset);
|
|
if(smallchunk)
|
|
{
|
|
uint16_t miniSize = (chunklen&0xffff);
|
|
RDCASSERT(chunklen <= 0xffff);
|
|
WriteFrom(miniSize);
|
|
}
|
|
else
|
|
{
|
|
WriteFrom(chunklen);
|
|
}
|
|
m_BufferHead = head;
|
|
}
|
|
|
|
if(m_DebugTextWriting)
|
|
DebugPrint("} // %hs\n", name);
|
|
}
|
|
else
|
|
{
|
|
if(m_DebugTextWriting)
|
|
DebugPrint("}\n");
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// Serialise functions
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// generic
|
|
|
|
void Serialiser::SerialiseString(const char *name, string &el)
|
|
{
|
|
uint32_t len = (uint32_t)el.length();
|
|
|
|
Serialise(NULL, len);
|
|
|
|
if(m_Mode == READING)
|
|
el.resize(len);
|
|
|
|
if(m_Mode >= WRITING)
|
|
{
|
|
WriteBytes((byte *)el.c_str(), len);
|
|
|
|
if(m_DebugTextWriting)
|
|
{
|
|
string s = el;
|
|
if(s.length() > 64)
|
|
s = s.substr(0, 60) + "...";
|
|
DebugPrint("%hs: \"%hs\"\n", name, s.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memcpy(&el[0], ReadBytes(len), len);
|
|
|
|
if(m_DebugTextWriting)
|
|
{
|
|
string s = el;
|
|
if(s.length() > 64)
|
|
s = s.substr(0, 60) + "...";
|
|
DebugPrint("%hs: \"%hs\"\n", name, s.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Serialiser::SerialiseString(const char *name, wstring &el)
|
|
{
|
|
string utf8str;
|
|
if(m_Mode >= WRITING)
|
|
utf8str = StringFormat::Wide2UTF8(el);
|
|
|
|
SerialiseString(name, utf8str);
|
|
|
|
if(m_Mode == READING)
|
|
el = StringFormat::UTF82Wide(utf8str);
|
|
}
|
|
|
|
void Serialiser::Insert(Chunk *chunk)
|
|
{
|
|
m_Chunks.push_back(chunk);
|
|
|
|
m_DebugText += chunk->GetDebugString();
|
|
}
|
|
|
|
void Serialiser::SkipBuffer()
|
|
{
|
|
RDCASSERT(m_Mode < WRITING);
|
|
|
|
uint32_t len;
|
|
ReadInto(len);
|
|
|
|
// ensure byte alignment
|
|
uint64_t offs = GetOffset();
|
|
uint64_t alignedoffs = AlignUp16(offs);
|
|
|
|
if(offs != alignedoffs)
|
|
{
|
|
ReadBytes((size_t)(alignedoffs-offs));
|
|
}
|
|
|
|
ReadBytes(len);
|
|
}
|
|
|
|
void Serialiser::SerialiseBuffer(const char *name, byte *&buf, size_t &len)
|
|
{
|
|
uint32_t bufLen = (uint32_t)len;
|
|
|
|
if(m_Mode >= WRITING)
|
|
{
|
|
WriteFrom(bufLen);
|
|
|
|
// ensure byte alignment
|
|
uint64_t offs = GetOffset();
|
|
uint64_t alignedoffs = AlignUp16(offs);
|
|
|
|
if(offs != alignedoffs)
|
|
{
|
|
static const byte padding[BufferAlignment] = {0};
|
|
WriteBytes(&padding[0], (size_t)(alignedoffs-offs));
|
|
}
|
|
|
|
RDCASSERT((GetOffset()%BufferAlignment)==0);
|
|
|
|
WriteBytes(buf, bufLen);
|
|
|
|
m_AlignedData = true;
|
|
}
|
|
else
|
|
{
|
|
ReadInto(bufLen);
|
|
|
|
// ensure byte alignment
|
|
uint64_t offs = GetOffset();
|
|
uint64_t alignedoffs = AlignUp16(offs);
|
|
|
|
if(offs != alignedoffs)
|
|
{
|
|
ReadBytes((size_t)(alignedoffs-offs));
|
|
}
|
|
|
|
if(buf == NULL)
|
|
buf = new byte[bufLen];
|
|
memcpy(buf, ReadBytes(bufLen), bufLen);
|
|
}
|
|
|
|
len = (size_t)bufLen;
|
|
|
|
if(m_DebugTextWriting)
|
|
{
|
|
const char *ellipsis = "...";
|
|
|
|
float *fbuf = new float[4];
|
|
fbuf[0] = fbuf[1] = fbuf[2] = fbuf[3] = 0.0f;
|
|
uint32_t *lbuf = (uint32_t *)fbuf;
|
|
|
|
memcpy(fbuf, buf, RDCMIN(len, 4*sizeof(float)));
|
|
|
|
if(bufLen <= 16)
|
|
{
|
|
ellipsis = " ";
|
|
}
|
|
|
|
DebugPrint("%hs: RawBuffer % 5d:< 0x%08x 0x%08x 0x%08x 0x%08x %hs % 8.4ff % 8.4ff % 8.4ff % 8.4ff %hs >\n"
|
|
, name
|
|
, bufLen, lbuf[0], lbuf[1], lbuf[2], lbuf[3], ellipsis
|
|
, fbuf[0], fbuf[1], fbuf[2], fbuf[3], ellipsis);
|
|
|
|
SAFE_DELETE_ARRAY(fbuf);
|
|
}
|
|
|
|
}
|
|
|
|
template<> void Serialiser::Serialise(const char *name, string &el)
|
|
{
|
|
SerialiseString(name, el);
|
|
}
|
|
|
|
template<> void Serialiser::Serialise(const char *name, wstring &el)
|
|
{
|
|
SerialiseString(name, el);
|
|
}
|
|
|
|
// floats need aligned reads
|
|
template<> void Serialiser::ReadInto(float &f)
|
|
{
|
|
if(m_HasError)
|
|
{
|
|
RDCERR("Reading into with error state serialiser");
|
|
return;
|
|
}
|
|
|
|
char *data = (char *)ReadBytes(sizeof(float));
|
|
|
|
memcpy(&f, data, sizeof(float));
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// String conversions for debug log.
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// Basic types
|
|
|
|
template<>
|
|
string ToStrHelper<false, void *>::Get(void* const &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "0x%X", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, uint64_t>::Get(const uint64_t &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
|
|
StringFormat::snprintf(tostrBuf, 255, "%llu", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, uint32_t>::Get(const uint32_t &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%u", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, char>::Get(const char &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "'%c'", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, wchar_t>::Get(const wchar_t &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "'%lc'", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, byte>::Get(const byte &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%d%d%d%d%d%d%d%d"
|
|
, (el&0x80)?1:0, (el&0x40)?1:0, (el&0x20)?1:0, (el&0x10)?1:0
|
|
, (el&0x8)?1:0, (el&0x4)?1:0, (el&0x2)?1:0, (el&0x1)?1:0);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, uint16_t>::Get(const uint16_t &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%04d", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, int32_t>::Get(const int &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%d", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, int16_t>::Get(const short &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%04d", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, float>::Get(const float &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%0.4f", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, double>::Get(const double &el)
|
|
{
|
|
char tostrBuf[256] = {0};
|
|
StringFormat::snprintf(tostrBuf, 255, "%0.4lf", el);
|
|
|
|
return tostrBuf;
|
|
}
|
|
|
|
template<>
|
|
string ToStrHelper<false, bool>::Get(const bool &el)
|
|
{
|
|
if(el)
|
|
return "True";
|
|
|
|
return "False";
|
|
}
|