From 2d0f675ca8d745eed57873891e09a1c2cab69a74 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 20 Apr 2018 12:39:50 +0100 Subject: [PATCH] Add socket-based interop with RGP and profile embedding in captures --- qrenderdoc/Code/CaptureContext.cpp | 131 +++-- qrenderdoc/Code/CaptureContext.h | 21 +- qrenderdoc/Code/Interface/PersistantConfig.h | 6 + qrenderdoc/Code/Interface/QRDInterface.h | 72 ++- qrenderdoc/Code/RGPInterop.cpp | 447 ++++++++++++++++++ qrenderdoc/Code/RGPInterop.h | 144 ++++++ qrenderdoc/Windows/Dialogs/SettingsDialog.cpp | 42 ++ qrenderdoc/Windows/Dialogs/SettingsDialog.h | 4 + qrenderdoc/Windows/Dialogs/SettingsDialog.ui | 29 +- qrenderdoc/Windows/EventBrowser.cpp | 10 + qrenderdoc/Windows/MainWindow.cpp | 98 ++++ qrenderdoc/Windows/MainWindow.h | 2 + qrenderdoc/Windows/MainWindow.ui | 13 + qrenderdoc/Windows/PythonShell.cpp | 13 +- qrenderdoc/qrenderdoc.pro | 2 + qrenderdoc/qrenderdoc_local.vcxproj | 2 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 2 + 17 files changed, 913 insertions(+), 125 deletions(-) create mode 100644 qrenderdoc/Code/RGPInterop.cpp create mode 100644 qrenderdoc/Code/RGPInterop.h diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index c5d726e2d..6a224f177 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -40,6 +40,7 @@ #include "Windows/DebugMessageView.h" #include "Windows/Dialogs/CaptureDialog.h" #include "Windows/Dialogs/LiveCapture.h" +#include "Windows/Dialogs/SettingsDialog.h" #include "Windows/EventBrowser.h" #include "Windows/MainWindow.h" #include "Windows/PerformanceCounterViewer.h" @@ -52,6 +53,7 @@ #include "Windows/TextureViewer.h" #include "Windows/TimelineBar.h" #include "QRDUtils.h" +#include "RGPInterop.h" CaptureContext::CaptureContext(QString paramFilename, QString remoteHost, uint32_t remoteIdent, bool temp, PersistantConfig &cfg) @@ -829,8 +831,8 @@ void CaptureContext::CloseCapture() if(!m_CaptureLoaded) return; - m_RGP2Event.clear(); - m_Event2RGP.clear(); + delete m_RGP; + m_RGP = NULL; m_Config.CrashReport_LastOpenedCapture = QString(); @@ -1257,102 +1259,75 @@ void CaptureContext::LoadNotes(const QString &data) } } -void CaptureContext::CreateRGPMapping(uint32_t version) +bool CaptureContext::OpenRGPProfile(const rdcstr &filename) { - m_RGP2Event.clear(); - m_Event2RGP.clear(); + delete m_RGP; + m_RGP = NULL; if(!m_CaptureLoaded) - return; + { + RDDialog::critical(m_MainWindow, tr("Error opening RGP"), + tr("Can't open RGP profile with no capture open")); + return false; + } if(!m_StructuredFile) { - qCritical() << "Capture not loaded correctly - no structured data"; - return; + RDDialog::critical(m_MainWindow, tr("Error opening RGP"), + tr("Open capture has no structured data - can't initialise RGP interop.")); + return false; } - if(version != 0) + if(filename.isEmpty() || !QFileInfo(filename).exists()) { - qCritical() << "Unsupported version" << version; - return; + RDDialog::critical(m_MainWindow, tr("Error opening RGP"), + tr("Invalid filename specified to open as RGP Profile\n%1\n" + "Please restart RenderDoc and try again.") + .arg(QString(filename))); + return false; } - QStringList eventNames; + QString RGPPath = m_Config.ExternalTool_RadeonGPUProfiler; - if(m_APIProps.pipelineType == GraphicsAPI::Vulkan) + while(!QFileInfo(RGPPath).exists()) { - eventNames << lit("vkCmdDispatch") << lit("vkCmdDispatchIndirect") << lit("vkCmdDraw") - << lit("vkCmdDrawIndexed") << lit("vkCmdDrawIndirect") - << lit("vkCmdDrawIndexedIndirect") << lit("vkCmdClearColorImage") - << lit("vkCmdClearDepthStencilImage") << lit("vkCmdClearAttachments") - << lit("vkCmdPipelineBarrier") << lit("vkCmdCopyBuffer") << lit("vkCmdCopyImage") - << lit("vkCmdBlitImage") << lit("vkCmdCopyBufferToImage") - << lit("vkCmdCopyImageToBuffer") << lit("vkCmdUpdateBuffer") - << lit("vkCmdFillBuffer") << lit("vkCmdResolveImage") << lit("vkCmdResetQueryPool") - << lit("vkCmdCopyQueryPoolResults") << lit("vkCmdWaitEvents"); + QMessageBox::StandardButton res = RDDialog::question( + m_MainWindow, tr("Error opening RGP"), + tr("Path to RGP is incorrectly configured\n\nOpen settings window to configure path?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res == QMessageBox::Cancel || res == QMessageBox::No) + return false; + + SettingsDialog settings(*this, m_MainWindow); + settings.focusItem(lit("ExternalTool_RadeonGPUProfiler")); + RDDialog::show(&settings); + + RGPPath = m_Config.ExternalTool_RadeonGPUProfiler; } - else if(m_APIProps.pipelineType == GraphicsAPI::D3D12) + + // Make sure the version of RGP specified supports interop. If not, bail. + // Some older versions of RGP don't have command line support and will crash + if(!RGPInterop::RGPSupportsInterop(RGPPath)) { - // these names must match those in DoStringise(const D3D12Chunk &el) for the chunks - eventNames << lit("ID3D12GraphicsCommandList::Dispatch") - << lit("ID3D12GraphicsCommandList::DrawInstanced") - << lit("ID3D12GraphicsCommandList::DrawIndexedInstanced") - << lit("ID3D12GraphicsCommandList::ClearDepthStencilView") - << lit("ID3D12GraphicsCommandList::ClearRenderTargetView") - << lit("ID3D12GraphicsCommandList::ClearUnorderedAccessViewFloat") - << lit("ID3D12GraphicsCommandList::ClearUnorderedAccessViewUint") - << lit("ID3D12GraphicsCommandList::ResourceBarrier") - << lit("ID3D12GraphicsCommandList::CopyTextureRegion") - << lit("ID3D12GraphicsCommandList::CopyBufferRegion") - << lit("ID3D12GraphicsCommandList::CopyResource") - << lit("ID3D12GraphicsCommandList::CopyTiles") - << lit("ID3D12GraphicsCommandList::ResolveSubresource") - << lit("ID3D12GraphicsCommandList::ResolveSubresourceRegion") - << lit("ID3D12GraphicsCommandList::DiscardResource") - << lit("ID3D12GraphicsCommandList::ResolveQueryData") - << lit("ID3D12GraphicsCommandList::ExecuteIndirect") - << lit("ID3D12GraphicsCommandList::ExecuteBundle"); + QMessageBox::StandardButton res = RDDialog::question( + m_MainWindow, tr("RGP Version"), + tr("This version of RGP does not support interop. Please download RGP v1.2 or newer."), + QMessageBox::Ok); + return false; } - // if we don't have any event names, this API doesn't have a mapping. - if(eventNames.isEmpty()) - return; + QString portStr = QString::number(RGPInterop::Port); - m_Event2RGP.resize(m_LastDrawcall->eventId + 1); - - // linearId 0 is invalid, so map to eventId 0. - // the first real event will be linearId 1 - m_RGP2Event.push_back(0); - - // iterate over all draws depth-first, to enumerate events - CreateRGPMapping(eventNames, m_Drawcalls); -} - -void CaptureContext::CreateRGPMapping(const QStringList &eventNames, - const rdcarray &drawcalls) -{ - const SDFile &file = *m_StructuredFile; - - for(const DrawcallDescription &draw : drawcalls) + if(!QProcess::startDetached(RGPPath, QStringList() << QString(filename) << portStr)) { - for(const APIEvent &ev : draw.events) - { - if(ev.chunkIndex == 0 || ev.chunkIndex >= file.chunks.size()) - continue; - - const SDChunk *chunk = file.chunks[ev.chunkIndex]; - - if(eventNames.contains(chunk->name, Qt::CaseSensitive)) - { - m_Event2RGP[ev.eventId] = (uint32_t)m_RGP2Event.size(); - m_RGP2Event.push_back(ev.eventId); - } - } - - // if we have children, step into them first before going to our next sibling - if(!draw.children.empty()) - CreateRGPMapping(eventNames, draw.children); + RDDialog::critical(m_MainWindow, tr("Error opening RGP"), tr("Failed to launch RGP.")); + return false; } + + m_RGP = new RGPInterop(*this); + + return true; } rdcstr CaptureContext::GetResourceName(ResourceId id) diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 06b5bbdd6..935d1c2f1 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -116,19 +116,8 @@ public: const DrawcallDescription *CurDrawcall() override { return GetDrawcall(CurEvent()); } const DrawcallDescription *GetFirstDrawcall() override { return m_FirstDrawcall; }; const DrawcallDescription *GetLastDrawcall() override { return m_LastDrawcall; }; - void CreateRGPMapping(uint32_t version) override; - uint32_t GetRGPIdFromEventId(uint32_t eventId) override - { - if(eventId >= m_Event2RGP.size()) - return 0; - return m_Event2RGP[eventId]; - } - uint32_t GetEventIdFromRGPId(uint32_t RGPId) override - { - if(RGPId >= m_RGP2Event.size()) - return 0; - return m_RGP2Event[RGPId]; - } + bool OpenRGPProfile(const rdcstr &filename) override; + IRGPInterop *GetRGPInterop() override { return m_RGP; } const rdcarray &CurDrawcalls() override { return m_Drawcalls; } ResourceDescription *GetResource(ResourceId id) override { return m_Resources[id]; } const rdcarray &GetResources() override { return m_ResourceList; } @@ -261,9 +250,6 @@ private: rdcarray m_DebugMessages; int m_UnreadMessageCount = 0; - void CreateRGPMapping(const QStringList &eventNames, - const rdcarray &drawcalls); - bool PassEquivalent(const DrawcallDescription &a, const DrawcallDescription &b); bool ContainsMarker(const rdcarray &m_Drawcalls); void AddFakeProfileMarkers(); @@ -318,8 +304,7 @@ private: DrawcallDescription *m_FirstDrawcall = NULL; DrawcallDescription *m_LastDrawcall = NULL; - rdcarray m_Event2RGP; - rdcarray m_RGP2Event; + IRGPInterop *m_RGP = NULL; QMap m_Textures; rdcarray m_TextureList; diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index 6986f288a..33541c8ac 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -231,6 +231,8 @@ DECLARE_REFLECTION_STRUCT(BugReport); CONFIG_SETTING_VAL(public, QDateTime, rdcdatetime, DegradedCapture_LastUpdate, \ rdcdatetime(2015, 01, 01)) \ \ + CONFIG_SETTING_VAL(public, QString, rdcstr, ExternalTool_RadeonGPUProfiler, "") \ + \ CONFIG_SETTING_VAL(public, bool, bool, Tips_HasSeenFirst, false) \ \ CONFIG_SETTING_VAL(public, bool, bool, AllowGlobalHook, false) \ @@ -520,6 +522,10 @@ For more information about some of these settings that are user-facing see A date containing the last time that the user was warned about captures being loaded in degraded support. This prevents the user being spammed if their hardware is low spec. +.. data:: ExternalTool_RadeonGPUProfiler + + The path to the executable of the external Radeon GPU Profiler tool. + .. data:: Tips_HasSeenFirst ``True`` if the user has seen the first tip, which should always be shown first before diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index a1c79999f..8e0e46361 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -978,6 +978,36 @@ struct EventBookmark DECLARE_REFLECTION_STRUCT(EventBookmark); +struct IRGPInterop +{ + DOCUMENT(R"(Return true if the given :data:`eventId ` has and +equivalent in RGP. + +:param int eventId: The :data:`eventId ` to query for. +:return: ``True`` if there is an equivalent. This only confirms the equivalent exists, not that it + will be selectable in all cases. +:rtype: bool +)"); + virtual bool HasRGPEvent(uint32_t eventId) = 0; + + DOCUMENT(R"(Select the given :data:`eventId ` equivalent in RGP. + +:param int eventId: The :data:`eventId ` to query for. +:return: ``True`` if the selection request succeeded. This only confirms the request was sent, not + that the event was selected in RGP. +:rtype: bool +)"); + virtual bool SelectRGPEvent(uint32_t eventId) = 0; + + DOCUMENT(""); + virtual ~IRGPInterop() = default; + +protected: + IRGPInterop() = default; +}; + +DECLARE_REFLECTION_STRUCT(IRGPInterop); + DOCUMENT("The capture context that the python script is running in.") struct ICaptureContext { @@ -1318,30 +1348,36 @@ considered out of date )"); virtual const DrawcallDescription *GetDrawcall(uint32_t eventId) = 0; - DOCUMENT(R"(Creates a mapping from eventId to/from RGP linearId for the current capture. + DOCUMENT(R"(Sets the path to the RGP profile to use with :meth:`GetRGPInterop`, launches RGP and +opens an interop connection. This function will block (with a progress dialog) until either an +error is encountered or else the connection is successfully established. -Does nothing if no capture is loaded. +This could be newly created, or extracted from an embedded section in the RDC. -:param int version: The version of mapping to use, determining which events are counted. +The connection is automatically closed when the capture is closed. If OpenRGPProfile is called +again, any previous connection will be closed. + +:param str filename: The filename of the extracted temporary RGP capture on disk. +:return: Whether RGP launched successfully. +:rtype: ``bool`` )"); - virtual void CreateRGPMapping(uint32_t version) = 0; + virtual bool OpenRGPProfile(const rdcstr &filename) = 0; - DOCUMENT(R"(Get the RGP linearId from an :data:`eventId `. + DOCUMENT(R"(Returns the current interop handle for RGP. -:param int eventId: The :data:`eventId ` to query for. -:return: The linearId, or ``0`` if no linearId corresponds to this event. -:rtype: int +This may return ``None`` in several cases: + +- if there is no capture loaded +- if no RGP profile has been associated with the current capture yet (See :meth:`OpenRGPProfile`) +- if RGP failed to launch or connect. + +The handle returned is invalidated when the capture is closed, or if :meth:`OpenRGPProfile` is +called. + +:return: The RGP interop connection handle. +:rtype: RGPInterop )"); - virtual uint32_t GetRGPIdFromEventId(uint32_t eventId) = 0; - - DOCUMENT(R"(Get the :data:`eventId ` from an RGP linearId. - -:param int RGPId: The RGP linearId to query for. -:return: The :data:`eventId `, or ``0`` if no eventId can be found - corresponding to this linearId. -:rtype: int -)"); - virtual uint32_t GetEventIdFromRGPId(uint32_t RGPId) = 0; + virtual IRGPInterop *GetRGPInterop() = 0; DOCUMENT(R"(Retrieve the :class:`~renderdoc.SDFile` for the currently open capture. diff --git a/qrenderdoc/Code/RGPInterop.cpp b/qrenderdoc/Code/RGPInterop.cpp new file mode 100644 index 000000000..2e17e02b7 --- /dev/null +++ b/qrenderdoc/Code/RGPInterop.cpp @@ -0,0 +1,447 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "RGPInterop.h" +#include +#include +#include + +bool RGPInterop::RGPSupportsInterop(const QString &RGPPath) +{ + uint32_t majorVersion = 0; + uint32_t minorVersion = 0; + + const char *searchString = "RGPVersion="; + const int searchStringLength = (int)strlen(searchString); + + // look for an embedded string in the exe + QFile f(RGPPath); + if(f.open(QIODevice::ReadOnly)) + { + QByteArray contents = f.readAll(); + + int search = 0; + do + { + int needle = contents.indexOf("RGPVersion=", search); + + if(needle == -1) + break; + + search = needle + searchStringLength; + + // bail if there isn't enough room for the string plus X.Y + if(contents.size() - needle < searchStringLength + 3) + break; + + // get the major version number + const char *major = contents.data() + search; + + const char *sep = major; + + // find the separator + while(*sep >= '0' && *sep <= '9') + sep++; + + // get the minor version number + const char *minor = sep + 1; + + const char *end = minor; + + // find the end + while(*end >= '0' && *end <= '9') + end++; + + // convert the strings to integers + QByteArray majorStr(major, sep - major); + QByteArray minorStr(minor, end - minor); + + bool ok = false; + majorVersion = majorStr.toUInt(&ok); + + if(!ok) + { + majorVersion = 0; + continue; + } + + minorVersion = minorStr.toUInt(&ok); + + if(!ok) + { + majorVersion = minorVersion = 0; + continue; + } + + // found the version + break; + + } while(search >= 0); + } + + // interop supported in RGP V1.2 and higher + if(majorVersion > 1 || (majorVersion == 1 && minorVersion > 1)) + { + return true; + } + + return false; +} + +template <> +std::string DoStringise(const RGPCommand &el) +{ + BEGIN_ENUM_STRINGISE(RGPCommand); + { + STRINGISE_ENUM_CLASS_NAMED(Initialize, "initialize"); + STRINGISE_ENUM_CLASS_NAMED(SetEvent, "set_event"); + STRINGISE_ENUM_CLASS_NAMED(Terminate, "terminate"); + } + END_ENUM_STRINGISE(); +} + +RGPInterop::RGPInterop(ICaptureContext &ctx) : m_Ctx(ctx) +{ + m_Server = new QTcpServer(NULL); + m_Server->listen(QHostAddress::Any, Port); + + QObject::connect(m_Server, &QTcpServer::newConnection, [this]() { + if(m_Socket == NULL) + { + m_Socket = m_Server->nextPendingConnection(); + ConnectionEstablished(); + } + else + { + // close any other connections while we already have one + delete m_Server->nextPendingConnection(); + } + }); +} + +RGPInterop::~RGPInterop() +{ + RGPInteropTerminate terminate; + QString encoded = EncodeCommand(RGPCommand::Terminate, terminate.toParams(m_Version)); + + if(m_Socket) + { + m_Socket->write(encoded.trimmed().toUtf8().data()); + m_Socket->waitForBytesWritten(); + } + + m_Server->close(); + delete m_Server; +} + +void RGPInterop::InitializeRGP() +{ + RGPInteropInit init; + + init.interop_version = 1; + init.interop_name = lit("RenderDoc"); + + QString encoded = EncodeCommand(RGPCommand::Initialize, init.toParams(m_Version)); + + if(m_Socket) + { + m_Socket->write(encoded.trimmed().toUtf8().data()); + } +} + +bool RGPInterop::HasRGPEvent(uint32_t eventId) +{ + if(m_Version == 0) + return false; + + if(m_Socket == NULL) + return false; + + return m_Event2RGP[eventId].interoplinearid != 0; +} + +bool RGPInterop::SelectRGPEvent(uint32_t eventId) +{ + if(m_Version == 0) + return false; + + RGPInteropEvent ev = m_Event2RGP[eventId]; + + if(ev.interoplinearid == 0) + return false; + + QString encoded = EncodeCommand(RGPCommand::SetEvent, ev.toParams(m_Version)); + + if(m_Socket) + { + m_Socket->write(encoded.trimmed().toUtf8().data()); + return true; + } + + return false; +} + +void RGPInterop::EventSelected(RGPInteropEvent event) +{ + uint32_t eventId = m_RGP2Event[event.interoplinearid]; + + if(eventId == 0) + { + qWarning() << "RGP Event " << event.interoplinearid << event.cmdbufid << event.eventname + << " did not correspond to a known eventId"; + return; + } + + const DrawcallDescription *draw = m_Ctx.GetDrawcall(eventId); + + if(draw && QString(draw->name) != event.eventname) + qWarning() << "Drawcall name mismatch. Expected " << event.eventname << " but got " + << QString(draw->name); + + m_Ctx.SetEventID({}, eventId, eventId); + + m_Ctx.GetMainWindow()->Widget()->activateWindow(); + m_Ctx.GetMainWindow()->Widget()->raise(); +} + +void RGPInterop::ConnectionEstablished() +{ + QObject::connect(m_Socket, &QAbstractSocket::disconnected, [this]() { + m_Socket->deleteLater(); + m_Socket = NULL; + }); + + // initial handshake and protocol version + InitializeRGP(); + + // TODO: negotiate mapping version + uint32_t version = 1; + CreateMapping(version); + + // add a handler that appends all data to the read buffer and processes each time more comes in. + QObject::connect(m_Socket, &QIODevice::readyRead, [this]() { + // append all available data + m_ReadBuffer += m_Socket->readAll(); + + // process the read buffer + ProcessReadBuffer(); + }); +} + +void RGPInterop::CreateMapping(const rdcarray &drawcalls) +{ + const SDFile &file = m_Ctx.GetStructuredFile(); + + for(const DrawcallDescription &draw : drawcalls) + { + for(const APIEvent &ev : draw.events) + { + if(ev.chunkIndex == 0 || ev.chunkIndex >= file.chunks.size()) + continue; + + const SDChunk *chunk = file.chunks[ev.chunkIndex]; + + if(m_EventNames.contains(chunk->name, Qt::CaseSensitive)) + { + m_Event2RGP[ev.eventId].interoplinearid = (uint32_t)m_RGP2Event.size(); + if(ev.eventId == draw.eventId) + m_Event2RGP[ev.eventId].eventname = draw.name; + else + m_Event2RGP[ev.eventId].eventname = chunk->name; + + m_RGP2Event.push_back(ev.eventId); + } + } + + // if we have children, step into them first before going to our next sibling + if(!draw.children.empty()) + CreateMapping(draw.children); + } +} + +void RGPInterop::CreateMapping(uint32_t version) +{ + m_Version = version; + + if(m_Ctx.APIProps().pipelineType == GraphicsAPI::Vulkan) + { + if(version == 1) + { + m_EventNames << lit("vkCmdDispatch") << lit("vkCmdDraw") << lit("vkCmdDrawIndexed"); + } + } + else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::D3D12) + { + // these names must match those in DoStringise(const D3D12Chunk &el) for the chunks + + if(version == 1) + { + m_EventNames << lit("ID3D12GraphicsCommandList::Dispatch") + << lit("ID3D12GraphicsCommandList::DrawInstanced") + << lit("ID3D12GraphicsCommandList::DrawIndexedInstanced"); + } + } + + // if we don't have any event names, this API doesn't have a mapping or this was an unrecognised + // version. + if(m_EventNames.isEmpty()) + return; + + m_Event2RGP.resize(m_Ctx.GetLastDrawcall()->eventId + 1); + + // linearId 0 is invalid, so map to eventId 0. + // the first real event will be linearId 1 + m_RGP2Event.push_back(0); + + CreateMapping(m_Ctx.CurDrawcalls()); +} + +QString RGPInterop::EncodeCommand(RGPCommand command, QVariantList params) +{ + QString ret; + + QString cmd = ToQStr(command); + + ret += lit("command=%1\n").arg(cmd); + + // iterate params in pair, name and value + for(int i = 0; i + 1 < params.count(); i += 2) + ret += QFormatStr("%1.%2=%3\n").arg(cmd).arg(params[i].toString()).arg(params[i + 1].toString()); + + ret += lit("endcommand=%1\n").arg(cmd); + + return ret; +} + +bool RGPInterop::DecodeCommand(QString command) +{ + QStringList lines = command.trimmed().split(QLatin1Char('\n')); + + if(lines[0].indexOf(lit("command=")) != 0 || lines.last().indexOf(lit("endcommand=")) != 0) + { + qWarning() << "Malformed RGP command:\n" << command; + return false; + } + + QString commandName = lines[0].split(QLatin1Char('='))[1]; + + if(lines.last().split(QLatin1Char('='))[1] != commandName) + { + qWarning() << "Mismatch between command and endcommand:\n" << command; + return false; + } + + lines.pop_front(); + lines.pop_back(); + + QVariantList params; + + QString prefix = commandName + lit("."); + + for(QString ¶m : lines) + { + int eq = param.indexOf(QLatin1Char('=')); + + if(eq < 0) + { + qWarning() << "Malformed param: " << param; + continue; + } + + QString key = param.left(eq); + QString value = param.mid(eq + 1); + + if(!key.startsWith(prefix)) + { + qWarning() << "Malformed param key for" << commandName << ": " << key; + continue; + } + + key = key.mid(prefix.count()); + + params << key << value; + } + + if(commandName == ToQStr(RGPCommand::SetEvent)) + { + RGPInteropEvent ev; + ev.fromParams(m_Version, params); + + EventSelected(ev); + + return true; + } + else if(commandName == ToQStr(RGPCommand::Initialize)) + { + RGPInteropInit init; + init.fromParams(m_Version, params); + + // TODO: decode the params here. This will contain the interop + // version and the name of the tool connected to RenderDoc + + return true; + } + else if(commandName == ToQStr(RGPCommand::Terminate)) + { + // RGP has shut down so disconnect the socket etc + emit m_Socket->disconnected(); + return true; + } + else + { + qWarning() << "Unrecognised command: " << commandName; + } + + return false; +} + +void RGPInterop::ProcessReadBuffer() +{ + // we might have partial data, so wait until we have a full command + do + { + int idx = m_ReadBuffer.indexOf("endcommand="); + + // if we don't have endcommand= yet, we don't have a full command + if(idx < 0) + return; + + idx = m_ReadBuffer.indexOf('\n', idx); + + // also break if we don't have the full line yet including newline. + if(idx < 0) + return; + + // extract the command and decode as UTF-8 + QString command = QString::fromUtf8(m_ReadBuffer.data(), idx + 1); + + // remove the command from our buffer, to retain any partial subsequent command we might have + m_ReadBuffer.remove(0, idx + 1); + + // process this command + DecodeCommand(command); + + // loop again - we might have read multiple commands + } while(true); +} diff --git a/qrenderdoc/Code/RGPInterop.h b/qrenderdoc/Code/RGPInterop.h new file mode 100644 index 000000000..229bb5732 --- /dev/null +++ b/qrenderdoc/Code/RGPInterop.h @@ -0,0 +1,144 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "Code/QRDUtils.h" +#include "renderdoc_replay.h" + +class QTcpServer; +class QTcpSocket; + +// macros to help define encode/decode functions + +#define VARIANT_ENCODE(p) lit(#p), p +#define VARIANT_DECODE(p) \ + if(paramName == lit(#p)) \ + { \ + p = paramValue.value(); \ + continue; \ + } + +enum class RGPCommand +{ + Initialize, + SetEvent, + Terminate, +}; + +struct RGPInteropInit +{ + int32_t interop_version = 0; + QString interop_name = QStringLiteral("RenderDoc"); + + QVariantList toParams(uint32_t version) const + { + return {VARIANT_ENCODE(interop_version), VARIANT_ENCODE(interop_name)}; + } + + void fromParams(uint32_t version, QVariantList list) + { + for(int i = 0; i + 1 < list.size(); i += 2) + { + QString paramName = list[i].toString(); + QVariant paramValue = list[i + 1]; + VARIANT_DECODE(interop_version); + VARIANT_DECODE(interop_name); + + qWarning() << "Unrecognised param" << paramName; + } + } +}; + +struct RGPInteropEvent +{ + uint32_t interoplinearid = 0; + uint32_t cmdbufid = 0; + QString eventname; + + QVariantList toParams(uint32_t version) const + { + return {VARIANT_ENCODE(interoplinearid), VARIANT_ENCODE(cmdbufid), VARIANT_ENCODE(eventname)}; + } + + void fromParams(uint32_t version, QVariantList list) + { + for(int i = 0; i + 1 < list.size(); i += 2) + { + QString paramName = list[i].toString(); + QVariant paramValue = list[i + 1]; + VARIANT_DECODE(interoplinearid); + VARIANT_DECODE(cmdbufid); + VARIANT_DECODE(eventname); + + qWarning() << "Unrecognised param" << paramName; + } + } +}; + +struct RGPInteropTerminate +{ + QVariantList toParams(uint32_t version) const { return QVariantList(); } +}; + +class RGPInterop : public IRGPInterop +{ +public: + static const int Port = 29000; + + RGPInterop(ICaptureContext &ctx); + ~RGPInterop(); + + virtual bool HasRGPEvent(uint32_t eventId) override; + virtual bool SelectRGPEvent(uint32_t eventId) override; + + static bool RGPSupportsInterop(const QString &pPath); + +private: + void InitializeRGP(); + void ConnectionEstablished(); + + void CreateMapping(const rdcarray &drawcalls); + void CreateMapping(uint32_t version); + + void EventSelected(RGPInteropEvent event); + + uint32_t m_Version = 0; + + QTcpServer *m_Server = NULL; + QTcpSocket *m_Socket = NULL; + + QByteArray m_ReadBuffer; + + ICaptureContext &m_Ctx; + QStringList m_EventNames; + rdcarray m_Event2RGP; + rdcarray m_RGP2Event; + QString EncodeCommand(RGPCommand command, QVariantList params); + bool DecodeCommand(QString command); + void ProcessReadBuffer(); +}; \ No newline at end of file diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp index 161b306c7..31e8121c7 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp @@ -155,6 +155,8 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) ui->deleteDisasm->setEnabled(false); + ui->ExternalTool_RadeonGPUProfiler->setText(m_Ctx.Config().ExternalTool_RadeonGPUProfiler); + ui->Android_SDKPath->setText(m_Ctx.Config().Android_SDKPath); ui->Android_JDKPath->setText(m_Ctx.Config().Android_JDKPath); ui->Android_MaxConnectTimeout->setValue(m_Ctx.Config().Android_MaxConnectTimeout); @@ -245,6 +247,23 @@ SettingsDialog::~SettingsDialog() delete ui; } +void SettingsDialog::focusItem(QString item) +{ + for(int i = 0; i < ui->tabWidget->count(); i++) + { + QWidget *w = ui->tabWidget->widget(i)->findChild(item); + + if(w) + { + ui->tabWidget->setCurrentIndex(i); + w->setFocus(Qt::MouseFocusReason); + return; + } + } + + qCritical() << "Couldn't find" << item << "to focus on settings dialog"; +} + void SettingsDialog::on_pages_itemSelectionChanged() { QList sel = ui->pages->selectedItems(); @@ -423,6 +442,29 @@ void SettingsDialog::on_chooseSearchPaths_clicked() list.getItems().join(QLatin1Char(';'))); } +void SettingsDialog::on_ExternalTool_RadeonGPUProfiler_textEdited(const QString &rgp) +{ + if(QFileInfo::exists(rgp) || rgp.isEmpty()) + m_Ctx.Config().ExternalTool_RadeonGPUProfiler = rgp; + + m_Ctx.Config().Save(); +} + +void SettingsDialog::on_browseRGPPath_clicked() +{ + QString rgp = RDDialog::getExecutableFileName( + this, tr("Locate RGP executable"), + QFileInfo(m_Ctx.Config().ExternalTool_RadeonGPUProfiler).absoluteDir().path()); + + if(!rgp.isEmpty()) + { + ui->ExternalTool_RadeonGPUProfiler->setText(rgp); + m_Ctx.Config().ExternalTool_RadeonGPUProfiler = rgp; + } + + m_Ctx.Config().Save(); +} + // texture viewer void SettingsDialog::on_TextureViewer_PerTexSettings_toggled(bool checked) { diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.h b/qrenderdoc/Windows/Dialogs/SettingsDialog.h index a20c2eeae..04f0821ff 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.h +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.h @@ -44,6 +44,8 @@ public: explicit SettingsDialog(ICaptureContext &ctx, QWidget *parent = 0); ~SettingsDialog(); + void focusItem(QString item); + private slots: // automatic slots @@ -66,6 +68,8 @@ private slots: // core void on_chooseSearchPaths_clicked(); + void on_ExternalTool_RadeonGPUProfiler_textEdited(const QString &rgp); + void on_browseRGPPath_clicked(); // texture viewer void on_TextureViewer_PerTexSettings_toggled(bool checked); diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui index 43bcb2483..e507eac0f 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui @@ -476,7 +476,34 @@ e.g. a value of 5 means 0.123456789 will display as 0.12345 - + + + + Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. + + + Path to Radeon GPU Profiler executable + + + + + + + Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. + + + + + + + Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. + + + Browse + + + + Qt::Vertical diff --git a/qrenderdoc/Windows/EventBrowser.cpp b/qrenderdoc/Windows/EventBrowser.cpp index 439b0f089..db79787b7 100644 --- a/qrenderdoc/Windows/EventBrowser.cpp +++ b/qrenderdoc/Windows/EventBrowser.cpp @@ -927,6 +927,8 @@ void EventBrowser::events_contextMenu(const QPoint &pos) QAction expandAll(tr("&Expand All"), this); QAction collapseAll(tr("&Collapse All"), this); QAction selectCols(tr("&Select Columns..."), this); + QAction rgpSelect(tr("Select &RGP Event"), this); + rgpSelect.setIcon(Icons::connect()); contextMenu.addAction(&expandAll); contextMenu.addAction(&collapseAll); @@ -947,6 +949,14 @@ void EventBrowser::events_contextMenu(const QPoint &pos) QObject::connect(&selectCols, &QAction::triggered, this, &EventBrowser::on_colSelect_clicked); + IRGPInterop *rgp = m_Ctx.GetRGPInterop(); + if(rgp && rgp->HasRGPEvent(m_Ctx.CurEvent())) + { + contextMenu.addAction(&rgpSelect); + QObject::connect(&rgpSelect, &QAction::triggered, + [this, rgp]() { rgp->SelectRGPEvent(m_Ctx.CurEvent()); }); + } + RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos)); } diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 5c8dec5f4..c33569a53 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -286,6 +286,8 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai }); ui->action_Start_Replay_Loop->setEnabled(false); + ui->action_Open_RGP_Profile->setEnabled(false); + ui->action_Create_RGP_Profile->setEnabled(false); ui->action_Resolve_Symbols->setEnabled(false); ui->action_Resolve_Symbols->setText(tr("Resolve Symbols")); @@ -1817,6 +1819,9 @@ void MainWindow::OnCaptureLoaded() ui->action_Recompress_Capture->setEnabled(true); ui->action_Start_Replay_Loop->setEnabled(true); + ui->action_Open_RGP_Profile->setEnabled( + m_Ctx.Replay().GetCaptureAccess()->FindSectionByType(SectionType::AMDRGPProfile) >= 0); + ui->action_Create_RGP_Profile->setEnabled(m_Ctx.APIProps().RGPCapture && m_Ctx.IsCaptureLocal()); setCaptureHasErrors(!m_Ctx.DebugMessages().empty()); @@ -1847,6 +1852,8 @@ void MainWindow::OnCaptureClosed() ui->menu_Export_As->setEnabled(false); ui->action_Start_Replay_Loop->setEnabled(false); + ui->action_Open_RGP_Profile->setEnabled(false); + ui->action_Create_RGP_Profile->setEnabled(false); contextChooser->setEnabled(true); @@ -2246,6 +2253,97 @@ void MainWindow::on_action_Start_Replay_Loop_triggered() m_Ctx.Replay().CancelReplayLoop(); } +void MainWindow::on_action_Open_RGP_Profile_triggered() +{ + if(!m_Ctx.IsCaptureLoaded()) + return; + + int idx = m_Ctx.Replay().GetCaptureAccess()->FindSectionByType(SectionType::AMDRGPProfile); + + if(idx < 0) + return; + + QString path = QDir::temp().absoluteFilePath(lit("renderdoc_extracted.rgp")); + + QFile f(path); + if(f.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + bytebuf buf = m_Ctx.Replay().GetCaptureAccess()->GetSectionContents(idx); + + f.write((const char *)buf.data(), (qint64)buf.size()); + f.flush(); + } + else + { + qCritical() << "Couldn't open temporary file " << path << " for write"; + return; + } + + m_Ctx.OpenRGPProfile(path); +} + +void MainWindow::on_action_Create_RGP_Profile_triggered() +{ + if(!m_Ctx.IsCaptureLoaded()) + return; + + if(m_Ctx.Replay().GetCaptureAccess()->FindSectionByType(SectionType::AMDRGPProfile) >= 0) + { + QMessageBox::StandardButton res = RDDialog::question( + this, tr("Existing RGP profile"), tr("Capture already contains an RGP profile. Overwrite?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res != QMessageBox::Yes) + return; + } + + QDialog popup; + popup.setWindowFlags(popup.windowFlags() & ~Qt::WindowContextHelpButtonHint); + popup.setWindowIcon(windowIcon()); + popup.resize(128, 16); + popup.setWindowTitle(tr("Making RGP Profile from %1").arg(m_Ctx.GetCaptureFilename())); + + WindowingData winData = m_Ctx.CreateWindowingData(popup.winId()); + + rdcstr path; + + m_Ctx.Replay().AsyncInvoke([winData, &popup, &path](IReplayController *r) { + path = r->CreateRGPProfile(winData); + GUIInvoke::call([&popup]() { popup.close(); }); + }); + + RDDialog::show(&popup); + + qInfo() << "RGP Capture created at" << QString(path); + m_Ctx.OpenRGPProfile(path); + + if(!path.isEmpty()) + { + QFile f(path); + if(f.open(QIODevice::ReadOnly)) + { + QByteArray contents = f.readAll(); + + bytebuf buf; + buf.resize(contents.count()); + memcpy(&buf[0], contents.data(), contents.count()); + + SectionProperties props; + props.type = SectionType::AMDRGPProfile; + props.version = 1; + props.flags = SectionFlags::ZstdCompressed; + + m_Ctx.Replay().GetCaptureAccess()->WriteSection(props, buf); + + ui->action_Open_RGP_Profile->setEnabled(true); + } + else + { + qCritical() << "Couldn't read from temporary RGP capture at " << QString(path); + } + } +} + void MainWindow::on_action_Attach_to_Running_Instance_triggered() { on_action_Manage_Remote_Servers_triggered(); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index a4f99647a..bfb8d2360 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -127,6 +127,8 @@ private slots: void on_action_Resolve_Symbols_triggered(); void on_action_Recompress_Capture_triggered(); void on_action_Start_Replay_Loop_triggered(); + void on_action_Open_RGP_Profile_triggered(); + void on_action_Create_RGP_Profile_triggered(); void on_action_Attach_to_Running_Instance_triggered(); void on_action_Manage_Remote_Servers_triggered(); void on_action_Settings_triggered(); diff --git a/qrenderdoc/Windows/MainWindow.ui b/qrenderdoc/Windows/MainWindow.ui index 00e56fd9b..036445478 100644 --- a/qrenderdoc/Windows/MainWindow.ui +++ b/qrenderdoc/Windows/MainWindow.ui @@ -51,7 +51,10 @@ + + + @@ -453,6 +456,16 @@ Check for updates + + + Create new RGP Profile + + + + + Open RGP Profile + + diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index b5ddd1bbe..5626ee38e 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -99,18 +99,11 @@ struct CaptureContextInvoker : ICaptureContext { return m_Ctx.GetDrawcall(eventId); } - virtual void CreateRGPMapping(uint32_t version) override + virtual bool OpenRGPProfile(const rdcstr &filename) override { - return m_Ctx.CreateRGPMapping(version); - } - virtual uint32_t GetRGPIdFromEventId(uint32_t eventId) override - { - return m_Ctx.GetRGPIdFromEventId(eventId); - } - virtual uint32_t GetEventIdFromRGPId(uint32_t RGPId) override - { - return m_Ctx.GetEventIdFromRGPId(RGPId); + return m_Ctx.OpenRGPProfile(filename); } + virtual IRGPInterop *GetRGPInterop() override { return m_Ctx.GetRGPInterop(); } virtual const SDFile &GetStructuredFile() override { return m_Ctx.GetStructuredFile(); } virtual WindowingSystem CurWindowingSystem() override { return m_Ctx.CurWindowingSystem(); } virtual WindowingData CreateWindowingData(uintptr_t winId) override diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 4eb7f762b..76f9ce490 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -162,6 +162,7 @@ SOURCES += Code/qrenderdoc.cpp \ Code/QRDUtils.cpp \ Code/FormatElement.cpp \ Code/Resources.cpp \ + Code/RGPInterop.cpp \ Code/pyrenderdoc/PythonContext.cpp \ Code/Interface/QRDInterface.cpp \ Code/Interface/Analytics.cpp \ @@ -234,6 +235,7 @@ HEADERS += Code/CaptureContext.h \ Code/ScintillaSyntax.h \ Code/QRDUtils.h \ Code/Resources.h \ + Code/RGPInterop.h \ Code/pyrenderdoc/PythonContext.h \ Code/pyrenderdoc/pyconversion.h \ Code/pyrenderdoc/interface_check.h \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index d75392b7e..763f0f5fb 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -587,6 +587,7 @@ + @@ -949,6 +950,7 @@ + diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 76e581928..16ff574f0 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -705,6 +705,7 @@ Code\Interface + @@ -1055,6 +1056,7 @@ Code\pyrenderdoc +