mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-01 04:10:58 +00:00
Add socket-based interop with RGP and profile embedding in captures
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 ¶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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user