Add socket-based interop with RGP and profile embedding in captures

This commit is contained in:
baldurk
2018-04-25 19:10:51 +01:00
parent 79637f6caa
commit 2d0f675ca8
17 changed files with 913 additions and 125 deletions
+53 -78
View File
@@ -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<DrawcallDescription> &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)
+3 -18
View File
@@ -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<DrawcallDescription> &CurDrawcalls() override { return m_Drawcalls; }
ResourceDescription *GetResource(ResourceId id) override { return m_Resources[id]; }
const rdcarray<ResourceDescription> &GetResources() override { return m_ResourceList; }
@@ -261,9 +250,6 @@ private:
rdcarray<DebugMessage> m_DebugMessages;
int m_UnreadMessageCount = 0;
void CreateRGPMapping(const QStringList &eventNames,
const rdcarray<DrawcallDescription> &drawcalls);
bool PassEquivalent(const DrawcallDescription &a, const DrawcallDescription &b);
bool ContainsMarker(const rdcarray<DrawcallDescription> &m_Drawcalls);
void AddFakeProfileMarkers();
@@ -318,8 +304,7 @@ private:
DrawcallDescription *m_FirstDrawcall = NULL;
DrawcallDescription *m_LastDrawcall = NULL;
rdcarray<uint32_t> m_Event2RGP;
rdcarray<uint32_t> m_RGP2Event;
IRGPInterop *m_RGP = NULL;
QMap<ResourceId, TextureDescription *> m_Textures;
rdcarray<TextureDescription> m_TextureList;
@@ -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
+54 -18
View File
@@ -978,6 +978,36 @@ struct EventBookmark
DECLARE_REFLECTION_STRUCT(EventBookmark);
struct IRGPInterop
{
DOCUMENT(R"(Return true if the given :data:`eventId <renderdoc.APIEvent.eventId>` has and
equivalent in RGP.
:param int eventId: The :data:`eventId <renderdoc.APIEvent.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 <renderdoc.APIEvent.eventId>` equivalent in RGP.
:param int eventId: The :data:`eventId <renderdoc.APIEvent.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 <renderdoc.APIEvent.eventId>`.
DOCUMENT(R"(Returns the current interop handle for RGP.
:param int eventId: The :data:`eventId <renderdoc.APIEvent.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 <renderdoc.APIEvent.eventId>` from an RGP linearId.
:param int RGPId: The RGP linearId to query for.
:return: The :data:`eventId <renderdoc.APIEvent.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.
+447
View File
@@ -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 <QApplication>
#include <QTcpServer>
#include <QTcpSocket>
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<DrawcallDescription> &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 &param : 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);
}
+144
View File
@@ -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 <QDebug>
#include <QStringList>
#include <QVariantList>
#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<decltype(p)>(); \
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<DrawcallDescription> &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<RGPInteropEvent> m_Event2RGP;
rdcarray<uint32_t> m_RGP2Event;
QString EncodeCommand(RGPCommand command, QVariantList params);
bool DecodeCommand(QString command);
void ProcessReadBuffer();
};
@@ -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<QWidget *>(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<QListWidgetItem *> 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)
{
@@ -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);
+28 -1
View File
@@ -476,7 +476,34 @@ e.g. a value of 5 means 0.123456789 will display as 0.12345</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_17">
<property name="toolTip">
<string>Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles.</string>
</property>
<property name="text">
<string>Path to Radeon GPU Profiler executable</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="ExternalTool_RadeonGPUProfiler">
<property name="toolTip">
<string>Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles.</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="browseRGPPath">
<property name="toolTip">
<string>Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles.</string>
</property>
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
+10
View File
@@ -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));
}
+98
View File
@@ -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();
+2
View File
@@ -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();
+13
View File
@@ -51,7 +51,10 @@
</property>
<addaction name="action_Resolve_Symbols"/>
<addaction name="action_Recompress_Capture"/>
<addaction name="separator"/>
<addaction name="action_Start_Replay_Loop"/>
<addaction name="action_Create_RGP_Profile"/>
<addaction name="action_Open_RGP_Profile"/>
<addaction name="separator"/>
<addaction name="action_Settings"/>
<addaction name="action_Manage_Remote_Servers"/>
@@ -453,6 +456,16 @@
<string>Check for updates</string>
</property>
</action>
<action name="action_Create_RGP_Profile">
<property name="text">
<string>Create new RGP Profile</string>
</property>
</action>
<action name="action_Open_RGP_Profile">
<property name="text">
<string>Open RGP Profile</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
+3 -10
View File
@@ -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
+2
View File
@@ -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 \
+2
View File
@@ -587,6 +587,7 @@
<ClCompile Include="Code\pyrenderdoc\PythonContext.cpp" />
<ClCompile Include="Code\QRDUtils.cpp" />
<ClCompile Include="Code\Resources.cpp" />
<ClCompile Include="Code\RGPInterop.cpp" />
<ClCompile Include="Code\ScintillaSyntax.cpp" />
<ClCompile Include="$(IntDir)generated\moc_RDStyle.cpp" />
<ClCompile Include="$(IntDir)generated\moc_RDTweakedNativeStyle.cpp" />
@@ -949,6 +950,7 @@
<ClInclude Include="$(IntDir)generated\ui_ThumbnailStrip.h" />
<ClInclude Include="$(IntDir)generated\ui_VirtualFileDialog.h" />
<ClInclude Include="$(IntDir)generated\ui_VulkanPipelineStateViewer.h" />
<ClInclude Include="Code\RGPInterop.h" />
<ClInclude Include="Resources\resource.h" />
<ClInclude Include="Code\CaptureContext.h" />
<ClInclude Include="Styles\StyleData.h" />
@@ -705,6 +705,7 @@
<ClCompile Include="Code\Interface\SPIRVDisassembler.cpp">
<Filter>Code\Interface</Filter>
</ClCompile>
<ClCompile Include="Code\RGPInterop.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="3rdparty\flowlayout\FlowLayout.h">
@@ -1055,6 +1056,7 @@
<ClInclude Include="Code\pyrenderdoc\interface_check.h">
<Filter>Code\pyrenderdoc</Filter>
</ClInclude>
<ClInclude Include="Code\RGPInterop.h" />
</ItemGroup>
<ItemGroup>
<None Include="Code\pyrenderdoc\pyconversion.h">