Files
renderdoc/renderdoc/core/core.cpp
T
Jake Turner e68e9ed8dd Don't return true from GetTrackedFileData() if no data has been loaded
The file might be being tracked but wasn't loaded from the embedded files in the capture i.e. it hasn't been physically embedded yet.
2026-02-06 09:56:33 +13:00

2913 lines
79 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2026 Baldur Karlsson
* 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 "core/core.h"
#include <time.h>
#include <algorithm>
#include "api/replay/version.h"
#include "common/common.h"
#include "common/threading.h"
#include "core/settings.h"
#include "hooks/hooks.h"
#include "jpeg-compressor/jpge.h"
#include "maths/formatpacking.h"
#include "replay/replay_driver.h"
#include "serialise/rdcfile.h"
#include "serialise/serialiser.h"
#include "stb/stb_image_write.h"
#include "strings/string_utils.h"
#include "superluminal/superluminal.h"
#include "crash_handler.h"
#include "api/replay/renderdoc_tostr.inl"
#include "api/replay/pipestate.inl"
#include "replay/renderdoc_serialise.inl"
extern "C" const rdcstr VulkanLayerJSONBasename = STRINGIZE(RDOC_BASE_NAME);
RDOC_DEBUG_CONFIG(bool, Capture_Debug_SnapshotDiagnosticLog, false,
"Snapshot the diagnostic log at capture time and embed in the capture.");
RDOC_CONFIG(bool, Capture_IncludeExtendedThumbnail, false,
"Save the thumbnail unresized and losslessly encoded during capture.");
RDOC_CONFIG(bool, Replay_Debug_PrintChunkTimings, false, "Print stats of chunk processing times");
RDOC_CONFIG(bool, Replay_Debug_SingleThreadedCompilation, false,
"Compile all shaders and PSOs single-threaded.");
// this is declared centrally so it can be shared with any backend - the name is a misnomer but kept
// for backwards compatibility reasons.
RDOC_CONFIG(rdcarray<rdcstr>, DXBC_Debug_SearchDirPaths, {},
"Paths to search for separated shader debug PDBs, including all types of paths.");
RDOC_CONFIG(rdcarray<rdcstr>, Replay_Shader_LimitedSearchDirPaths, {},
"Companion array to DXBC.Debug.SearchDirPaths - listing paths which should not be "
"searched exhaustively but only used for simple lookups.");
void WriteAnnotation(SDObject *obj, RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth,
RENDERDOC_AnnotationValue value)
{
if(valueType == eRENDERDOC_Empty)
{
RDCERR("Invalid type of annotation to write");
return;
}
const rdcinflexiblestr types[eRENDERDOC_APIObject + 1][4] = {
// eRENDERDOC_Empty,
{""_lit, ""_lit, ""_lit, ""_lit},
// eRENDERDOC_Bool,
{"bool"_lit, "bool2"_lit, "bool3"_lit, "bool4"_lit},
// eRENDERDOC_Int32,
{"int"_lit, "int2"_lit, "int3"_lit, "int4"_lit},
// eRENDERDOC_UInt32,
{"uint"_lit, "uint2"_lit, "uint3"_lit, "uint4"_lit},
// eRENDERDOC_Int64,
{"long"_lit, "long2"_lit, "long3"_lit, "long4"_lit},
// eRENDERDOC_UInt64,
{"ulong"_lit, "ulong2"_lit, "ulong3"_lit, "ulong4"_lit},
// eRENDERDOC_Float,
{"float"_lit, "float2"_lit, "float3"_lit, "float4"_lit},
// eRENDERDOC_Double,
{"double"_lit, "double2"_lit, "double3"_lit, "double4"_lit},
// eRENDERDOC_String,
{"string"_lit, ""_lit, ""_lit, ""_lit},
// eRENDERDOC_APIObject,
{"ResourceId"_lit, ""_lit, ""_lit, ""_lit},
};
const uint32_t byteSize[eRENDERDOC_APIObject + 1] = {
// eRENDERDOC_Empty,
0,
// eRENDERDOC_Bool,
1,
// eRENDERDOC_Int32,
4,
// eRENDERDOC_UInt32,
4,
// eRENDERDOC_Int64,
8,
// eRENDERDOC_UInt64,
8,
// eRENDERDOC_Float,
4,
// eRENDERDOC_Double,
8,
// eRENDERDOC_String,
0,
// eRENDERDOC_APIObject,
8,
};
const SDBasic basetype[eRENDERDOC_APIObject + 1] = {
// eRENDERDOC_Empty,
SDBasic::Null,
// eRENDERDOC_Bool,
SDBasic::Boolean,
// eRENDERDOC_Int32,
SDBasic::SignedInteger,
// eRENDERDOC_UInt32,
SDBasic::UnsignedInteger,
// eRENDERDOC_Int64,
SDBasic::SignedInteger,
// eRENDERDOC_UInt64,
SDBasic::UnsignedInteger,
// eRENDERDOC_Float,
SDBasic::Float,
// eRENDERDOC_Double,
SDBasic::Float,
// eRENDERDOC_String,
SDBasic::String,
// eRENDERDOC_APIObject,
SDBasic::Resource,
};
if(valueVectorWidth > 1)
{
if(valueType == eRENDERDOC_APIObject)
{
RDCERR("Invalid vector width for API object");
return;
}
else if(valueType == eRENDERDOC_String)
{
RDCERR("Invalid vector width for string");
return;
}
const rdcinflexiblestr comps[] = {"x"_lit, "y"_lit, "z"_lit, "w"_lit};
obj->type.basetype = SDBasic::Struct;
obj->type.name = types[valueType][valueVectorWidth - 1];
RENDERDOC_AnnotationValue tmp = {};
for(uint32_t i = 0; i < valueVectorWidth; i++)
{
SDObject *child = obj->CreateChildByKeyPath(comps[i]);
if(byteSize[valueType] == 1)
memcpy(&tmp.boolean, &value.vector.boolean[i], sizeof(tmp.boolean));
if(byteSize[valueType] == 4)
memcpy(&tmp.uint32, &value.vector.uint32[i], sizeof(tmp.uint32));
if(byteSize[valueType] == 8)
memcpy(&tmp.uint64, &value.vector.uint64[i], sizeof(tmp.uint64));
WriteAnnotation(child, valueType, 0, tmp);
if(i == 0)
obj->type.byteSize = child->type.byteSize * valueVectorWidth;
}
return;
}
obj->type.name = types[valueType][0];
obj->type.basetype = basetype[valueType];
obj->type.byteSize = byteSize[valueType];
switch(valueType)
{
case eRENDERDOC_Empty:
case eRENDERDOC_AnnotationMax: RDCERR("Invalid annotation type"); return;
case eRENDERDOC_Bool:
{
obj->data.basic.b = value.boolean;
break;
}
case eRENDERDOC_Int32:
{
obj->data.basic.i = value.int32;
break;
}
case eRENDERDOC_UInt32:
{
obj->data.basic.u = value.uint32;
break;
}
case eRENDERDOC_Int64:
{
obj->data.basic.u = value.int64;
break;
}
case eRENDERDOC_UInt64:
{
obj->data.basic.u = value.uint64;
break;
}
case eRENDERDOC_Float:
{
obj->data.basic.d = value.float32;
break;
}
case eRENDERDOC_Double:
{
obj->data.basic.d = value.float64;
break;
}
case eRENDERDOC_String:
{
obj->type.byteSize = strlen(value.string);
obj->data.str = value.string;
break;
}
case eRENDERDOC_APIObject:
{
memcpy(&obj->data.basic.id, &value.uint64, sizeof(ResourceId));
break;
}
}
}
void LogReplayOptions(const ReplayOptions &opts)
{
RDCLOG("%s API validation during replay", (opts.apiValidation ? "Enabling" : "Not enabling"));
if(opts.forceGPUVendor == GPUVendor::Unknown && opts.forceGPUDeviceID == 0 &&
opts.forceGPUDriverName.empty())
{
RDCLOG("Using default GPU replay selection algorithm");
}
else
{
RDCLOG("Overriding GPU replay selection:");
RDCLOG(" Vendor %s, device %u, driver \"%s\"", ToStr(opts.forceGPUVendor).c_str(),
opts.forceGPUDeviceID, opts.forceGPUDriverName.c_str());
}
RDCLOG("Replay optimisation level: %s", ToStr(opts.optimisation).c_str());
}
// these one is done by hand as we format it
template <>
rdcstr DoStringise(const ResourceId &el)
{
RDCCOMPILE_ASSERT(sizeof(el) == sizeof(uint64_t), "ResourceId is no longer 1:1 with uint64_t");
// below is equivalent to:
// return StringFormat::Fmt("ResourceId::%llu", el);
uint64_t num = 0;
memcpy(&num, &el, sizeof(uint64_t));
#define PREFIX "ResourceId::"
// hardcode empty/null ResourceId to both avoid special case below and fast-path a common case as
// a string literal.
if(num == 0)
return PREFIX "0"_lit;
// enough for prefix and a 64-bit value in decimal
char str[48] = {};
RDCCOMPILE_ASSERT(ARRAY_COUNT(str) > sizeof(PREFIX) + 20, "Scratch buffer is not large enough");
// ARRAY_COUNT(str) - 1 would point us at the last element, we go one further back to leave a
// trailing NUL character
char *c = str + ARRAY_COUNT(str) - 2;
// build up digits in reverse order from the end of the buffer
while(num)
{
*(c--) = char((num % 10) + '0');
num /= 10;
}
// the length is sizeof(PREFIX) - 1, the index of the last actual character is - 2. Saves us a -1
// in the loop below.
const size_t prefixlast = sizeof(PREFIX) - 2;
// add the prefix (in reverse order)
for(size_t i = 0; i <= prefixlast; i++)
*(c--) = PREFIX[prefixlast - i];
#undef PREFIX
// the loop will have stepped us to the first NULL before our string, so return c+1
return c + 1;
}
template <>
rdcstr DoStringise(const PointerVal &el)
{
if(el.shader != ResourceId())
{
// we don't want to format as an ID, we need to encode the raw value
uint64_t num;
memcpy(&num, &el.shader, sizeof(num));
return StringFormat::Fmt("GPUAddress::%llu::%llu::%u", el.pointer, num, el.pointerTypeID);
}
else
{
return StringFormat::Fmt("GPUAddress::%llu", el.pointer);
}
}
template <class SerialiserType>
void DoSerialise(SerialiserType &ser, ResourceId &el)
{
ser.SerialiseValue(SDBasic::Resource, 8, (uint64_t &)el);
}
INSTANTIATE_SERIALISE_TYPE(ResourceId);
template <class SerialiserType>
void DoSerialise(SerialiserType &ser, RDResult &el)
{
SERIALISE_MEMBER(code);
SERIALISE_MEMBER(message);
SIZE_CHECK(16);
}
INSTANTIATE_SERIALISE_TYPE(RDResult);
#if ENABLED(RDOC_LINUX) && ENABLED(RDOC_XLIB)
#include <X11/Xlib.h>
#endif
// from image_viewer.cpp
RDResult IMG_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver);
template <>
rdcstr DoStringise(const CaptureState &el)
{
BEGIN_ENUM_STRINGISE(CaptureState);
{
STRINGISE_ENUM_CLASS(LoadingReplaying);
STRINGISE_ENUM_CLASS(ActiveReplaying);
STRINGISE_ENUM_CLASS(StructuredExport);
STRINGISE_ENUM_CLASS(BackgroundCapturing);
STRINGISE_ENUM_CLASS(ActiveCapturing);
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const RDCDriver &el)
{
BEGIN_ENUM_STRINGISE(RDCDriver);
{
STRINGISE_ENUM_CLASS(Unknown);
STRINGISE_ENUM_CLASS(OpenGL);
STRINGISE_ENUM_CLASS(OpenGLES);
STRINGISE_ENUM_CLASS(Mantle);
STRINGISE_ENUM_CLASS(D3D12);
STRINGISE_ENUM_CLASS(D3D11);
STRINGISE_ENUM_CLASS(D3D10);
STRINGISE_ENUM_CLASS(D3D9);
STRINGISE_ENUM_CLASS(D3D8);
STRINGISE_ENUM_CLASS(Image);
STRINGISE_ENUM_CLASS(Vulkan);
STRINGISE_ENUM_CLASS(Metal);
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const ReplayLogType &el)
{
BEGIN_ENUM_STRINGISE(ReplayLogType);
{
STRINGISE_ENUM_NAMED(eReplay_Full, "Full replay including action");
STRINGISE_ENUM_NAMED(eReplay_WithoutDraw, "Replay without action");
STRINGISE_ENUM_NAMED(eReplay_OnlyDraw, "Replay only action");
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const VendorExtensions &el)
{
BEGIN_ENUM_STRINGISE(VendorExtensions);
{
STRINGISE_ENUM_CLASS(NvAPI);
STRINGISE_ENUM_CLASS_NAMED(OpenGL_Ext, "Unsupported GL extensions");
STRINGISE_ENUM_CLASS_NAMED(Vulkan_Ext, "Unsupported Vulkan extensions");
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const RENDERDOC_InputButton &el)
{
char alphanumericbuf[2] = {'A', 0};
// enums map straight to ascii
if((el >= eRENDERDOC_Key_A && el <= eRENDERDOC_Key_Z) ||
(el >= eRENDERDOC_Key_0 && el <= eRENDERDOC_Key_9))
{
alphanumericbuf[0] = (char)el;
return alphanumericbuf;
}
BEGIN_ENUM_STRINGISE(RENDERDOC_InputButton);
{
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Divide, "/");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Multiply, "*");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Subtract, "-");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Plus, "+");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F1, "F1");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F2, "F2");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F3, "F3");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F4, "F4");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F5, "F5");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F6, "F6");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F7, "F7");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F8, "F8");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F9, "F9");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F10, "F10");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F11, "F11");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_F12, "F12");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Home, "Home");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_End, "End");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Insert, "Insert");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Delete, "Delete");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_PageUp, "PageUp");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_PageDn, "PageDn");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Backspace, "Backspace");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Tab, "Tab");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_PrtScrn, "PrtScrn");
STRINGISE_ENUM_NAMED(eRENDERDOC_Key_Pause, "Pause");
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const SystemChunk &el)
{
BEGIN_ENUM_STRINGISE(SystemChunk);
{
STRINGISE_ENUM_CLASS_NAMED(DriverInit, "Internal::Driver Initialisation Parameters");
STRINGISE_ENUM_CLASS_NAMED(InitialContentsList, "Internal::List of Initial Contents Resources");
STRINGISE_ENUM_CLASS_NAMED(InitialContents, "Internal::Initial Contents");
STRINGISE_ENUM_CLASS_NAMED(CaptureBegin, "Internal::Beginning of Capture");
STRINGISE_ENUM_CLASS_NAMED(CaptureScope, "Internal::Frame Metadata");
STRINGISE_ENUM_CLASS_NAMED(CaptureEnd, "Internal::End of Capture");
}
END_ENUM_STRINGISE();
}
template <>
rdcstr DoStringise(const RENDERDOC_AnnotationType &el)
{
BEGIN_ENUM_STRINGISE(RENDERDOC_AnnotationType);
{
STRINGISE_ENUM_NAMED(eRENDERDOC_Bool, "bool");
STRINGISE_ENUM_NAMED(eRENDERDOC_Int32, "int32");
STRINGISE_ENUM_NAMED(eRENDERDOC_UInt32, "uint32");
STRINGISE_ENUM_NAMED(eRENDERDOC_Int64, "int64");
STRINGISE_ENUM_NAMED(eRENDERDOC_UInt64, "uint64");
STRINGISE_ENUM_NAMED(eRENDERDOC_Float, "float");
STRINGISE_ENUM_NAMED(eRENDERDOC_Double, "double");
STRINGISE_ENUM_NAMED(eRENDERDOC_String, "string");
STRINGISE_ENUM_NAMED(eRENDERDOC_APIObject, "object");
}
END_ENUM_STRINGISE();
}
template <class SerialiserType>
void DoSerialise(SerialiserType &ser, RENDERDOC_AnnotationValue &el)
{
if(ser.GetStructArg() == eRENDERDOC_String)
{
SERIALISE_MEMBER(string).Hidden();
}
else
{
SERIALISE_MEMBER(vector.uint64).Hidden();
}
}
INSTANTIATE_SERIALISE_TYPE(RENDERDOC_AnnotationValue);
RenderDoc &RenderDoc::Inst()
{
static RenderDoc realInst;
return realInst;
}
void RenderDoc::RecreateCrashHandler()
{
SCOPED_WRITELOCK(m_ExHandlerLock);
#if ENABLED(RDOC_CRASH_HANDLER)
rdcstr exename;
FileIO::GetExecutableFilename(exename);
exename = strlower(exename);
// only create crash handler when we're not in renderdoccmd (to prevent infinite loop as
// the crash handler itself launches renderdoccmd)
if(exename.contains("renderdoccmd"))
return;
#if ENABLED(RDOC_WIN32)
// there are way too many invalid reports coming from chrome, completely disable the crash handler
// in that case.
if(exename.find("chrome.exe") &&
(GetModuleHandleA("chrome_elf.dll") || GetModuleHandleA("chrome_child.dll")))
{
RDCWARN("Disabling crash handling server due to detected chrome.");
return;
}
// some people use vivaldi which is just chrome
if(exename.find("vivaldi.exe") &&
(GetModuleHandleA("vivaldi_elf.dll") || GetModuleHandleA("vivaldi_child.dll")))
{
RDCWARN("Disabling crash handling server due to detected chrome.");
return;
}
// ditto opera
if(exename.find("opera.exe") && GetModuleHandleA("opera_browser.dll"))
{
RDCWARN("Disabling crash handling server due to detected chrome.");
return;
}
// ditto edge
if(exename.find("msedge.exe") && GetModuleHandleA("msedge.dll"))
{
RDCWARN("Disabling crash handling server due to detected chrome.");
return;
}
#endif
m_ExHandler = new CrashHandler(m_ExHandler);
m_ExHandler->RegisterMemoryRegion(this, sizeof(RenderDoc));
#endif
}
void RenderDoc::UnloadCrashHandler()
{
SCOPED_WRITELOCK(m_ExHandlerLock);
if(!m_ExHandler)
return;
m_ExHandler->UnregisterMemoryRegion(this);
SAFE_DELETE(m_ExHandler);
}
void RenderDoc::RegisterMemoryRegion(void *mem, size_t size)
{
SCOPED_READLOCK(m_ExHandlerLock);
if(m_ExHandler)
m_ExHandler->RegisterMemoryRegion(mem, size);
}
void RenderDoc::UnregisterMemoryRegion(void *mem)
{
SCOPED_READLOCK(m_ExHandlerLock);
if(m_ExHandler)
m_ExHandler->UnregisterMemoryRegion(mem);
}
RenderDoc::RenderDoc()
{
m_CaptureFileTemplate = "";
m_MarkerIndentLevel = 0;
m_CapturesActive = 0;
m_RemoteIdent = 0;
m_RemoteThread = 0;
m_Replay = false;
m_Cap = 0;
m_FocusKeys.clear();
m_FocusKeys.push_back(eRENDERDOC_Key_F11);
m_CaptureKeys.clear();
m_CaptureKeys.push_back(eRENDERDOC_Key_F12);
m_CaptureKeys.push_back(eRENDERDOC_Key_PrtScrn);
m_ExHandler = NULL;
m_Overlay = eRENDERDOC_Overlay_Default;
m_VulkanCheck = NULL;
m_VulkanInstall = NULL;
m_TargetControlThreadShutdown = false;
m_ControlClientThreadShutdown = false;
ClearTrackedFiles();
}
void RenderDoc::Initialise()
{
Callstack::Init();
Network::Init();
Threading::Init();
#if !RENDERDOC_STABLE_BUILD
Superluminal::Init();
#endif
m_RemoteIdent = 0;
m_RemoteThread = 0;
m_TimeBase = 0;
m_TimeFrequency = 1.0;
if(!IsReplayApp())
{
m_TimeBase = Timing::GetTick();
m_TimeFrequency = Timing::GetTickFrequency() / 1000.0;
Process::ApplyEnvironmentModification();
uint32_t port = RenderDoc_FirstTargetControlPort;
Network::Socket *sock = Network::CreateServerSocket("0.0.0.0", port & 0xffff, 4);
while(sock == NULL)
{
port++;
if(port > RenderDoc_LastTargetControlPort)
{
m_RemoteIdent = 0;
break;
}
sock = Network::CreateServerSocket("0.0.0.0", port & 0xffff, 4);
}
if(sock)
{
m_RemoteIdent = port;
m_TargetControlThreadShutdown = false;
m_RemoteThread = Threading::CreateThread([sock]() { TargetControlServerThread(sock); });
RDCLOG("Listening for target control on %u", port);
}
else
{
RDCWARN("Couldn't open socket for target control");
}
}
// set default capture log - useful for when hooks aren't setup
// through the UI (and a log file isn't set manually)
{
rdcstr capture_filename;
const rdcstr base = IsReplayApp() ? "RenderDoc" : "RenderDoc_app";
FileIO::GetDefaultFiles(base, capture_filename, m_LoggingFilename, m_Target);
if(m_CaptureFileTemplate.empty())
SetCaptureFileTemplate(capture_filename);
RDCLOGFILE(m_LoggingFilename.c_str());
}
const char *platform =
#if ENABLED(RDOC_WIN32)
"Windows";
#elif ENABLED(RDOC_LINUX)
"Linux";
#elif ENABLED(RDOC_ANDROID)
"Android";
#elif ENABLED(RDOC_APPLE)
"macOS";
#else
"Unknown";
#endif
RDCLOG("RenderDoc v%s %s %s %s (%s) %s", MAJOR_MINOR_VERSION_STRING, platform,
sizeof(uintptr_t) == sizeof(uint64_t) ? "64-bit" : "32-bit",
ENABLED(RDOC_RELEASE) ? "Release" : "Development", GitVersionHash,
IsReplayApp() ? "loaded in replay application" : "capturing application");
#if defined(DISTRIBUTION_VERSION)
RDCLOG("Packaged for %s (%s) - %s", DISTRIBUTION_NAME, DISTRIBUTION_VERSION, DISTRIBUTION_CONTACT);
#endif
#if defined(RENDERDOC_HOOK_DLSYM)
RDCWARN("dlsym() hooking enabled!");
#endif
if(!IsReplayApp())
{
if(m_RemoteIdent == 0)
RDCWARN("Couldn't open socket for target control");
else
RDCDEBUG("Listening for target control on %u", m_RemoteIdent);
}
Keyboard::Init();
m_FrameTimer.InitTimers();
m_ExHandler = NULL;
ClearTrackedFiles();
RecreateCrashHandler();
// begin printing to stdout/stderr after this point, earlier logging is debugging
// cruft that we don't want cluttering output.
// However we don't want to print in captured applications, since they may be outputting important
// information to stdout/stderr and being piped around and processed!
if(IsReplayApp())
RDCLOGOUTPUT();
ProcessConfig();
}
RenderDoc::~RenderDoc()
{
if(m_ExHandler)
{
UnloadCrashHandler();
}
for(auto it = m_ShutdownFunctions.begin(); it != m_ShutdownFunctions.end(); ++it)
(*it)();
m_ShutdownFunctions.clear();
for(size_t i = 0; i < m_Captures.size(); i++)
{
if(m_Captures[i].retrieved)
{
RDCLOG("Removing remotely retrieved capture %s", m_Captures[i].path.c_str());
FileIO::Delete(m_Captures[i].path);
}
else
{
RDCLOG("'Leaking' unretrieved capture %s", m_Captures[i].path.c_str());
}
}
RDCSTOPLOGGING();
if(m_RemoteThread)
{
m_TargetControlThreadShutdown = true;
// On windows we can't join to this thread as it could lead to deadlocks, since we're
// performing this destructor in the middle of module unloading. However we want to
// ensure that the thread gets properly tidied up and closes its socket, so wait a little
// while to give it time to notice the shutdown signal and close itself.
Threading::Sleep(50);
Threading::CloseThread(m_RemoteThread);
m_RemoteThread = 0;
}
delete m_Config;
Process::Shutdown();
Network::Shutdown();
Threading::Shutdown();
StringFormat::Shutdown();
}
void RenderDoc::RemoveHooks()
{
if(m_ExHandler)
{
UnloadCrashHandler();
}
if(m_RemoteThread)
{
// explicitly wait for thread to shutdown, this call is not from module unloading and
// we want to be sure everything is gone before we remove our module & hooks
m_TargetControlThreadShutdown = true;
Threading::JoinThread(m_RemoteThread);
Threading::CloseThread(m_RemoteThread);
m_RemoteThread = 0;
}
}
void RenderDoc::InitialiseReplay(GlobalEnvironment env, const rdcarray<rdcstr> &args)
{
if(!IsReplayApp())
{
RDCERR(
"Initialising replay within non-replaying app. Did you properly export replay marker in "
"host executable or library, or are you trying to replay directly with a self-hosted "
"capture build?");
}
m_GlobalEnv = env;
#if ENABLED(RDOC_LINUX) && ENABLED(RDOC_XLIB)
if(!m_GlobalEnv.xlibDisplay)
m_GlobalEnv.xlibDisplay = XOpenDisplay(NULL);
#endif
rdcstr exename;
FileIO::GetExecutableFilename(exename);
RDCLOG("Replay application '%s' launched", exename.c_str());
if(!args.empty())
{
for(size_t i = 0; i < args.size(); i++)
RDCLOG("Parameter [%u]: %s", (uint32_t)i, args[i].c_str());
}
if(args.contains("--crash"))
UnloadCrashHandler();
else
RecreateCrashHandler();
if(env.enumerateGPUs)
{
m_AvailableGPUThread = Threading::CreateThread([this]() {
rdcarray<rdcstr> driverFilePaths;
for(GraphicsAPI api : {GraphicsAPI::D3D11, GraphicsAPI::D3D12, GraphicsAPI::Vulkan})
{
RDCDriver driverType = RDCDriver::Unknown;
switch(api)
{
case GraphicsAPI::D3D11: driverType = RDCDriver::D3D11; break;
case GraphicsAPI::D3D12: driverType = RDCDriver::D3D12; break;
case GraphicsAPI::OpenGL: break;
case GraphicsAPI::Vulkan: driverType = RDCDriver::Vulkan; break;
}
if(driverType == RDCDriver::Unknown || !HasReplayDriver(driverType))
continue;
IReplayDriver *driver = NULL;
RDResult result = m_ReplayDriverProviders[driverType](NULL, ReplayOptions(), &driver);
if(result == ResultCode::Succeeded)
{
rdcarray<GPUDevice> gpus = driver->GetAvailableGPUs();
#if ENABLED(RDOC_WIN32)
for(const char *driverDLL : {
"amdvlk32.dll",
"amdvlk64.dll",
"atiadlxx.dll",
"atig6pxx.dll",
"atig6txx.dll",
"atigktxx.dll",
"atiglpxx.dll",
"atimuixx.dll",
"atio6axx.dll",
"atioglxx.dll",
"ControlLib.dll",
"ControlLib32.dll",
"igd10iumd32.dll",
"igd10iumd64.dll",
"igd12umd32.dll",
"igd12umd64.dll",
"igc32.dll",
"igc64.dll",
"igvk32.dll",
"igvk64.dll"
"igxelpgicd32.dll",
"igxelpgicd64.dll",
"igxelpicd32.dll",
"igxelpicd32.dll",
"igxelpicd64.dll",
"igxelpicd64.dll",
"nvoglv32.dll",
"nvoglv64.dll",
"nvwgf2um.dll",
"nvwgf2umx.dll",
})
{
HMODULE mod = GetModuleHandleA(driverDLL);
if(mod)
{
wchar_t curFile[512] = {0};
GetModuleFileNameW(mod, curFile, 511);
rdcstr path = StringFormat::Wide2UTF8(curFile);
if(!driverFilePaths.contains(path))
driverFilePaths.push_back(path);
}
}
#endif
for(const GPUDevice &newgpu : gpus)
{
bool addnew = true;
for(GPUDevice &oldgpu : m_AvailableGPUs)
{
// if we have this GPU listed already, just add its API to the previous list
if(oldgpu == newgpu)
{
oldgpu.apis.push_back(api);
addnew = false;
}
}
if(addnew)
m_AvailableGPUs.push_back(newgpu);
}
}
else
{
RDCWARN("Couldn't create proxy replay driver for %s: %s", ToStr(driverType).c_str(),
ResultDetails(result).Message().c_str());
}
if(driver)
driver->Shutdown();
}
// we now have a list of GPUs, however we might have some duplicates if some APIs have
// multiple drivers for a single device. To compact this list, for each GPU with no driver
// we find all matching multi-drive GPUs and merge it into all matching copies.
bool hasDriverNames = false;
for(size_t i = 0; i < m_AvailableGPUs.size(); i++)
hasDriverNames |= !m_AvailableGPUs[i].driver.empty();
if(hasDriverNames)
{
for(size_t i = 0; i < m_AvailableGPUs.size();)
{
bool applied = false;
if(!m_AvailableGPUs[i].driver.empty())
{
i++;
continue;
}
// scan all subsequent GPUs, if we find a duplicate, merge the APIs
for(size_t j = i + 1; j < m_AvailableGPUs.size(); j++)
{
if(m_AvailableGPUs[i].vendor == m_AvailableGPUs[j].vendor &&
m_AvailableGPUs[i].deviceID == m_AvailableGPUs[j].deviceID)
{
RDCASSERT(!m_AvailableGPUs[j].driver.empty());
for(GraphicsAPI a : m_AvailableGPUs[i].apis)
{
if(m_AvailableGPUs[j].apis.indexOf(a) == -1)
m_AvailableGPUs[j].apis.push_back(a);
}
applied = true;
}
}
// we "applied" this GPU to all its driver-based duplicates, so we can remove it now
if(applied)
{
m_AvailableGPUs.erase(i);
}
else
{
i++;
}
}
}
// sort the APIs list in each GPU, and sort the GPUs
std::sort(m_AvailableGPUs.begin(), m_AvailableGPUs.end());
for(GPUDevice &dev : m_AvailableGPUs)
{
std::sort(dev.apis.begin(), dev.apis.end());
RDCLOG("GPU: %s - %s (driver: %s)", ToStr(dev.vendor).c_str(), dev.name.c_str(),
dev.driver.empty() ? "--" : dev.driver.c_str());
}
#if ENABLED(RDOC_WIN32)
{
// only print unique versions to avoid the case of loading multiple driver files and
// printing redundantly.
rdcarray<OSUtility::DLLFileVersion> versions;
for(rdcstr &path : driverFilePaths)
{
OSUtility::DLLFileVersion ret = OSUtility::GetDLLVersion(path);
if(!versions.contains(ret))
{
versions.push_back(ret);
RDCLOG("Driver: '%s' %u.%u.%u.%u %s", path.c_str(), ret.major, ret.minor, ret.build,
ret.revision,
StringFormat::sntimef(FileIO::GetModifiedTimestamp(path), "%Y-%m-%d").c_str());
}
}
}
#endif
});
}
}
void RenderDoc::ShutdownReplay()
{
SyncAvailableGPUThread();
// call shutdown functions early, as we only want to do these in the RenderDoc destructor if we
// have no other choice (i.e. we're capturing).
for(auto it = m_ShutdownFunctions.begin(); it != m_ShutdownFunctions.end(); ++it)
(*it)();
m_ShutdownFunctions.clear();
}
void RenderDoc::RegisterShutdownFunction(ShutdownFunction func)
{
auto it = std::lower_bound(m_ShutdownFunctions.begin(), m_ShutdownFunctions.end(), func);
if(it == m_ShutdownFunctions.end() || *it != func)
m_ShutdownFunctions.insert(it - m_ShutdownFunctions.begin(), func);
}
bool RenderDoc::MatchClosestWindow(DeviceOwnedWindow &devWnd)
{
SCOPED_LOCK(m_CapturerListLock);
// lower_bound and the DeviceWnd ordering (pointer compares, dev over wnd) means that if either
// element in devWnd is NULL we can go forward from this iterator and find the first wildcardMatch
// note that if dev is specified and wnd is NULL, this will actually point at the first
// wildcardMatch already and we can use it immediately (since which window of multiple we
// choose is undefined, so up to us). If dev is NULL there is no window ordering (since dev is
// the primary sorting value) so we just iterate through the whole map. It should be small in
// the majority of cases
auto it = m_WindowFrameCapturers.lower_bound(devWnd);
while(it != m_WindowFrameCapturers.end())
{
if(it->first.wildcardMatch(devWnd))
break;
++it;
}
if(it != m_WindowFrameCapturers.end())
{
devWnd = it->first;
return true;
}
return false;
}
bool RenderDoc::IsActiveWindow(DeviceOwnedWindow devWnd)
{
SCOPED_LOCK(m_CapturerListLock);
return devWnd == m_ActiveWindow;
}
void RenderDoc::GetActiveWindow(DeviceOwnedWindow &devWnd)
{
SCOPED_LOCK(m_CapturerListLock);
devWnd = m_ActiveWindow;
}
IFrameCapturer *RenderDoc::MatchFrameCapturer(DeviceOwnedWindow devWnd)
{
// try and find the closest frame capture registered, and update
// the values in devWnd to point to it precisely
bool exactMatch = MatchClosestWindow(devWnd);
SCOPED_LOCK(m_CapturerListLock);
if(!exactMatch)
{
// handle off-screen rendering where there are no device/window pairs in
// m_WindowFrameCapturers, instead we use the first matching device frame capturer
if(devWnd.windowHandle == NULL)
{
auto defaultit = m_DeviceFrameCapturers.find(devWnd.device);
if(defaultit == m_DeviceFrameCapturers.end() && !m_DeviceFrameCapturers.empty())
defaultit = m_DeviceFrameCapturers.begin();
if(defaultit != m_DeviceFrameCapturers.end())
return defaultit->second;
}
RDCERR(
"Couldn't find matching frame capturer for device %p window %p "
"from %zu device frame capturers and %zu frame capturers",
devWnd.device, devWnd.windowHandle, m_DeviceFrameCapturers.size(),
m_WindowFrameCapturers.size());
return NULL;
}
auto it = m_WindowFrameCapturers.find(devWnd);
if(it == m_WindowFrameCapturers.end())
{
RDCERR("Couldn't find frame capturer after exact match!");
return NULL;
}
return it->second.FrameCapturer;
}
void RenderDoc::StartFrameCapture(DeviceOwnedWindow devWnd)
{
m_CaptureTitle.clear();
IFrameCapturer *frameCap = MatchFrameCapturer(devWnd);
if(frameCap)
{
frameCap->StartFrameCapture(devWnd);
m_CapturesActive++;
}
}
void RenderDoc::SetActiveWindow(DeviceOwnedWindow devWnd)
{
SCOPED_LOCK(m_CapturerListLock);
auto it = m_WindowFrameCapturers.find(devWnd);
if(it == m_WindowFrameCapturers.end())
{
RDCERR("Couldn't find frame capturer for device %p window %p", devWnd.device,
devWnd.windowHandle);
return;
}
m_ActiveWindow = devWnd;
}
void RenderDoc::SetCaptureTitle(const rdcstr &title)
{
m_CaptureTitle = title;
}
bool RenderDoc::EndFrameCapture(DeviceOwnedWindow devWnd)
{
IFrameCapturer *frameCap = MatchFrameCapturer(devWnd);
if(frameCap)
{
bool ret = frameCap->EndFrameCapture(devWnd);
m_CapturesActive--;
return ret;
}
return false;
}
bool RenderDoc::DiscardFrameCapture(DeviceOwnedWindow devWnd)
{
IFrameCapturer *frameCap = MatchFrameCapturer(devWnd);
if(frameCap)
{
bool ret = frameCap->DiscardFrameCapture(devWnd);
m_CapturesActive--;
return ret;
}
return false;
}
bool RenderDoc::IsTargetControlConnected()
{
SCOPED_LOCK(m_SingleClientLock);
return !m_SingleClientName.empty();
}
rdcstr RenderDoc::GetTargetControlUsername()
{
SCOPED_LOCK(m_SingleClientLock);
return m_SingleClientName;
}
bool RenderDoc::ShowReplayUI()
{
SCOPED_LOCK(m_SingleClientLock);
if(m_SingleClientName.empty())
return false;
m_RequestControllerShow = true;
return true;
}
void RenderDoc::Tick()
{
bool cur_focus = false;
for(size_t i = 0; i < m_FocusKeys.size(); i++)
cur_focus |= Keyboard::GetKeyState(m_FocusKeys[i]);
bool cur_cap = false;
for(size_t i = 0; i < m_CaptureKeys.size(); i++)
cur_cap |= Keyboard::GetKeyState(m_CaptureKeys[i]);
m_FrameTimer.UpdateTimers();
if(!m_PrevFocus && cur_focus)
{
CycleActiveWindow();
}
if(!m_PrevCap && cur_cap)
{
TriggerCapture(1);
}
m_PrevFocus = cur_focus;
m_PrevCap = cur_cap;
// check for any child threads that need to be waited on, remove them from the list
rdcarray<Threading::ThreadHandle> waitThreads;
{
SCOPED_LOCK(m_ChildLock);
for(rdcpair<uint32_t, Threading::ThreadHandle> &c : m_ChildThreads)
{
if(c.first == 0)
waitThreads.push_back(c.second);
}
m_ChildThreads.removeIf(
[](const rdcpair<uint32_t, Threading::ThreadHandle> &c) { return c.first == 0; });
}
// clean up the threads now
for(Threading::ThreadHandle t : waitThreads)
{
Threading::JoinThread(t);
Threading::CloseThread(t);
}
}
void RenderDoc::CycleActiveWindow()
{
SCOPED_LOCK(m_CapturerListLock);
m_Cap = 0;
// can only shift focus if we have multiple windows
if(m_WindowFrameCapturers.size() > 1)
{
for(auto it = m_WindowFrameCapturers.begin(); it != m_WindowFrameCapturers.end(); ++it)
{
if(it->first == m_ActiveWindow)
{
auto nextit = it;
++nextit;
if(nextit != m_WindowFrameCapturers.end())
m_ActiveWindow = nextit->first;
else
m_ActiveWindow = m_WindowFrameCapturers.begin()->first;
break;
}
}
}
}
uint32_t RenderDoc::GetCapturableWindowCount()
{
SCOPED_LOCK(m_CapturerListLock);
return (uint32_t)m_WindowFrameCapturers.size();
}
rdcstr RenderDoc::GetOverlayText(RDCDriver driver, DeviceOwnedWindow devWnd, uint32_t frameNumber,
int flags)
{
bool activeWindow;
const bool capturesEnabled = (flags & eOverlay_CaptureDisabled) == 0;
uint32_t overlay = GetOverlayBits();
RDCDriver activeDriver = RDCDriver::Unknown;
RDCDriver curDriver = RDCDriver::Unknown;
int activeIdx = -1, curIdx = -1, idx = 0;
size_t numWindows;
{
SCOPED_LOCK(m_CapturerListLock);
activeWindow = (devWnd == m_ActiveWindow);
for(auto it = m_WindowFrameCapturers.begin(); it != m_WindowFrameCapturers.end(); ++it, ++idx)
{
if(it->first == m_ActiveWindow)
{
activeIdx = idx;
activeDriver = it->second.FrameCapturer->GetFrameCaptureDriver();
}
if(it->first == devWnd)
{
curIdx = idx;
curDriver = it->second.FrameCapturer->GetFrameCaptureDriver();
}
}
numWindows = m_WindowFrameCapturers.size();
}
if(activeDriver == RDCDriver::Unknown)
activeDriver = curDriver;
if(activeDriver == RDCDriver::Unknown)
activeDriver = driver;
// example layout:
//
// Capturing D3D11. Frame: 1234. 33ms (30 FPS)
// F12, PrtScrn to capture. 3 Captures saved.
// Captured frame 1200.
//
// Frame number, FPS, capture list are optional. If capture list is disabled
// the second line still displays the keys as long as capturing is allowed.
// if capturing is disabled, only the first line displays.
//
// On platforms without keyboards, the keys are replaced by a remote access connection status
// message.
//
// with multiple windows the active window will look like:
//
// Capturing D3D11. Window 1 active. Frame: 1234. 33ms (30 FPS)
// F12, PrtScrn to capture. 3 Captures saved.
// Captured frame 1200.
//
// Inactive windows will look like:
//
// Capturing D3D11. Window 1 active.
// F11 to cycle. OpenGL window 2.
rdcstr overlayText = ToStr(activeDriver) + ".";
// pad this so it's the same length regardless of API length
while(overlayText.length() < 8)
overlayText.push_back(' ');
overlayText = "Capturing " + overlayText;
if(numWindows > 1)
{
if(activeIdx >= 0)
overlayText += StringFormat::Fmt(" Window %d active.", activeIdx);
else
overlayText += " No window active.";
}
if(activeWindow)
{
if(overlay & eRENDERDOC_Overlay_FrameNumber)
overlayText += StringFormat::Fmt(" Frame: %d.", frameNumber);
if(overlay & eRENDERDOC_Overlay_FrameRate)
{
const double frameTime = m_FrameTimer.GetAvgFrameTime();
// max with 0.01ms so that we don't divide by zero
const double fps = 1000.0f / RDCMAX(0.01, frameTime);
if(frameTime < 0.0001)
{
overlayText += " --- ms (--- FPS)";
}
else
{
// only display frametime fractions if it's relevant (sub-integer frame time or FPS)
if(frameTime < 1.0)
overlayText += StringFormat::Fmt(" %.2lf ms", m_FrameTimer.GetAvgFrameTime());
else
overlayText += StringFormat::Fmt(" %d ms", int(m_FrameTimer.GetAvgFrameTime()));
if(fps < 1.0)
overlayText += StringFormat::Fmt(" (%.2lf FPS)", fps);
else
overlayText += StringFormat::Fmt(" (%d FPS)", int(fps));
}
}
}
overlayText += "\n";
#if ENABLED(RDOC_DEVEL)
{
overlayText += StringFormat::Fmt("%llu chunks - %.2f MB\n", Chunk::NumLiveChunks(),
float(Chunk::TotalMem()) / 1024.0f / 1024.0f);
}
#endif
if(capturesEnabled)
{
if(activeWindow)
{
rdcarray<RENDERDOC_InputButton> keys = GetCaptureKeys();
if(Keyboard::PlatformHasKeyInput())
{
for(size_t i = 0; i < keys.size(); i++)
{
if(i > 0)
overlayText += ", ";
overlayText += ToStr(keys[i]);
}
if(!keys.empty())
overlayText += " to capture.";
}
else
{
if(IsTargetControlConnected())
overlayText += "Connected by " + GetTargetControlUsername() + ".";
else
overlayText += "No remote access connection.";
}
if(overlay & eRENDERDOC_Overlay_CaptureList)
{
overlayText += StringFormat::Fmt(" %d Captures saved.\n", (uint32_t)m_Captures.size());
uint64_t now = Timing::GetUnixTimestamp();
for(size_t i = 0; i < m_Captures.size(); i++)
{
if(now - m_Captures[i].timestamp < 20)
{
if(m_Captures[i].frameNumber == ~0U)
overlayText += "Captured user-defined capture.\n";
else
overlayText += StringFormat::Fmt("Captured frame %d.\n", m_Captures[i].frameNumber);
}
}
}
}
else
{
rdcarray<RENDERDOC_InputButton> keys = GetFocusKeys();
if(Keyboard::PlatformHasKeyInput())
{
for(size_t i = 0; i < keys.size(); i++)
{
if(i > 0)
overlayText += ", ";
overlayText += ToStr(keys[i]);
}
if(!keys.empty())
overlayText += " to cycle.";
}
else
{
if(IsTargetControlConnected())
overlayText += "Connected by " + GetTargetControlUsername() + ".";
else
overlayText += "No remote access connection.";
}
if(curIdx >= 0)
overlayText += StringFormat::Fmt(" %s window %d.", ToStr(curDriver).c_str(), curIdx);
else if(curDriver != RDCDriver::Unknown)
overlayText += StringFormat::Fmt(" Unknown %s window.", ToStr(curDriver).c_str());
else
overlayText += " Unknown window.";
}
}
return overlayText;
}
void RenderDoc::QueueCapture(uint32_t frameNumber)
{
auto it = std::lower_bound(m_QueuedFrameCaptures.begin(), m_QueuedFrameCaptures.end(), frameNumber);
if(it == m_QueuedFrameCaptures.end() || *it != frameNumber)
m_QueuedFrameCaptures.insert(it - m_QueuedFrameCaptures.begin(), frameNumber);
}
bool RenderDoc::ShouldTriggerCapture(uint32_t frameNumber)
{
bool ret = m_Cap > 0;
if(m_Cap > 0)
m_Cap--;
rdcarray<uint32_t> frames;
frames.swap(m_QueuedFrameCaptures);
for(auto it = frames.begin(); it != frames.end(); ++it)
{
if(*it < frameNumber)
{
// discard, this frame is past.
}
else if((*it) == frameNumber)
{
// we want to capture the next frame
ret = true;
}
else
{
// not hit this yet, keep it around
m_QueuedFrameCaptures.push_back(*it);
}
}
return ret;
}
void RenderDoc::ResamplePixels(const FramePixels &in, RDCThumb &out)
{
if(in.width == 0 || in.height == 0)
{
out = RDCThumb();
return;
}
// code below assumes pitch_requirement is a power of 2 number
RDCASSERT((in.pitch_requirement & (in.pitch_requirement - 1)) == 0);
out.width = (uint16_t)RDCMIN(in.max_width, in.width);
out.width &= ~(in.pitch_requirement - 1); // align down to multiple of in.
out.height = uint16_t(out.width * in.height / in.width);
out.pixels.resize(3 * out.width * out.height);
out.format = FileType::Raw;
byte *dst = (byte *)out.pixels.data();
byte *source = (byte *)in.data;
for(uint32_t y = 0; y < out.height; y++)
{
for(uint32_t x = 0; x < out.width; x++)
{
uint32_t xSource = x * in.width / out.width;
uint32_t ySource = y * in.height / out.height;
byte *src = &source[in.stride * xSource + in.pitch * ySource];
if(in.buf1010102)
{
uint32_t *src1010102 = (uint32_t *)src;
Vec4f unorm = ConvertFromR10G10B10A2(*src1010102);
dst[0] = (byte)(unorm.x * 255.0f);
dst[1] = (byte)(unorm.y * 255.0f);
dst[2] = (byte)(unorm.z * 255.0f);
}
else if(in.buf565)
{
uint16_t *src565 = (uint16_t *)src;
Vec3f unorm = ConvertFromB5G6R5(*src565);
dst[0] = (byte)(unorm.x * 255.0f);
dst[1] = (byte)(unorm.y * 255.0f);
dst[2] = (byte)(unorm.z * 255.0f);
}
else if(in.buf5551)
{
uint16_t *src5551 = (uint16_t *)src;
Vec4f unorm = ConvertFromB5G5R5A1(*src5551);
dst[0] = (byte)(unorm.x * 255.0f);
dst[1] = (byte)(unorm.y * 255.0f);
dst[2] = (byte)(unorm.z * 255.0f);
}
else if(in.bgra)
{
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
}
else if(in.bpc == 2) // R16G16B16A16 backbuffer
{
uint16_t *src16 = (uint16_t *)src;
float linearR = RDCCLAMP(ConvertFromHalf(src16[0]), 0.0f, 1.0f);
float linearG = RDCCLAMP(ConvertFromHalf(src16[1]), 0.0f, 1.0f);
float linearB = RDCCLAMP(ConvertFromHalf(src16[2]), 0.0f, 1.0f);
dst[0] = byte(255.0f * ConvertLinearToSRGB(linearR));
dst[1] = byte(255.0f * ConvertLinearToSRGB(linearG));
dst[2] = byte(255.0f * ConvertLinearToSRGB(linearB));
}
else
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
}
dst += 3;
}
}
if(!in.is_y_flipped)
{
for(uint16_t y = 0; y < out.height / 2; y++)
{
uint16_t flipY = (out.height - 1 - y);
for(uint16_t x = 0; x < out.width; x++)
{
byte *src = (byte *)out.pixels.data();
byte save[3];
save[0] = src[(y * out.width + x) * 3 + 0];
save[1] = src[(y * out.width + x) * 3 + 1];
save[2] = src[(y * out.width + x) * 3 + 2];
src[(y * out.width + x) * 3 + 0] = src[(flipY * out.width + x) * 3 + 0];
src[(y * out.width + x) * 3 + 1] = src[(flipY * out.width + x) * 3 + 1];
src[(y * out.width + x) * 3 + 2] = src[(flipY * out.width + x) * 3 + 2];
src[(flipY * out.width + x) * 3 + 0] = save[0];
src[(flipY * out.width + x) * 3 + 1] = save[1];
src[(flipY * out.width + x) * 3 + 2] = save[2];
}
}
}
}
void RenderDoc::EncodeThumbPixels(const RDCThumb &in, RDCThumb &out)
{
if(in.width == 0 || in.height == 0)
{
out = RDCThumb();
return;
}
struct WriteCallbackData
{
bytebuf buffer;
static void writeData(void *context, void *data, int size)
{
WriteCallbackData *pThis = (WriteCallbackData *)context;
pThis->buffer.append((const byte *)data, size);
}
};
if(out.format == FileType::PNG)
{
WriteCallbackData callbackData;
stbi_write_png_to_func(&WriteCallbackData::writeData, &callbackData, in.width, in.height, 3,
in.pixels.data(), 0);
out.width = in.width;
out.height = in.height;
out.pixels.swap(callbackData.buffer);
}
else
{
// should be JPG if not PNG
RDCASSERTEQUAL(out.format, FileType::JPG);
out.width = in.width;
out.height = in.height;
out.format = FileType::JPG;
const int comp = 3;
int len = in.width * in.height * comp;
out.pixels.resize(len);
jpge::params p;
p.m_quality = 90;
jpge::compress_image_to_jpeg_file_in_memory(out.pixels.data(), len, in.width, in.height, comp,
in.pixels.data(), p);
out.pixels.resize(len);
}
}
RDCFile *RenderDoc::CreateRDC(RDCDriver driver, uint32_t frameNum, const FramePixels &fp)
{
RDCFile *ret = new RDCFile;
rdcstr suffix = StringFormat::Fmt("_frame%u", frameNum);
if(frameNum == ~0U)
suffix = "_capture";
m_CurrentLogFile = StringFormat::Fmt("%s%s.rdc", m_CaptureFileTemplate.c_str(), suffix.c_str());
// make sure we don't stomp another capture if we make multiple captures in the same frame.
{
SCOPED_LOCK(m_CaptureLock);
int altnum = 2;
while(std::find_if(m_Captures.begin(), m_Captures.end(), [this](const CaptureData &o) {
return o.path == m_CurrentLogFile;
}) != m_Captures.end())
{
m_CurrentLogFile =
StringFormat::Fmt("%s%s_%d.rdc", m_CaptureFileTemplate.c_str(), suffix.c_str(), altnum);
altnum++;
}
}
RDCThumb outRaw, outThumb;
if(fp.data)
{
// point sample info into raw buffer
ResamplePixels(fp, outRaw);
if(Capture_IncludeExtendedThumbnail())
outThumb.format = FileType::PNG;
else
outThumb.format = FileType::JPG;
EncodeThumbPixels(outRaw, outThumb);
}
ret->SetData(driver, ToStr(driver).c_str(), OSUtility::GetMachineIdent(), &outThumb, m_TimeBase,
m_TimeFrequency);
FileIO::CreateParentDirectory(m_CurrentLogFile);
ret->Create(m_CurrentLogFile.c_str());
if(ret->Error() != ResultCode::Succeeded)
SAFE_DELETE(ret);
return ret;
}
bool RenderDoc::HasReplayDriver(RDCDriver driver) const
{
// Image driver is handled specially and isn't registered in the map
if(driver == RDCDriver::Image)
return true;
return m_ReplayDriverProviders.find(driver) != m_ReplayDriverProviders.end();
}
bool RenderDoc::HasRemoteDriver(RDCDriver driver) const
{
if(m_RemoteDriverProviders.find(driver) != m_RemoteDriverProviders.end())
return true;
return HasReplayDriver(driver);
}
void RenderDoc::RegisterReplayProvider(RDCDriver driver, ReplayDriverProvider provider)
{
if(HasReplayDriver(driver))
RDCERR("Re-registering provider for %s", ToStr(driver).c_str());
if(HasRemoteDriver(driver))
RDCWARN("Registering local provider for existing remote provider %s", ToStr(driver).c_str());
m_ReplayDriverProviders[driver] = provider;
}
void RenderDoc::RegisterRemoteProvider(RDCDriver driver, RemoteDriverProvider provider)
{
if(HasRemoteDriver(driver))
RDCERR("Re-registering provider for %s", ToStr(driver).c_str());
if(HasReplayDriver(driver))
RDCWARN("Registering remote provider for existing local provider %s", ToStr(driver).c_str());
m_RemoteDriverProviders[driver] = provider;
}
void RenderDoc::RegisterStructuredProcessor(RDCDriver driver, StructuredProcessor provider)
{
RDCASSERT(m_StructProcesssors.find(driver) == m_StructProcesssors.end());
m_StructProcesssors[driver] = provider;
}
void RenderDoc::RegisterCaptureExporter(CaptureExporter exporter, CaptureFileFormat description)
{
rdcstr filetype = description.extension;
for(const CaptureFileFormat &fmt : m_ImportExportFormats)
{
if(fmt.extension == filetype)
{
RDCERR("Duplicate exporter for '%s' found", filetype.c_str());
return;
}
}
description.openSupported = false;
description.convertSupported = true;
m_ImportExportFormats.push_back(description);
m_Exporters[filetype] = exporter;
}
void RenderDoc::RegisterCaptureImportExporter(CaptureImporter importer, CaptureExporter exporter,
CaptureFileFormat description)
{
rdcstr filetype = description.extension;
for(const CaptureFileFormat &fmt : m_ImportExportFormats)
{
if(fmt.extension == filetype)
{
RDCERR("Duplicate import/exporter for '%s' found", filetype.c_str());
return;
}
}
description.openSupported = true;
description.convertSupported = true;
m_ImportExportFormats.push_back(description);
m_Importers[filetype] = importer;
m_Exporters[filetype] = exporter;
}
void RenderDoc::RegisterDeviceProtocol(const rdcstr &protocol, ProtocolHandler handler)
{
if(m_Protocols[protocol] != NULL)
{
RDCERR("Duplicate protocol registration: %s", protocol.c_str());
return;
}
m_Protocols[protocol] = handler;
}
StructuredProcessor RenderDoc::GetStructuredProcessor(RDCDriver driver)
{
auto it = m_StructProcesssors.find(driver);
if(it == m_StructProcesssors.end())
return NULL;
return it->second;
}
CaptureExporter RenderDoc::GetCaptureExporter(const rdcstr &filetype)
{
auto it = m_Exporters.find(filetype);
if(it == m_Exporters.end())
return NULL;
return it->second;
}
CaptureImporter RenderDoc::GetCaptureImporter(const rdcstr &filetype)
{
auto it = m_Importers.find(filetype);
if(it == m_Importers.end())
return NULL;
return it->second;
}
rdcarray<rdcstr> RenderDoc::GetSupportedDeviceProtocols()
{
rdcarray<rdcstr> ret;
for(auto it = m_Protocols.begin(); it != m_Protocols.end(); ++it)
ret.push_back(it->first);
return ret;
}
IDeviceProtocolHandler *RenderDoc::GetDeviceProtocol(const rdcstr &protocol)
{
rdcstr p = protocol;
// allow passing in an URL with ://
int32_t offs = p.find("://");
if(offs >= 0)
p.erase(offs, p.size() - offs);
auto it = m_Protocols.find(p);
if(it != m_Protocols.end())
return it->second();
return NULL;
}
rdcarray<CaptureFileFormat> RenderDoc::GetCaptureFileFormats()
{
rdcarray<CaptureFileFormat> ret = m_ImportExportFormats;
std::sort(ret.begin(), ret.end());
{
CaptureFileFormat rdc;
rdc.extension = "rdc";
rdc.name = "Native RDC capture file format.";
rdc.description = "The format produced by frame-captures from applications directly.";
rdc.openSupported = true;
rdc.convertSupported = true;
ret.insert(0, rdc);
}
return ret;
}
rdcarray<GPUDevice> RenderDoc::GetAvailableGPUs()
{
SyncAvailableGPUThread();
return m_AvailableGPUs;
}
void RenderDoc::SyncAvailableGPUThread()
{
if(m_AvailableGPUThread)
{
Threading::JoinThread(m_AvailableGPUThread);
Threading::CloseThread(m_AvailableGPUThread);
m_AvailableGPUThread = 0;
}
}
bool RenderDoc::HasReplaySupport(RDCDriver driverType)
{
if(driverType == RDCDriver::Image)
return true;
if(driverType == RDCDriver::Unknown && !m_ReplayDriverProviders.empty())
return true;
return m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end();
}
RDResult RenderDoc::CreateProxyReplayDriver(RDCDriver proxyDriver, IReplayDriver **driver)
{
SyncAvailableGPUThread();
// passing RDCDriver::Unknown means 'I don't care, give me a proxy driver of any type'
if(proxyDriver == RDCDriver::Unknown)
{
if(!m_ReplayDriverProviders.empty())
return m_ReplayDriverProviders.begin()->second(NULL, ReplayOptions(), driver);
}
if(m_ReplayDriverProviders.find(proxyDriver) != m_ReplayDriverProviders.end())
return m_ReplayDriverProviders[proxyDriver](NULL, ReplayOptions(), driver);
RETURN_ERROR_RESULT(ResultCode::APIUnsupported, "Unsupported replay driver requested: %s",
ToStr(proxyDriver).c_str());
}
RDResult RenderDoc::CreateReplayDriver(RDCFile *rdc, const ReplayOptions &opts, IReplayDriver **driver)
{
if(driver == NULL)
return ResultCode::InvalidParameter;
SyncAvailableGPUThread();
// allows passing NULL rdcfile as 'I don't care, give me a proxy driver of any type'
if(rdc == NULL)
{
if(!m_ReplayDriverProviders.empty())
return m_ReplayDriverProviders.begin()->second(NULL, opts, driver);
RETURN_ERROR_RESULT(ResultCode::APIUnsupported,
"Request for proxy replay device, but no replay providers are available.");
}
RDCDriver driverType = rdc->GetDriver();
// image support is special, handle it here
if(driverType == RDCDriver::Image)
return IMG_CreateReplayDevice(rdc, driver);
if(m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end())
return m_ReplayDriverProviders[driverType](rdc, opts, driver);
RDCERR("Unsupported replay driver requested: %s", ToStr(driverType).c_str());
return ResultCode::APIUnsupported;
}
RDResult RenderDoc::CreateRemoteDriver(RDCFile *rdc, const ReplayOptions &opts, IRemoteDriver **driver)
{
if(rdc == NULL || driver == NULL)
return ResultCode::InvalidParameter;
SyncAvailableGPUThread();
RDCDriver driverType = rdc->GetDriver();
if(m_RemoteDriverProviders.find(driverType) != m_RemoteDriverProviders.end())
return m_RemoteDriverProviders[driverType](rdc, opts, driver);
// replay drivers are remote drivers, fall back and try them
if(m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end())
{
IReplayDriver *dr = NULL;
RDResult result = m_ReplayDriverProviders[driverType](rdc, opts, &dr);
if(result == ResultCode::Succeeded)
*driver = (IRemoteDriver *)dr;
else
RDCASSERT(dr == NULL);
return result;
}
RETURN_ERROR_RESULT(ResultCode::APIUnsupported, "Unsupported replay driver requested: %s",
ToStr(driverType).c_str());
}
void RenderDoc::AddActiveDriver(RDCDriver driver, bool present)
{
if(driver == RDCDriver::Unknown)
return;
uint64_t timestamp = present ? Timing::GetUnixTimestamp() : 0;
{
SCOPED_LOCK(m_DriverLock);
uint64_t &active = m_ActiveDrivers[driver];
active = RDCMAX(active, timestamp);
}
}
void RenderDoc::SetDriverUnsupportedMessage(RDCDriver driver, rdcstr message)
{
if(driver == RDCDriver::Unknown)
return;
SCOPED_LOCK(m_DriverLock);
m_APISupportMessages[driver] = message;
}
std::map<RDCDriver, RDCDriverStatus> RenderDoc::GetActiveDrivers()
{
std::map<RDCDriver, uint64_t> drivers;
{
SCOPED_LOCK(m_DriverLock);
drivers = m_ActiveDrivers;
}
std::map<RDCDriver, RDCDriverStatus> ret;
for(auto it = drivers.begin(); it != drivers.end(); ++it)
{
RDCDriverStatus &status = ret[it->first];
// driver is presenting if the timestamp is greater than 0 and less than 10 seconds ago (gives a
// little leeway for loading screens or something where the presentation stops temporarily).
// we also assume that during a capture if it was presenting, then it's still capturing.
// Otherwise a long capture would temporarily set it as not presenting.
status.presenting = it->second > 0;
if(status.presenting && !IsFrameCapturing() && it->second < Timing::GetUnixTimestamp() - 10)
status.presenting = false;
status.supported = (HasRemoteDriver(it->first) || HasReplayDriver(it->first)) &&
HasActiveFrameCapturer(it->first);
if(!status.supported)
{
SCOPED_LOCK(m_DriverLock);
status.supportMessage = m_APISupportMessages[it->first];
}
}
return ret;
}
std::map<RDCDriver, rdcstr> RenderDoc::GetReplayDrivers()
{
std::map<RDCDriver, rdcstr> ret;
for(auto it = m_ReplayDriverProviders.begin(); it != m_ReplayDriverProviders.end(); ++it)
ret[it->first] = ToStr(it->first);
return ret;
}
std::map<RDCDriver, rdcstr> RenderDoc::GetRemoteDrivers()
{
std::map<RDCDriver, rdcstr> ret;
for(auto it = m_RemoteDriverProviders.begin(); it != m_RemoteDriverProviders.end(); ++it)
ret[it->first] = ToStr(it->first);
// replay drivers are remote drivers.
for(auto it = m_ReplayDriverProviders.begin(); it != m_ReplayDriverProviders.end(); ++it)
ret[it->first] = ToStr(it->first);
return ret;
}
DriverInformation RenderDoc::GetDriverInformation(GraphicsAPI api)
{
DriverInformation ret = {};
RDCDriver driverType = RDCDriver::Unknown;
switch(api)
{
case GraphicsAPI::D3D11: driverType = RDCDriver::D3D11; break;
case GraphicsAPI::D3D12: driverType = RDCDriver::D3D12; break;
case GraphicsAPI::OpenGL: driverType = RDCDriver::OpenGL; break;
case GraphicsAPI::Vulkan: driverType = RDCDriver::Vulkan; break;
}
if(driverType == RDCDriver::Unknown || !HasReplayDriver(driverType))
return ret;
IReplayDriver *driver = NULL;
RDResult result = CreateProxyReplayDriver(driverType, &driver);
if(result == ResultCode::Succeeded)
{
ret = driver->GetDriverInfo();
}
else
{
RDCERR("Couldn't create proxy replay driver for %s: %s", ToStr(driverType).c_str(),
ResultDetails(result).Message().c_str());
}
if(driver)
driver->Shutdown();
return ret;
}
void RenderDoc::EnableVendorExtensions(VendorExtensions ext)
{
m_VendorExts[(int)ext] = true;
RDCWARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
RDCWARN("!!! Vendor Extension enabled: %s", ToStr(ext).c_str());
RDCWARN("!!! ");
RDCWARN("!!! This can cause crashes, incorrect replay, or other problems and");
RDCWARN("!!! is explicitly unsupported. Do not enable without understanding.");
RDCWARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
void RenderDoc::SetCaptureOptions(const CaptureOptions &opts)
{
m_Options = opts;
LibraryHooks::OptionsUpdated();
}
void RenderDoc::SetCaptureFileTemplate(const rdcstr &pathtemplate)
{
if(pathtemplate.empty())
return;
m_CaptureFileTemplate = pathtemplate;
if(m_CaptureFileTemplate.length() > 4 &&
m_CaptureFileTemplate.substr(m_CaptureFileTemplate.length() - 4) == ".rdc")
m_CaptureFileTemplate = m_CaptureFileTemplate.substr(0, m_CaptureFileTemplate.length() - 4);
FileIO::CreateParentDirectory(m_CaptureFileTemplate);
}
void RenderDoc::FinishCaptureWriting(RDCFile *rdc, uint32_t frameNumber)
{
RenderDoc::Inst().SetProgress(CaptureProgress::FileWriting, 0.0f);
if(rdc)
{
// add the resolve database if we were capturing callstacks.
if(m_Options.captureCallstacks)
{
SectionProperties props = {};
props.type = SectionType::ResolveDatabase;
props.version = 1;
StreamWriter *w = rdc->WriteSection(props);
size_t sz = 0;
Callstack::GetLoadedModules(NULL, sz);
byte *buf = new byte[sz];
Callstack::GetLoadedModules(buf, sz);
w->Write(buf, sz);
w->Finish();
delete w;
}
const RDCThumb &thumb = rdc->GetThumbnail();
if(thumb.format != FileType::JPG && thumb.width > 0 && thumb.height > 0)
{
SectionProperties props = {};
props.type = SectionType::ExtendedThumbnail;
props.version = 1;
StreamWriter *w = rdc->WriteSection(props);
// if this file format ever changes, be sure to update the XML export which has a special
// handling for this case.
ExtThumbnailHeader header;
header.width = thumb.width;
header.height = thumb.height;
header.format = thumb.format;
header.len = (uint32_t)thumb.pixels.size();
w->Write(header);
w->Write(thumb.pixels.data(), thumb.pixels.size());
w->Finish();
delete w;
}
if(Capture_Debug_SnapshotDiagnosticLog())
{
rdcstr logcontents = FileIO::logfile_readall(0, RDCGETLOGFILE());
SectionProperties props = {};
props.type = SectionType::EmbeddedLogfile;
props.version = 1;
props.flags = SectionFlags::LZ4Compressed;
StreamWriter *w = rdc->WriteSection(props);
w->Write(logcontents.data(), logcontents.size());
w->Finish();
delete w;
}
RDCLOG("Written to disk: %s", m_CurrentLogFile.c_str());
CaptureData cap;
cap.path = m_CurrentLogFile;
cap.title = m_CaptureTitle;
cap.timestamp = Timing::GetUnixTimestamp();
cap.driver = rdc->GetDriver();
cap.frameNumber = frameNumber;
m_CaptureTitle.clear();
{
SCOPED_LOCK(m_CaptureLock);
m_Captures.push_back(cap);
}
delete rdc;
}
else
{
RDCLOG("Discarded capture, Frame %u", frameNumber);
}
RenderDoc::Inst().SetProgress(CaptureProgress::FileWriting, 1.0f);
}
void RenderDoc::AddChildProcess(uint32_t pid, uint32_t ident)
{
if(ident == 0 || ident == m_RemoteIdent)
{
RDCERR("Child process %u returned invalid ident %u. Possibly too many listen sockets in use!",
pid, ident);
return;
}
SCOPED_LOCK(m_ChildLock);
m_Children.push_back(make_rdcpair(pid, ident));
}
rdcarray<rdcpair<uint32_t, uint32_t>> RenderDoc::GetChildProcesses()
{
SCOPED_LOCK(m_ChildLock);
return m_Children;
}
void RenderDoc::CompleteChildThread(uint32_t pid)
{
SCOPED_LOCK(m_ChildLock);
// the thread for this PID is done, mark it as ready to wait on by zero-ing out the PID
for(rdcpair<uint32_t, Threading::ThreadHandle> &c : m_ChildThreads)
{
if(c.first == pid)
c.first = 0;
}
}
void RenderDoc::AddChildThread(uint32_t pid, Threading::ThreadHandle thread)
{
SCOPED_LOCK(m_ChildLock);
m_ChildThreads.push_back(make_rdcpair(pid, thread));
}
void RenderDoc::ValidateCaptures()
{
SCOPED_LOCK(m_CaptureLock);
m_Captures.removeIf([](const CaptureData &cap) { return !FileIO::exists(cap.path); });
}
rdcarray<CaptureData> RenderDoc::GetCaptures()
{
SCOPED_LOCK(m_CaptureLock);
return m_Captures;
}
void RenderDoc::MarkCaptureRetrieved(uint32_t idx)
{
SCOPED_LOCK(m_CaptureLock);
if(idx < m_Captures.size())
{
m_Captures[idx].retrieved = true;
}
}
void RenderDoc::AddDeviceFrameCapturer(void *dev, IFrameCapturer *cap)
{
if(IsReplayApp())
return;
if(dev == NULL || cap == NULL)
{
RDCERR("Invalid FrameCapturer %#p for device: %#p", cap, dev);
return;
}
RDCLOG("Adding %s device frame capturer for %#p", ToStr(cap->GetFrameCaptureDriver()).c_str(), dev);
SCOPED_LOCK(m_CapturerListLock);
m_DeviceFrameCapturers[dev] = cap;
}
void RenderDoc::RemoveDeviceFrameCapturer(void *dev)
{
if(IsReplayApp())
return;
if(dev == NULL)
{
RDCERR("Invalid device pointer: %#p", dev);
return;
}
RDCLOG("Removing device frame capturer for %#p", dev);
SCOPED_LOCK(m_CapturerListLock);
m_DeviceFrameCapturers.erase(dev);
}
void RenderDoc::AddFrameCapturer(DeviceOwnedWindow devWnd, IFrameCapturer *cap)
{
if(IsReplayApp())
return;
if(devWnd.device == NULL || devWnd.windowHandle == NULL || cap == NULL)
{
RDCERR("Invalid FrameCapturer %#p for combination: %#p / %#p", cap, devWnd.device,
devWnd.windowHandle);
return;
}
RDCLOG("Adding %s frame capturer for %#p / %#p", ToStr(cap->GetFrameCaptureDriver()).c_str(),
devWnd.device, devWnd.windowHandle);
SCOPED_LOCK(m_CapturerListLock);
auto it = m_WindowFrameCapturers.find(devWnd);
if(it != m_WindowFrameCapturers.end())
{
if(it->second.FrameCapturer != cap)
RDCERR("New different FrameCapturer being registered for known device/window pair!");
it->second.RefCount++;
}
else
{
m_WindowFrameCapturers[devWnd].FrameCapturer = cap;
}
// the first one we see becomes the default
if(m_ActiveWindow == DeviceOwnedWindow())
m_ActiveWindow = devWnd;
}
void RenderDoc::RemoveFrameCapturer(DeviceOwnedWindow devWnd)
{
if(IsReplayApp())
return;
RDCLOG("Removing frame capturer for %#p / %#p", devWnd.device, devWnd.windowHandle);
SCOPED_LOCK(m_CapturerListLock);
auto it = m_WindowFrameCapturers.find(devWnd);
if(it != m_WindowFrameCapturers.end())
{
it->second.RefCount--;
if(it->second.RefCount <= 0)
{
RDCLOG("Removed last refcount");
if(m_ActiveWindow == devWnd)
{
RDCLOG("Removed active window");
if(m_WindowFrameCapturers.size() == 1)
{
m_ActiveWindow = DeviceOwnedWindow();
}
else
{
auto newactive = m_WindowFrameCapturers.begin();
// active window could be the first in our list, move
// to second (we know from above there are at least 2)
if(m_ActiveWindow == newactive->first)
newactive++;
m_ActiveWindow = newactive->first;
}
}
m_WindowFrameCapturers.erase(it);
}
}
else
{
RDCERR("Removing FrameCapturer for unknown window!");
}
}
bool RenderDoc::HasActiveFrameCapturer(RDCDriver driver)
{
SCOPED_LOCK(m_CapturerListLock);
for(auto cap = m_WindowFrameCapturers.begin(); cap != m_WindowFrameCapturers.end(); cap++)
if(cap->second.FrameCapturer->GetFrameCaptureDriver() == driver)
return true;
for(auto cap = m_DeviceFrameCapturers.begin(); cap != m_DeviceFrameCapturers.end(); cap++)
if(cap->second->GetFrameCaptureDriver() == driver)
return true;
return false;
}
bool RenderDoc::GetTrackedFileData(const rdcstr &nickname, bytebuf &data) const
{
SCOPED_READLOCK(m_TrackedFilesLock);
for(const TrackedFile &f : m_TrackedFiles)
{
if(f.nickname == nickname)
{
if(f.hasData)
data = f.data;
return f.hasData;
}
}
return false;
}
bool RenderDoc::DoesTrackedFileExist(const rdcstr &nickname) const
{
SCOPED_READLOCK(m_TrackedFilesLock);
for(const TrackedFile &f : m_TrackedFiles)
{
if(f.nickname == nickname)
return true;
}
return false;
}
// return false if the nickname already exists
bool RenderDoc::AddTrackedFileReference(const rdcstr &nickname, const rdcstr &filepath)
{
if(DoesTrackedFileExist(nickname))
return false;
SCOPED_WRITELOCK(m_TrackedFilesLock);
m_TrackedFiles.emplace_back(nickname, filepath);
return true;
}
void RenderDoc::ClearTrackedFiles()
{
SCOPED_WRITELOCK(m_TrackedFilesLock);
m_TrackedFiles.clear();
}
bool RenderDoc::HasTrackedFileData() const
{
SCOPED_READLOCK(m_TrackedFilesLock);
return !m_TrackedFiles.empty();
}
rdcarray<rdcstr> RenderDoc::GetTrackedFileNicknames() const
{
rdcarray<rdcstr> nickNames;
{
SCOPED_READLOCK(m_TrackedFilesLock);
for(const TrackedFile &f : m_TrackedFiles)
nickNames.push_back(f.nickname);
}
return nickNames;
}
RDResult RenderDoc::ReadExternalFiles(RDCFile *rdc)
{
int32_t idx = rdc->SectionIndex(SectionType::EmbeddedExternalFiles);
if(idx < 0)
RETURN_WARNING_RESULT(ResultCode::DataNotAvailable, "No EmbeddedExternalFiles section");
// int32_t countFileEntries;
int32_t countFileEntries = 0;
bytebuf sectionData;
{
StreamReader *reader = rdc->ReadSection(idx);
sectionData.resize((size_t)reader->GetSize());
bool success = reader->Read(sectionData.data(), reader->GetSize());
delete reader;
if(!success)
sectionData.clear();
}
const uint32_t bufferSize = (uint32_t)sectionData.size();
uint32_t readSize = sizeof(countFileEntries);
uint32_t byteIndex = 0;
if(byteIndex + readSize > bufferSize)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"EmbeddedExternalFiles section does not have valid data");
const byte *bufferPtr = sectionData.data();
memcpy(&countFileEntries, bufferPtr, readSize);
byteIndex += readSize;
bufferPtr += readSize;
rdcarray<TrackedFile> externalFiles;
externalFiles.resize(countFileEntries);
// FileEntry fileEntries[];
for(int32_t i = 0; i < countFileEntries; ++i)
{
TrackedFile &externalFile = externalFiles[i];
externalFile.filepath.clear();
// FileEntry:
// uint32_t nameSize;
uint32_t nameSize = 0;
readSize = sizeof(nameSize);
if(byteIndex + readSize > bufferSize)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"EmbeddedExternalFiles section does not have valid data");
memcpy(&nameSize, bufferPtr, readSize);
byteIndex += readSize;
bufferPtr += readSize;
// char name[];
readSize = nameSize;
if(byteIndex + readSize > bufferSize)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"EmbeddedExternalFiles section does not have valid data");
externalFile.nickname.assign((const char *)bufferPtr, readSize);
byteIndex += readSize;
bufferPtr += readSize;
// uint32_t dataSize;
uint32_t dataSize = 0;
readSize = sizeof(dataSize);
if(byteIndex + readSize > bufferSize)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"EmbeddedExternalFiles section does not have valid data");
memcpy(&dataSize, bufferPtr, readSize);
byteIndex += readSize;
bufferPtr += readSize;
// byte data[];
readSize = dataSize;
if(byteIndex + readSize > bufferSize)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"EmbeddedExternalFiles section does not have valid data");
externalFile.data.assign(bufferPtr, readSize);
byteIndex += readSize;
bufferPtr += readSize;
}
if(byteIndex != bufferSize)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"EmbeddedExternalFiles section does not have valid data");
SCOPED_WRITELOCK(m_TrackedFilesLock);
for(const TrackedFile &f : externalFiles)
{
for(TrackedFile &file : m_TrackedFiles)
{
if(file.nickname == f.nickname)
{
file.data = f.data;
file.filepath.clear();
file.hasData = true;
break;
}
}
m_TrackedFiles.emplace_back(f.nickname, f.data);
}
return RDResult();
}
RDResult RenderDoc::WriteExternalFiles(RDCFile *rdc, const rdcarray<TrackedFile> &trackedFiles)
{
if(!rdc)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"Data missing for creation of file, set metadata first.");
RDResult rdcRes = rdc->Error();
if(rdcRes != ResultCode::Succeeded)
return rdcRes;
int32_t countFileEntries = trackedFiles.count();
bytebuf sectionData;
// int32_t countFileEntries;
sectionData.append((byte *)&countFileEntries, sizeof(countFileEntries));
// FileEntry fileEntries[];
for(const RenderDoc::TrackedFile &f : trackedFiles)
{
// FileEntry:
// uint32_t nameSize;
// char name[];
const rdcstr &name = f.nickname;
const uint32_t nameSize = (uint32_t)name.size();
sectionData.append((byte *)&nameSize, sizeof(nameSize));
sectionData.append((byte *)name.data(), nameSize);
// uint32_t dataSize;
// byte data[];
uint32_t dataSize = 0;
const byte *data = NULL;
bytebuf fileData;
if(!f.filepath.empty())
{
if(FileIO::ReadAll(f.filepath, fileData))
{
dataSize = (uint32_t)fileData.size();
data = fileData.data();
}
else
{
RDCWARN("Data missing for externally referenced file %s", f.filepath.c_str());
}
}
else
{
dataSize = (uint32_t)f.data.size();
data = f.data.data();
}
sectionData.append((byte *)&dataSize, sizeof(dataSize));
sectionData.append(data, dataSize);
}
SectionProperties props;
props.type = SectionType::EmbeddedExternalFiles;
props.flags = SectionFlags::ZstdCompressed;
props.version = 1;
StreamWriter *writer = rdc->WriteSection(props);
rdcRes = rdc->Error();
if(!writer || rdcRes != ResultCode::Succeeded)
return rdcRes;
writer->Write(sectionData.data(), sectionData.size());
writer->Finish();
delete writer;
return RDResult();
}
RDResult RenderDoc::EmbedExternalFiles(RDCFile *rdc)
{
if(!rdc)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"Data missing for creation of file, set metadata first.");
RDResult rdcRes = rdc->Error();
if(rdcRes != ResultCode::Succeeded)
return rdcRes;
if(rdc->SectionIndex(SectionType::EmbeddedExternalFiles) >= 0)
RDCWARN("Capture already has embedded external files - replacing existing section.");
SCOPED_WRITELOCK(m_TrackedFilesLock);
const rdcarray<RenderDoc::TrackedFile> &trackedFiles = m_TrackedFiles;
if(trackedFiles.empty())
RDCWARN("No external files to embed.");
RDCLOG("Embedding %d external files", trackedFiles.count());
return RenderDoc::Inst().WriteExternalFiles(rdc, trackedFiles);
}
RDResult RenderDoc::RemoveExternalFiles(RDCFile *rdc)
{
if(!rdc)
RETURN_WARNING_RESULT(ResultCode::FileCorrupted,
"Data missing for creation of file, set metadata first.");
RDResult rdcRes = rdc->Error();
if(rdcRes != ResultCode::Succeeded)
return rdcRes;
RDResult result;
if(rdc->SectionIndex(SectionType::EmbeddedExternalFiles) < 0)
RETURN_WARNING_RESULT(ResultCode::DataNotAvailable,
"Capture does not have any embedded external files.");
RDCLOG("Removing embedded external files (setting count to 0)");
rdcarray<RenderDoc::TrackedFile> trackedFiles;
return RenderDoc::Inst().WriteExternalFiles(rdc, trackedFiles);
}
bool RenderDoc::HasEmbeddedFiles(RDCFile *rdc) const
{
if(!rdc)
return false;
RDResult rdcRes = rdc->Error();
if(rdcRes != ResultCode::Succeeded)
return false;
int32_t idx = rdc->SectionIndex(SectionType::EmbeddedExternalFiles);
if(idx < 0)
return false;
int32_t countFileEntries = 0;
bytebuf sectionData;
{
StreamReader *reader = rdc->ReadSection(idx);
sectionData.resize((size_t)reader->GetSize());
bool success = reader->Read(sectionData.data(), reader->GetSize());
delete reader;
if(!success)
sectionData.clear();
}
if(sectionData.size() < sizeof(countFileEntries))
return false;
memcpy(&countFileEntries, sectionData.data(), sizeof(countFileEntries));
return countFileEntries > 0;
}
#if ENABLED(ENABLE_UNIT_TESTS)
#undef None
#undef Always
#include "catch/catch.hpp"
TEST_CASE("Check ResourceId tostr", "[tostr]")
{
union
{
ResourceId *id;
uint64_t *num;
} u;
uint64_t data = 0;
u.num = &data;
*u.num = 0;
CHECK(ToStr(*u.id) == "ResourceId::0");
*u.num = 1;
CHECK(ToStr(*u.id) == "ResourceId::1");
*u.num = 7;
CHECK(ToStr(*u.id) == "ResourceId::7");
*u.num = 17;
CHECK(ToStr(*u.id) == "ResourceId::17");
*u.num = 32;
CHECK(ToStr(*u.id) == "ResourceId::32");
*u.num = 913;
CHECK(ToStr(*u.id) == "ResourceId::913");
*u.num = 454;
CHECK(ToStr(*u.id) == "ResourceId::454");
*u.num = 123456;
CHECK(ToStr(*u.id) == "ResourceId::123456");
*u.num = 1234567;
CHECK(ToStr(*u.id) == "ResourceId::1234567");
*u.num = 0x1234567812345678ULL;
CHECK(ToStr(*u.id) == "ResourceId::1311768465173141112");
}
TEST_CASE("Check ResamplePixels", "[core][resamplepixels]")
{
RenderDoc::FramePixels sourcePixels;
uint32_t height = 4;
uint32_t width = 4;
uint32_t bytesPerComponent = 1;
uint32_t countComponents = 3;
uint32_t stride = bytesPerComponent * countComponents;
uint32_t pitch = width * stride;
sourcePixels.data = new byte[pitch * height];
sourcePixels.len = 0;
sourcePixels.width = width;
sourcePixels.pitch = width * stride;
sourcePixels.height = height;
sourcePixels.stride = stride;
sourcePixels.bpc = bytesPerComponent;
sourcePixels.buf1010102 = false;
sourcePixels.buf565 = false;
sourcePixels.buf5551 = false;
sourcePixels.bgra = false;
sourcePixels.pitch_requirement = width;
sourcePixels.max_width = width;
byte *source = (byte *)sourcePixels.data;
for(uint32_t y = 0; y < height; ++y)
{
for(uint32_t x = 0; x < width; ++x)
{
byte *src = &source[stride * x + pitch * y];
src[0] = (byte)(y + x);
src[1] = (byte)(y + x * 2);
src[2] = (byte)(y + x * 3);
}
}
RDCThumb thumbOutYNotFlipped;
sourcePixels.is_y_flipped = false;
RenderDoc::Inst().ResamplePixels(sourcePixels, thumbOutYNotFlipped);
CHECK(thumbOutYNotFlipped.width == width);
CHECK(thumbOutYNotFlipped.height == height);
byte *dest = (byte *)thumbOutYNotFlipped.pixels.data();
for(uint32_t y = 0; y < height; ++y)
{
for(uint32_t x = 0; x < width; ++x)
{
byte *src = &source[stride * x + pitch * y];
byte *dst = &dest[stride * x + pitch * (height - y - 1)];
CHECK((uint32_t)src[0] == (uint32_t)dst[0]);
CHECK((uint32_t)src[1] == (uint32_t)dst[1]);
CHECK((uint32_t)src[2] == (uint32_t)dst[2]);
}
}
RDCThumb thumbOutYFlipped;
sourcePixels.is_y_flipped = true;
RenderDoc::Inst().ResamplePixels(sourcePixels, thumbOutYFlipped);
CHECK(thumbOutYFlipped.width == width);
CHECK(thumbOutYFlipped.height == height);
dest = (byte *)thumbOutYFlipped.pixels.data();
for(uint32_t y = 0; y < height; ++y)
{
for(uint32_t x = 0; x < width; ++x)
{
byte *src = &source[stride * x + pitch * y];
byte *dst = &dest[stride * x + pitch * y];
CHECK((uint32_t)src[0] == (uint32_t)dst[0]);
CHECK((uint32_t)src[1] == (uint32_t)dst[1]);
CHECK((uint32_t)src[2] == (uint32_t)dst[2]);
}
}
RDCThumb thumbOutBGRA;
sourcePixels.bgra = true;
RenderDoc::Inst().ResamplePixels(sourcePixels, thumbOutBGRA);
CHECK(thumbOutBGRA.width == width);
CHECK(thumbOutBGRA.height == height);
dest = (byte *)thumbOutBGRA.pixels.data();
for(uint32_t y = 0; y < height; ++y)
{
for(uint32_t x = 0; x < width; ++x)
{
byte *src = &source[stride * x + pitch * y];
byte *dst = &dest[stride * x + pitch * y];
CHECK((uint32_t)src[0] == (uint32_t)dst[2]);
CHECK((uint32_t)src[1] == (uint32_t)dst[1]);
CHECK((uint32_t)src[2] == (uint32_t)dst[0]);
}
}
RDCThumb thumbOutDownsample;
sourcePixels.bgra = false;
sourcePixels.max_width = 2;
sourcePixels.pitch_requirement = 2;
RenderDoc::Inst().ResamplePixels(sourcePixels, thumbOutDownsample);
CHECK(thumbOutDownsample.width == 2);
CHECK(thumbOutDownsample.height == 2);
dest = (byte *)thumbOutDownsample.pixels.data();
for(uint32_t y = 0; y < 2; ++y)
{
for(uint32_t x = 0; x < 2; ++x)
{
byte *src = &source[stride * x * 2 + pitch * y * 2];
byte *dst = &dest[stride * x + pitch / 2 * y];
CHECK((uint32_t)src[0] == (uint32_t)dst[0]);
CHECK((uint32_t)src[1] == (uint32_t)dst[1]);
CHECK((uint32_t)src[2] == (uint32_t)dst[2]);
}
}
}
#endif