From 94dfb9890b8d912c3bb2d1424da1d0302bcd8285 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 22 Oct 2018 16:50:51 +0100 Subject: [PATCH] Allow registering window, panel and context menu items with callbacks --- qrenderdoc/Code/CaptureContext.cpp | 302 +++++++++++++++++- qrenderdoc/Code/CaptureContext.h | 17 + qrenderdoc/Code/Interface/QRDInterface.h | 187 ++++++++++- qrenderdoc/Code/QRDUtils.h | 11 + qrenderdoc/Code/pyrenderdoc/PythonContext.cpp | 65 ++++ qrenderdoc/Code/pyrenderdoc/renderdoc.i | 9 + qrenderdoc/Windows/BufferViewer.cpp | 33 ++ qrenderdoc/Windows/BufferViewer.ui | 11 + qrenderdoc/Windows/EventBrowser.cpp | 14 + qrenderdoc/Windows/EventBrowser.ui | 11 + qrenderdoc/Windows/MainWindow.cpp | 43 +++ qrenderdoc/Windows/MainWindow.h | 4 + qrenderdoc/Windows/MainWindow.ui | 28 ++ .../D3D11PipelineStateViewer.cpp | 10 + .../PipelineState/D3D11PipelineStateViewer.ui | 67 ++-- .../D3D12PipelineStateViewer.cpp | 10 + .../PipelineState/D3D12PipelineStateViewer.ui | 63 +++- .../PipelineState/GLPipelineStateViewer.cpp | 10 + .../PipelineState/GLPipelineStateViewer.ui | 57 ++-- .../VulkanPipelineStateViewer.cpp | 10 + .../VulkanPipelineStateViewer.ui | 19 +- qrenderdoc/Windows/TextureViewer.cpp | 32 +- qrenderdoc/Windows/TextureViewer.h | 2 +- qrenderdoc/Windows/TextureViewer.ui | 15 +- 24 files changed, 963 insertions(+), 67 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 787e94ddf..ea2946447 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -35,6 +35,7 @@ #include #include #include +#include "Code/Resources.h" #include "Code/pyrenderdoc/PythonContext.h" #include "Windows/APIInspector.h" #include "Windows/BufferViewer.h" @@ -348,10 +349,309 @@ bool CaptureContext::LoadExtension(rdcstr name) } }); - m_MainWindow->CleanMenus(); + for(QAction *a : m_MainWindow->GetMenuActions()) + CleanMenu(a); + + m_RegisteredMenuItems.removeAll(NULL); return ret; } + +void CaptureContext::RegisterWindowMenu(WindowMenu base, const rdcarray &submenus, + ExtensionCallback callback) +{ + if(base == WindowMenu::Unknown) + { + qCritical() << "Can't register menu for WindowMenu::Unknown"; + return; + } + + if(submenus.isEmpty()) + { + qCritical() << "Empty list of submenus"; + return; + } + + if(base == WindowMenu::NewMenu && submenus.count() == 1) + { + qCritical() << "Need at least two items in submenus for WindowMenu::NewMenu"; + return; + } + + QMenu *menu = m_MainWindow->GetBaseMenu(base, submenus[0]); + + if(!menu) + { + qCritical() << "Couldn't get base menu"; + return; + } + + std::function slotcallback = [this, callback]() { callback(this, {}); }; + + // if it's a new menu, GetBaseMenu already did the work, so skip the 0th element of submenus + if(base == WindowMenu::NewMenu) + { + rdcarray items = submenus; + items.erase(0); + AddSortedMenuItem(menu, false, items, slotcallback); + } + else + { + AddSortedMenuItem(menu, false, submenus, slotcallback); + } +} + +void CaptureContext::RegisterPanelMenu(PanelMenu base, const rdcarray &submenus, + ExtensionCallback callback) +{ + if(base == PanelMenu::Unknown) + { + qCritical() << "Can't register menu for PanelMenu::Unknown"; + return; + } + + RegisteredMenuItem *item = new RegisteredMenuItem(); + item->panel = base; + item->submenus = submenus; + item->callback = callback; + m_RegisteredMenuItems.push_back(item); + m_PendingExtensionObjects.push_back(item); +} + +void CaptureContext::RegisterContextMenu(ContextMenu base, const rdcarray &submenus, + ExtensionCallback callback) +{ + if(base == ContextMenu::Unknown) + { + qCritical() << "Can't register menu for ContextMenu::Unknown"; + return; + } + + RegisteredMenuItem *item = new RegisteredMenuItem(); + item->context = base; + item->submenus = submenus; + item->callback = callback; + m_RegisteredMenuItems.push_back(item); + m_PendingExtensionObjects.push_back(item); +} + +void CaptureContext::MenuDisplaying(ContextMenu contextMenu, QMenu *menu, + const ExtensionCallbackData &data) +{ + ContextMenu contextMenuAlt = contextMenu; + + switch(contextMenu) + { + case ContextMenu::MeshPreview_GSOutVertex: + case ContextMenu::MeshPreview_VSInVertex: + case ContextMenu::MeshPreview_VSOutVertex: + contextMenuAlt = ContextMenu::MeshPreview_Vertex; + break; + case ContextMenu::TextureViewer_InputThumbnail: + case ContextMenu::TextureViewer_OutputThumbnail: + contextMenuAlt = ContextMenu::TextureViewer_Thumbnail; + break; + } + + for(RegisteredMenuItem *item : m_RegisteredMenuItems) + { + if(item->context == contextMenu || item->context == contextMenuAlt) + { + AddSortedMenuItem(menu, true, item->submenus, [this, item, data]() { + rdcarray> args; + + PythonContext::ConvertPyArgs(data, args); + + item->callback(this, args); + + PythonContext::FreePyArgs(args); + }); + } + } +} + +void CaptureContext::MenuDisplaying(PanelMenu panelMenu, QWidget *extensionButton, + const ExtensionCallbackData &data) +{ + QMenu contextMenu(m_MainWindow); + + for(RegisteredMenuItem *item : m_RegisteredMenuItems) + { + if(item->panel == panelMenu) + { + AddSortedMenuItem(&contextMenu, false, item->submenus, [this, item, data]() { + rdcarray> args; + + PythonContext::ConvertPyArgs(data, args); + + item->callback(this, args); + + PythonContext::FreePyArgs(args); + }); + } + } + + QAction emptyAction(tr("No extension commands configured"), m_MainWindow); + + if(contextMenu.isEmpty()) + { + contextMenu.addAction(&emptyAction); + QObject::connect(&emptyAction, &QAction::triggered, + [this]() { m_MainWindow->showExtensionManager(); }); + } + + RDDialog::show(&contextMenu, extensionButton->mapToGlobal(extensionButton->rect().bottomLeft())); +} + +void CaptureContext::AddSortedMenuItem(QMenu *menu, bool rootMenu, const rdcarray &items, + std::function callback) +{ + QAction *beforeAction = NULL; + bool addIcon = rootMenu; + + // if we're inserting into a pre-existing menu, try to find an __examples__ action which tells us + // where + // to be inserting + { + QList children = menu->actions(); + int idx = 0; + for(; idx < children.count(); idx++) + { + if(children[idx]->text() == lit("__extensions__")) + break; + } + + // if we found it, set beforeAction + if(idx < children.count()) + { + // search back to find the right place alphabetically to place this, if there are existing + // menus + + for(; idx > 0;) + { + // if the previous child is a separator, break, there are no more previous menus + if(children[idx - 1]->isSeparator()) + break; + + // if we should be sorted before the previous child, move backwards + if(children[idx - 1]->text() > items[0]) + { + idx--; + continue; + } + + // if we found the right place, break + break; + } + + beforeAction = children[idx]; + addIcon = true; + } + } + + for(int i = 0; i < items.count() - 1; i++) + { + QList children = menu->actions(); + + bool found = false; + + // see if this submenu already exists, if so just iterate down to it + for(QAction *a : children) + { + if(a->text() == items[i]) + { + menu = a->menu(); + found = true; + break; + } + } + + if(found) + { + addIcon = false; + beforeAction = NULL; + continue; + } + + // create the new submenu + QMenu *submenu = new QMenu(items[i], m_MainWindow); + + // if we don't have a beforeAction, find where to insert this to keep alphabetical order. + if(!beforeAction) + { + for(QAction *a : children) + { + if(submenu->menuAction()->text() < a->text()) + { + beforeAction = a; + break; + } + } + } + + if(beforeAction) + menu->insertMenu(beforeAction, submenu); + else + menu->addMenu(submenu); + + if(addIcon) + submenu->setIcon(Icons::plugin()); + + addIcon = false; + + beforeAction = NULL; + + menu = submenu; + } + + QAction *action = new QAction(m_MainWindow); + + action->setText(items.back()); + + QObject::connect(action, &QAction::triggered, callback); + + if(!beforeAction) + { + QList children = menu->actions(); + + for(QAction *a : children) + { + if(action->text() < a->text()) + { + beforeAction = a; + break; + } + } + } + + if(beforeAction) + menu->insertAction(beforeAction, action); + else + menu->addAction(action); + + if(addIcon) + action->setIcon(Icons::plugin()); + + m_PendingExtensionObjects.push_back(action); +} + +void CaptureContext::CleanMenu(QAction *action) +{ + QMenu *menu = action->menu(); + + // if this isn't a menu, nothing to do - return + if(!menu) + return; + + // first clean all our children + for(QAction *a : menu->actions()) + CleanMenu(a); + + // if we no longer have any children in this menu, delete it. + if(menu->actions().isEmpty()) + delete menu; +} + void CaptureContext::LoadCapture(const rdcstr &captureFile, const rdcstr &origFilename, bool temporary, bool local) { diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index c2df2521e..3754409c3 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -72,6 +72,17 @@ public: bool IsExtensionLoaded(rdcstr name) override; bool LoadExtension(rdcstr name) override; + void RegisterWindowMenu(WindowMenu base, const rdcarray &submenus, + ExtensionCallback callback) override; + void RegisterPanelMenu(PanelMenu base, const rdcarray &submenus, + ExtensionCallback callback) override; + void RegisterContextMenu(ContextMenu base, const rdcarray &submenus, + ExtensionCallback callback) override; + void MenuDisplaying(ContextMenu contextMenu, QMenu *menu, + const ExtensionCallbackData &data) override; + void MenuDisplaying(PanelMenu panelMenu, QWidget *extensionButton, + const ExtensionCallbackData &data) override; + ////////////////////////////////////////////////////////////////////////////// // Control functions @@ -277,6 +288,10 @@ private: void LoadCaptureThreaded(const QString &captureFile, const QString &origFilename, bool temporary, bool local); + void AddSortedMenuItem(QMenu *menu, bool rootMenu, const rdcarray &items, + std::function callback); + void CleanMenu(QAction *action); + uint32_t m_SelectedEventID = 0; uint32_t m_EventID = 0; @@ -341,6 +356,8 @@ private: QList m_PendingExtensionObjects; QMap> m_ExtensionObjects; + QList> m_RegisteredMenuItems; + // Windows MainWindow *m_MainWindow = NULL; EventBrowser *m_EventBrowser = NULL; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 1329be356..8c520aa89 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -29,6 +29,7 @@ // this is pre-declared as an opaque type as we only support converting to QWidget* via PySide class QWidget; +class QMenu; // we only support QVariant as an 'internal' interface, it's not exposed to python. However we need // to use it in constructors/operators so conditionally compile it rather than split small structs @@ -64,6 +65,13 @@ class QWidget; #include "PersistantConfig.h" #include "RemoteHost.h" +#ifdef RENDERDOC_QT_COMPAT + +typedef rdcarray > ExtensionCallbackData; +#define make_pyarg make_rdcpair + +#endif + struct ICaptureContext; DOCUMENT("Contains all of the settings that control how to capture an executable."); @@ -1022,6 +1030,111 @@ protected: DECLARE_REFLECTION_STRUCT(IRGPInterop); +DOCUMENT(R"(Specifies the base menu to add a menu item into. + +.. data:: File + + The menu item will be in a section between Open/Save/Close captures and Import/Export. + +.. data:: Window + + The menu item will be in a new section at the end of the menu. + +.. data:: Tools + + The menu item will be added to a new section above Settings. + +.. data:: NewMenu + + The menu item will be a root menu, placed between Tools and Help. + +.. data:: Help + + The menu item will be added after the error reporting item. +)"); +enum class WindowMenu +{ + Unknown, + File, + Window, + Tools, + NewMenu, + Help, +}; + +DOCUMENT(R"(Specifies the panel to add a menu item into. + +.. data:: EventBrowser + + The :class:`EventBrowser`. + +.. data:: PipelineStateViewer + + The :class:`PipelineStateViewer`. + +.. data:: MeshPreview + + The mesh previewing :class:`BufferViewer`. + +.. data:: TextureViewer + + The :class:`TextureViewer`. +)"); +enum class PanelMenu +{ + Unknown, + EventBrowser, + PipelineStateViewer, + MeshPreview, + TextureViewer, +}; + +DOCUMENT(R"(Specifies the panel to add a menu item into. + +.. data:: EventBrowser_Event + + Adds the item to the context menu for events in the :class:`EventBrowser`. + +.. data:: MeshPreview_Vertex + + Adds the item to the context menu for all vertices in the mesh previewing :class:`BufferViewer`. + +.. data:: MeshPreview_VSInVertex + + Adds the item to the context menu for vertex inputs in the mesh previewing :class:`BufferViewer`. + +.. data:: MeshPreview_VSOutVertex + + Adds the item to the context menu for VS output in the mesh previewing :class:`BufferViewer`. + +.. data:: MeshPreview_GSOutVertex + + Adds the item to the context menu for GS/Tess output in the mesh previewing :class:`BufferViewer`. + +.. data:: TextureViewer_Thumbnail + + Adds the item to the context menu for all thumbnails in the :class:`TextureViewer`. + +.. data:: TextureViewer_InputThumbnail + + Adds the item to the context menu for input thumbnails in the :class:`TextureViewer`. + +.. data:: TextureViewer_OutputThumbnail + + Adds the item to the context menu for output thumbnails in the :class:`TextureViewer`. +)"); +enum class ContextMenu +{ + Unknown, + EventBrowser_Event, + MeshPreview_Vertex, + MeshPreview_VSInVertex, + MeshPreview_VSOutVertex, + MeshPreview_GSOutVertex, + TextureViewer_Thumbnail, + TextureViewer_InputThumbnail, + TextureViewer_OutputThumbnail, +}; DOCUMENT("The metadata for an extension."); struct ExtensionMetadata @@ -1081,10 +1194,12 @@ struct ExtensionMetadata DECLARE_REFLECTION_STRUCT(ExtensionMetadata); +typedef struct _object PyObject; + DOCUMENT(R"(A manager for listing available and active extensions, as well as the interface for extensions to register hooks and additional functionality. -.. function:: ExtensionCallback(context) +.. function:: ExtensionCallback(context, data) Not a member function - the signature for any ``ExtensionCallback`` callbacks. @@ -1092,10 +1207,13 @@ extensions to register hooks and additional functionality. was registered. :param CaptureContext context: The current capture context. + :param dict data: Additional data for the call, as a dictionary with string keys. + Context-dependent based on what generated the callback )"); struct IExtensionManager { - typedef std::function ExtensionCallback; + typedef std::function > &data)> + ExtensionCallback; DOCUMENT(R"(Retrieve a list of installed extensions. @@ -1118,6 +1236,65 @@ struct IExtensionManager )"); virtual bool LoadExtension(rdcstr name) = 0; + DOCUMENT(R"(Register a new menu item in the main window's menus for an extension. + +.. note: + The intermediate submenu items will be created as needed, there's no need to register each item + in the hierarchy. Registering a menu item and then registering a child is undefined, and the item + with a child may not receive callbacks at the correct times. + +:param WindowMenu base: The base menu to add the item to. +:param list submenus: A list of strings containing the submenus to add before the item. The last + string will be the name of the menu item itself. Must contain at least one entry, or two entries + if ``base`` is :data:`WindowMenu.NewMenu`. +:param callback: The function to callback when the menu item is selected. +:type method: :func:`ExtensionCallback` +)"); + virtual void RegisterWindowMenu(WindowMenu base, const rdcarray &submenus, + ExtensionCallback callback) = 0; + + DOCUMENT(R"(Register a menu item in a panel for an extension. + +.. note: + The intermediate submenu items will be created as needed, there's no need to register each item + in the hierarchy. Registering a menu item and then registering a child is undefined, and the item + with a child may not receive callbacks at the correct times. + +:param PanelMenu base: The panel to add the item to. +:param list submenus: A list of strings containing the submenus to add before the item. The last + string will be the name of the menu item itself. Must contain at least one entry. +:param callback: The function to callback when the menu item is selected. +:type method: :func:`ExtensionCallback` +)"); + virtual void RegisterPanelMenu(PanelMenu base, const rdcarray &submenus, + ExtensionCallback callback) = 0; + + DOCUMENT(R"(Register a context menu item in a panel for an extension. + +.. note: + The intermediate submenu items will be created as needed, there's no need to register each item + in the hierarchy. Registering a menu item and then registering a child is undefined, and the item + with a child may not receive callbacks at the correct times. + +:param ContextMenu base: The panel to add the item to. +:param list submenus: A list of strings containing the submenus to add before the item. The last + string will be the name of the menu item itself. Must contain at least one entry. +:param callback: The function to callback when the menu item is selected. +:type method: :func:`ExtensionCallback` +)"); + virtual void RegisterContextMenu(ContextMenu base, const rdcarray &submenus, + ExtensionCallback callback) = 0; + +#if !defined(SWIG) && !defined(SWIG_GENERATED) + // not exposed to SWIG, only used internally. For when a menu is displayed dynamically in a panel, + // this function is called to add any relevant menu items. Doing this almost immediate-mode GUI + // style avoids complex retained state that has to be refreshed each time a panel is created. + virtual void MenuDisplaying(ContextMenu contextMenu, QMenu *menu, + const ExtensionCallbackData &data) = 0; + virtual void MenuDisplaying(PanelMenu panelMenu, QWidget *extensionButton, + const ExtensionCallbackData &data) = 0; +#endif + protected: DOCUMENT(""); IExtensionManager() = default; @@ -1616,7 +1793,7 @@ If no bookmark exists, this function will do nothing. DOCUMENT(R"(Retrieve the current singleton :class:`EventBrowser`. :return: The current window, which is created (but not shown) it there wasn't one open. -:rtype: EventBrowser +:rtype: ~qrenderdoc.EventBrowser )"); virtual IEventBrowser *GetEventBrowser() = 0; @@ -1630,7 +1807,7 @@ If no bookmark exists, this function will do nothing. DOCUMENT(R"(Retrieve the current singleton :class:`TextureViewer`. :return: The current window, which is created (but not shown) it there wasn't one open. -:rtype: TextureViewer +:rtype: ~qrenderdoc.TextureViewer )"); virtual ITextureViewer *GetTextureViewer() = 0; @@ -1644,7 +1821,7 @@ If no bookmark exists, this function will do nothing. DOCUMENT(R"(Retrieve the current singleton :class:`PipelineStateViewer`. :return: The current window, which is created (but not shown) it there wasn't one open. -:rtype: PipelineStateViewer +:rtype: ~qrenderdoc.PipelineStateViewer )"); virtual IPipelineStateViewer *GetPipelineViewer() = 0; diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 7de3b4805..36612867a 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -315,6 +315,17 @@ public: void detach() { setProcessState(QProcess::NotRunning); } }; +class RegisteredMenuItem : public QObject +{ +private: + Q_OBJECT +public: + ContextMenu context; + PanelMenu panel; + rdcarray submenus; + IExtensionManager::ExtensionCallback callback; +}; + class QFileFilterModel : public QSortFilterProxyModel { Q_OBJECT diff --git a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp index 98d3e342e..f5a96077a 100644 --- a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp +++ b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp @@ -74,6 +74,7 @@ bool CheckQtInterface(); // defined in SWIG-generated renderdoc_python.cpp extern "C" PyObject *PyInit_renderdoc(void); extern "C" PyObject *PassObjectToPython(const char *type, void *obj); +extern "C" PyObject *PassNewObjectToPython(const char *type, void *obj); // this one is in qrenderdoc_python.cpp extern "C" PyObject *PyInit_qrenderdoc(void); extern "C" PyObject *WrapBareQWidget(QWidget *); @@ -642,6 +643,70 @@ bool PythonContext::LoadExtension(ICaptureContext &ctx, const rdcstr &extension) return ext != NULL; } +void PythonContext::ConvertPyArgs(const ExtensionCallbackData &data, + rdcarray> &args) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + + args.resize(data.size()); + for(size_t i = 0; i < data.size(); i++) + { + rdcpair &a = args[i]; + a.first = data[i].first; + + // convert QVariant to python object + const QVariant &in = data[i].second; + PyObject *&out = a.second; + + // coverity[mixed_enums] + QMetaType::Type type = (QMetaType::Type)in.type(); + switch(type) + { + case QMetaType::Bool: out = PyBool_FromLong(in.toBool()); break; + case QMetaType::Short: + case QMetaType::Long: + case QMetaType::Int: out = PyLong_FromLong(in.toInt()); break; + case QMetaType::UShort: + case QMetaType::ULong: + case QMetaType::UInt: out = PyLong_FromUnsignedLong(in.toUInt()); break; + case QMetaType::LongLong: out = PyLong_FromLongLong(in.toLongLong()); break; + case QMetaType::ULongLong: out = PyLong_FromUnsignedLongLong(in.toULongLong()); break; + case QMetaType::Float: out = PyFloat_FromDouble(in.toFloat()); break; + case QMetaType::Double: out = PyFloat_FromDouble(in.toDouble()); break; + case QMetaType::QString: out = PyUnicode_FromString(in.toString().toUtf8().data()); break; + default: break; + } + + if(!out) + { + // try various other types + if(in.userType() == qMetaTypeId()) + out = PassNewObjectToPython("ResourceId *", new ResourceId(in.value())); + } + + if(!out) + { + qCritical() << "Couldn't convert" << in << "to python object"; + out = Py_None; + Py_XINCREF(out); + } + } + + PyGILState_Release(gil); +} + +void PythonContext::FreePyArgs(rdcarray> &args) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + + for(rdcpair &a : args) + { + Py_XDECREF(a.second); + } + + PyGILState_Release(gil); +} + QString PythonContext::versionString() { return QFormatStr("%1.%2.%3").arg(PY_MAJOR_VERSION).arg(PY_MINOR_VERSION).arg(PY_MICRO_VERSION); diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index fd5c0a437..cf05a0006 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -332,6 +332,15 @@ PyObject *PassObjectToPython(const char *type, void *obj) return SWIG_InternalNewPointerObj(obj, t, 0); } +PyObject *PassNewObjectToPython(const char *type, void *obj) +{ + swig_type_info *t = SWIG_TypeQuery(type); + if(t == NULL) + return NULL; + + return SWIG_InternalNewPointerObj(obj, t, SWIG_POINTER_OWN); +} + %} /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index c805e0a7c..2f910349f 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -1132,6 +1132,15 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) QObject::connect(ui->matrixType, OverloadedSlot::of(&QComboBox::currentIndexChanged), [this](int) { camGuess_changed(0.0); }); + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::MeshPreview, ui->extensions, {}); + }); + } + Reset(); m_Ctx.AddCaptureViewer(this); @@ -1328,6 +1337,30 @@ void BufferViewer::stageRowMenu(MeshDataStage stage, QMenu *menu, const QPoint & menu->addAction(m_ExportBytes); menu->popup(m_CurView->viewport()->mapToGlobal(pos)); + + ContextMenu contextMenu = ContextMenu::MeshPreview_VSInVertex; + + if(stage == MeshDataStage::VSOut) + contextMenu = ContextMenu::MeshPreview_VSOutVertex; + else if(stage == MeshDataStage::GSOut) + contextMenu = ContextMenu::MeshPreview_GSOutVertex; + + QModelIndex idx = m_CurView->selectionModel()->currentIndex(); + + ExtensionCallbackData callbackdata = {make_pyarg("stage", (uint32_t)stage)}; + + if(idx.isValid()) + { + uint32_t vertid = + m_CurView->model()->data(m_CurView->model()->index(idx.row(), 0), Qt::DisplayRole).toUInt(); + uint32_t index = + m_CurView->model()->data(m_CurView->model()->index(idx.row(), 1), Qt::DisplayRole).toUInt(); + + callbackdata.push_back(make_pyarg("vertex", vertid)); + callbackdata.push_back(make_pyarg("index", index)); + } + + m_Ctx.Extensions().MenuDisplaying(contextMenu, menu, callbackdata); } BufferViewer::~BufferViewer() diff --git a/qrenderdoc/Windows/BufferViewer.ui b/qrenderdoc/Windows/BufferViewer.ui index 473170afa..4e6453494 100644 --- a/qrenderdoc/Windows/BufferViewer.ui +++ b/qrenderdoc/Windows/BufferViewer.ui @@ -752,6 +752,17 @@ + + + + + :/plugin.png:/plugin.png + + + true + + + diff --git a/qrenderdoc/Windows/EventBrowser.cpp b/qrenderdoc/Windows/EventBrowser.cpp index 65466d8d6..2fc0ce19d 100644 --- a/qrenderdoc/Windows/EventBrowser.cpp +++ b/qrenderdoc/Windows/EventBrowser.cpp @@ -189,6 +189,15 @@ EventBrowser::EventBrowser(ICaptureContext &ctx, QWidget *parent) QObject::connect(ui->events->header(), &QHeaderView::customContextMenuRequested, this, &EventBrowser::events_contextMenu); + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::EventBrowser, ui->extensions, {}); + }); + } + OnCaptureClosed(); m_redPalette = palette(); @@ -1009,6 +1018,11 @@ void EventBrowser::events_contextMenu(const QPoint &pos) [this, rgp]() { rgp->SelectRGPEvent(m_Ctx.CurEvent()); }); } + contextMenu.addSeparator(); + + m_Ctx.Extensions().MenuDisplaying(ContextMenu::EventBrowser_Event, &contextMenu, + {{"eventId", m_Ctx.CurEvent()}}); + RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos)); } diff --git a/qrenderdoc/Windows/EventBrowser.ui b/qrenderdoc/Windows/EventBrowser.ui index c0b07614d..56121bb98 100644 --- a/qrenderdoc/Windows/EventBrowser.ui +++ b/qrenderdoc/Windows/EventBrowser.ui @@ -191,6 +191,17 @@ + + + + + :/plugin.png:/plugin.png + + + true + + + diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 6066cbb9a..9e1975c93 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -389,6 +389,13 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai if(a->menu()) actions.append(a->menu()->actions()); } + + // hide the dummy extension markers. They shouldn't be visible, they're just there so the code can + // easily find where to insert new extension menu items. + ui->extension_dummy_File->setVisible(false); + ui->extension_dummy_Window->setVisible(false); + ui->extension_dummy_Tools->setVisible(false); + ui->extension_dummy_Help->setVisible(false); } MainWindow::~MainWindow() @@ -1324,6 +1331,42 @@ void MainWindow::LiveCaptureClosed(LiveCapture *live) m_LiveCaptures.removeOne(live); } +QMenu *MainWindow::GetBaseMenu(WindowMenu base, rdcstr name) +{ + switch(base) + { + case WindowMenu::File: return ui->menu_File; + case WindowMenu::Window: return ui->menu_Window; + case WindowMenu::Tools: return ui->menu_Tools; + case WindowMenu::Help: return ui->menu_Help; + case WindowMenu::NewMenu: break; + default: return NULL; + } + + // new menu. See if we have one for name already. If not, create a new one and add it to the menu + // bar. + for(QAction *m : ui->menuBar->actions()) + { + // if it has an object name it's a built-in, ignore it. + if(!m->objectName().isEmpty()) + continue; + + if(m->text() == name) + return m->menu(); + } + + // no existing menu, add a new one + QMenu *menu = new QMenu(name, this); + menu->setIcon(Icons::plugin()); + ui->menuBar->insertMenu(ui->menu_Help->menuAction(), menu); + return menu; +} + +QList MainWindow::GetMenuActions() +{ + return ui->menuBar->actions(); +} + ToolWindowManager *MainWindow::mainToolManager() { return ui->toolWindowManager; diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 2eca72ce4..b25212f53 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -89,6 +89,9 @@ public: void ShowLiveCapture(LiveCapture *live); void LiveCaptureClosed(LiveCapture *live); + QMenu *GetBaseMenu(WindowMenu base, rdcstr name); + QList GetMenuActions(); + void showEventBrowser() { on_action_Event_Browser_triggered(); } void showAPIInspector() { on_action_API_Inspector_triggered(); } void showMeshPreview() { on_action_Mesh_Output_triggered(); } @@ -102,6 +105,7 @@ public: void showPythonShell() { on_action_Python_Shell_triggered(); } void showPerformanceCounterViewer() { on_action_Counter_Viewer_triggered(); } void showResourceInspector() { on_action_Resource_Inspector_triggered(); } + void showExtensionManager() { on_action_Manage_Extensions_triggered(); } void PopulateRecentCaptureFiles(); void PopulateRecentCaptureSettings(); void PopulateReportedBugs(); diff --git a/qrenderdoc/Windows/MainWindow.ui b/qrenderdoc/Windows/MainWindow.ui index 9807a269e..580ad1774 100644 --- a/qrenderdoc/Windows/MainWindow.ui +++ b/qrenderdoc/Windows/MainWindow.ui @@ -56,6 +56,8 @@ + + @@ -104,6 +106,8 @@ + + @@ -159,6 +163,8 @@ + + @@ -178,6 +184,8 @@ + + @@ -467,6 +475,26 @@ Open RGP Profile + + + __extensions__ + + + + + __extensions__ + + + + + __extensions__ + + + + + __extensions__ + + Manage Extensions diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index 58fbd18ec..9b0ce92c2 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -24,6 +24,7 @@ #include "D3D11PipelineStateViewer.h" #include +#include #include #include #include @@ -171,6 +172,15 @@ D3D11PipelineStateViewer::D3D11PipelineStateViewer(ICaptureContext &ctx, QObject::connect(cbuffer, &RDTreeWidget::itemActivated, this, &D3D11PipelineStateViewer::cbuffer_itemActivated); + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::PipelineStateViewer, ui->extensions, {}); + }); + } + addGridLines(ui->rasterizerGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->blendStateGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->depthStateGridLayout, palette().color(QPalette::WindowText)); diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui index 6c7fafe95..19e4a140e 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui @@ -53,7 +53,7 @@ - Display Controls + Controls 4 @@ -136,6 +136,23 @@ + + + + Extensions + + + + :/plugin.png:/plugin.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -202,8 +219,8 @@ 0 0 - 1000 - 576 + 454 + 245 @@ -646,7 +663,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -708,8 +725,8 @@ 0 0 - 1000 - 527 + 101 + 344 @@ -1036,7 +1053,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -1095,8 +1112,8 @@ 0 0 - 1000 - 527 + 101 + 344 @@ -1423,7 +1440,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -1482,8 +1499,8 @@ 0 0 - 1000 - 527 + 101 + 344 @@ -1810,7 +1827,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -1869,8 +1886,8 @@ 0 0 - 1000 - 527 + 101 + 430 @@ -2868,7 +2885,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -2927,8 +2944,8 @@ 0 0 - 1000 - 527 + 101 + 344 @@ -3204,8 +3221,8 @@ 0 0 - 1000 - 576 + 829 + 311 @@ -4474,16 +4491,16 @@ - - RDLabel - QLabel -
Widgets/Extended/RDLabel.h
-
RDTreeWidget QTreeView
Widgets/Extended/RDTreeWidget.h
+ + RDLabel + QLabel +
Widgets/Extended/RDLabel.h
+
PipelineFlowChart QFrame diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index f319c6c72..c04227dc4 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -24,6 +24,7 @@ #include "D3D12PipelineStateViewer.h" #include +#include #include #include #include @@ -211,6 +212,15 @@ D3D12PipelineStateViewer::D3D12PipelineStateViewer(ICaptureContext &ctx, QObject::connect(cbuffer, &RDTreeWidget::itemActivated, this, &D3D12PipelineStateViewer::cbuffer_itemActivated); + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::PipelineStateViewer, ui->extensions, {}); + }); + } + addGridLines(ui->rasterizerGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->blendStateGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->depthStateGridLayout, palette().color(QPalette::WindowText)); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui index 7c3b83dc2..9c79e3ab8 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui @@ -53,7 +53,7 @@ - Display Controls + Controls 4 @@ -136,6 +136,23 @@ + + + + Extensions + + + + :/plugin.png:/plugin.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -549,9 +566,9 @@ :/page_white_edit.png:/page_white_edit.png - + - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -945,7 +962,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -1237,6 +1254,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + +
@@ -1333,7 +1356,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -1625,6 +1648,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + +
@@ -1721,7 +1750,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -2065,6 +2094,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -2708,7 +2743,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -3000,6 +3035,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -3764,7 +3805,7 @@ :/page_white_edit.png:/page_white_edit.png - QToolButton::MenuButtonPopup + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -4132,6 +4173,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index 9bf863ea1..bf86a66e5 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -24,6 +24,7 @@ #include "GLPipelineStateViewer.h" #include +#include #include #include #include @@ -168,6 +169,15 @@ GLPipelineStateViewer::GLPipelineStateViewer(ICaptureContext &ctx, PipelineState QObject::connect(res, &RDTreeWidget::itemActivated, this, &GLPipelineStateViewer::resource_itemActivated); + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::PipelineStateViewer, ui->extensions, {}); + }); + } + addGridLines(ui->rasterizerGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->MSAAGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->blendStateGridLayout, palette().color(QPalette::WindowText)); diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui index d2eb6b2e8..200fa85c0 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui @@ -53,7 +53,7 @@ - Display Controls + Controls 4 @@ -136,6 +136,23 @@ + + + + Extensions + + + + :/plugin.png:/plugin.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -671,8 +688,8 @@ 0 0 - 883 - 552 + 116 + 430 @@ -1125,8 +1142,8 @@ 0 0 - 883 - 552 + 116 + 430 @@ -1579,8 +1596,8 @@ 0 0 - 883 - 552 + 116 + 430 @@ -2033,8 +2050,8 @@ 0 0 - 883 - 552 + 117 + 544 @@ -3465,8 +3482,8 @@ 0 0 - 883 - 552 + 116 + 430 @@ -3794,8 +3811,8 @@ 0 0 - 883 - 601 + 488 + 311 @@ -4426,8 +4443,8 @@ 0 0 - 883 - 552 + 116 + 430 @@ -4730,16 +4747,16 @@ - - RDLabel - QLabel -
Widgets/Extended/RDLabel.h
-
RDTreeWidget QTreeView
Widgets/Extended/RDTreeWidget.h
+ + RDLabel + QLabel +
Widgets/Extended/RDLabel.h
+
PipelineFlowChart QFrame diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index 0845d364b..ede954de4 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -24,6 +24,7 @@ #include "VulkanPipelineStateViewer.h" #include +#include #include #include #include @@ -163,6 +164,15 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, QObject::connect(ubo, &RDTreeWidget::itemActivated, this, &VulkanPipelineStateViewer::ubo_itemActivated); + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::PipelineStateViewer, ui->extensions, {}); + }); + } + addGridLines(ui->rasterizerGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->MSAAGridLayout, palette().color(QPalette::WindowText)); addGridLines(ui->blendStateGridLayout, palette().color(QPalette::WindowText)); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui index 34d15d95e..458ffd672 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui @@ -53,7 +53,7 @@ - Display Controls + Controls 4 @@ -136,6 +136,23 @@ + + + + Extensions + + + + :/plugin.png:/plugin.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + diff --git a/qrenderdoc/Windows/TextureViewer.cpp b/qrenderdoc/Windows/TextureViewer.cpp index 8d9fbba9b..fe70e40c8 100644 --- a/qrenderdoc/Windows/TextureViewer.cpp +++ b/qrenderdoc/Windows/TextureViewer.cpp @@ -540,6 +540,15 @@ TextureViewer::TextureViewer(ICaptureContext &ctx, QWidget *parent) &TextureViewer::channelsWidget_mouseClicked); } + { + QMenu *extensionsMenu = new QMenu(this); + + QObject::connect(extensionsMenu, &QMenu::aboutToShow, [this, extensionsMenu]() { + extensionsMenu->clear(); + m_Ctx.Extensions().MenuDisplaying(PanelMenu::TextureViewer, ui->extensions, {}); + }); + } + QWidget *renderContainer = ui->renderContainer; ui->dockarea->addToolWindow(ui->renderContainer, ToolWindowManager::EmptySpace); @@ -1885,7 +1894,8 @@ void TextureViewer::AddResourceUsageEntry(QMenu &menu, uint32_t start, uint32_t menu.addAction(item); } -void TextureViewer::OpenResourceContextMenu(ResourceId id, const rdcarray &usage) +void TextureViewer::OpenResourceContextMenu(ResourceId id, bool input, + const rdcarray &usage) { QMenu contextMenu(this); @@ -1920,6 +1930,12 @@ void TextureViewer::OpenResourceContextMenu(ResourceId id, const rdcarray empty; + bool input = follow.Type == FollowType::ReadOnly; + if(id == ResourceId()) { - OpenResourceContextMenu(id, empty); + OpenResourceContextMenu(id, input, empty); } else { - m_Ctx.Replay().AsyncInvoke([this, id](IReplayController *r) { + m_Ctx.Replay().AsyncInvoke([this, id, input](IReplayController *r) { rdcarray usage = r->GetUsage(id); - GUIInvoke::call(this, [this, id, usage]() { OpenResourceContextMenu(id, usage); }); + GUIInvoke::call(this, + [this, id, input, usage]() { OpenResourceContextMenu(id, input, usage); }); }); } } diff --git a/qrenderdoc/Windows/TextureViewer.h b/qrenderdoc/Windows/TextureViewer.h index d079af679..8e71792e4 100644 --- a/qrenderdoc/Windows/TextureViewer.h +++ b/qrenderdoc/Windows/TextureViewer.h @@ -248,7 +248,7 @@ private: int &prevIndex, bool copy, bool rw); void AddResourceUsageEntry(QMenu &menu, uint32_t start, uint32_t end, ResourceUsage usage); - void OpenResourceContextMenu(ResourceId id, const rdcarray &usage); + void OpenResourceContextMenu(ResourceId id, bool input, const rdcarray &usage); void AutoFitRange(); void rangePoint_Update(); diff --git a/qrenderdoc/Windows/TextureViewer.ui b/qrenderdoc/Windows/TextureViewer.ui index 53b529483..b16eecbc1 100644 --- a/qrenderdoc/Windows/TextureViewer.ui +++ b/qrenderdoc/Windows/TextureViewer.ui @@ -227,7 +227,7 @@ - 490 + 530 40 129 32 @@ -418,7 +418,7 @@ 330 40 - 149 + 181 32 @@ -539,6 +539,17 @@ + + + + + :/plugin.png:/plugin.png + + + true + + +