diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 793886184..34e5308bd 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -557,6 +557,12 @@ struct IShaderViewer )"); virtual void ShowErrors(const rdcstr &errors) = 0; + DOCUMENT(R"(Add an expression to the watch panel. + +:param str expression: The name of the expression to watch. +)"); + virtual void AddWatch(const rdcstr &expression) = 0; + protected: IShaderViewer() = default; ~IShaderViewer() = default; diff --git a/qrenderdoc/Widgets/Extended/RDTableWidget.cpp b/qrenderdoc/Widgets/Extended/RDTableWidget.cpp index 048e799fe..5766caad2 100644 --- a/qrenderdoc/Widgets/Extended/RDTableWidget.cpp +++ b/qrenderdoc/Widgets/Extended/RDTableWidget.cpp @@ -86,29 +86,8 @@ void RDTableWidget::keyPressEvent(QKeyEvent *e) { if(!m_customCopyPaste && e->matches(QKeySequence::Copy)) { - QList items = selectedItems(); - - std::sort(items.begin(), items.end(), [this](QTableWidgetItem *a, QTableWidgetItem *b) { - if(row(a) != row(b)) - return row(a) < row(b); - return column(a) < column(b); - }); - - int prevRow = row(items[0]); - - QString clipboardText; - for(QTableWidgetItem *i : items) - { - clipboardText += i->text(); - - if(prevRow != row(i)) - clipboardText += lit("\n"); - else - clipboardText += lit(" | "); - } - - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(clipboardText.trimmed()); + copySelection(); + return; } else { @@ -117,3 +96,30 @@ void RDTableWidget::keyPressEvent(QKeyEvent *e) emit(keyPress(e)); } + +void RDTableWidget::copySelection() +{ + QList items = selectedItems(); + + std::sort(items.begin(), items.end(), [this](QTableWidgetItem *a, QTableWidgetItem *b) { + if(row(a) != row(b)) + return row(a) < row(b); + return column(a) < column(b); + }); + + int prevRow = row(items[0]); + + QString clipboardText; + for(QTableWidgetItem *i : items) + { + clipboardText += i->text(); + + if(prevRow != row(i)) + clipboardText += lit("\n"); + else + clipboardText += lit(" | "); + } + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(clipboardText.trimmed()); +} diff --git a/qrenderdoc/Widgets/Extended/RDTableWidget.h b/qrenderdoc/Widgets/Extended/RDTableWidget.h index 2a71b5c70..21a5618c0 100644 --- a/qrenderdoc/Widgets/Extended/RDTableWidget.h +++ b/qrenderdoc/Widgets/Extended/RDTableWidget.h @@ -32,6 +32,8 @@ class RDTableWidget : public QTableWidget public: explicit RDTableWidget(QWidget *parent = 0); + void copySelection(); + bool customCopyPasteHandler() { return m_customCopyPaste; } void setCustomCopyPasteHandler(bool custom) { m_customCopyPaste = custom; } signals: diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp index 829cbbd75..4648cc07f 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp @@ -1025,57 +1025,7 @@ void RDTreeWidget::keyPressEvent(QKeyEvent *e) { if(!m_customCopyPaste && e->matches(QKeySequence::Copy)) { - QModelIndexList sel = selectionModel()->selectedRows(); - - int stackWidths[16]; - int *heapWidths = NULL; - - int colCount = m_model->columnCount(); - - if(colCount >= 16) - heapWidths = new int[colCount]; - - int *widths = heapWidths ? heapWidths : stackWidths; - - for(int i = 0; i < colCount; i++) - widths[i] = 0; - - // align the copied data so that each column is the same width - for(QModelIndex idx : sel) - { - RDTreeWidgetItem *item = m_model->itemForIndex(idx); - - for(int i = 0; i < qMin(colCount, item->m_text.count()); i++) - { - QString text = item->m_text[i].toString(); - widths[i] = qMax(widths[i], text.count()); - } - } - - // only align up to 50 characters so one really long item doesn't mess up the whole thing - for(int i = 0; i < colCount; i++) - widths[i] = qMin(50, widths[i]); - - QString clipData; - for(QModelIndex idx : sel) - { - RDTreeWidgetItem *item = m_model->itemForIndex(idx); - - for(int i = 0; i < qMin(colCount, item->m_text.count()); i++) - { - QString format = i == 0 ? QFormatStr("%1") : QFormatStr(" %1"); - QString text = item->m_text[i].toString(); - - clipData += format.arg(text, -widths[i]); - } - - clipData += lit("\n"); - } - - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(clipData.trimmed()); - - delete[] heapWidths; + copySelection(); } else { @@ -1083,6 +1033,61 @@ void RDTreeWidget::keyPressEvent(QKeyEvent *e) } } +void RDTreeWidget::copySelection() +{ + QModelIndexList sel = selectionModel()->selectedRows(); + + int stackWidths[16]; + int *heapWidths = NULL; + + int colCount = m_model->columnCount(); + + if(colCount >= 16) + heapWidths = new int[colCount]; + + int *widths = heapWidths ? heapWidths : stackWidths; + + for(int i = 0; i < colCount; i++) + widths[i] = 0; + + // align the copied data so that each column is the same width + for(QModelIndex idx : sel) + { + RDTreeWidgetItem *item = m_model->itemForIndex(idx); + + for(int i = 0; i < qMin(colCount, item->m_text.count()); i++) + { + QString text = item->m_text[i].toString(); + widths[i] = qMax(widths[i], text.count()); + } + } + + // only align up to 50 characters so one really long item doesn't mess up the whole thing + for(int i = 0; i < colCount; i++) + widths[i] = qMin(50, widths[i]); + + QString clipData; + for(QModelIndex idx : sel) + { + RDTreeWidgetItem *item = m_model->itemForIndex(idx); + + for(int i = 0; i < qMin(colCount, item->m_text.count()); i++) + { + QString format = i == 0 ? QFormatStr("%1") : QFormatStr(" %1"); + QString text = item->m_text[i].toString(); + + clipData += format.arg(text, -widths[i]); + } + + clipData += lit("\n"); + } + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(clipData.trimmed()); + + delete[] heapWidths; +} + void RDTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { // we do our own custom branch rendering to ensure the backgrounds for the +/- markers are diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.h b/qrenderdoc/Widgets/Extended/RDTreeWidget.h index 9b2cfb4fb..526e5f835 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.h +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.h @@ -279,6 +279,8 @@ public: void collapseAllItems(RDTreeWidgetItem *item); void scrollToItem(RDTreeWidgetItem *node); + void copySelection(); + void clear(); signals: @@ -298,7 +300,6 @@ private: void leaveEvent(QEvent *e) override; void focusOutEvent(QFocusEvent *event) override; void keyPressEvent(QKeyEvent *e) override; - void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override; diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index 622cf8ffa..fce44df02 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -563,6 +563,16 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR QObject::connect(ui->watch, &RDTableWidget::keyPress, this, &ShaderViewer::watch_keyPress); + ui->watch->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ui->watch, &RDTableWidget::customContextMenuRequested, this, + &ShaderViewer::variables_contextMenu); + ui->registers->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ui->registers, &RDTreeWidget::customContextMenuRequested, this, + &ShaderViewer::variables_contextMenu); + ui->locals->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ui->locals, &RDTreeWidget::customContextMenuRequested, this, + &ShaderViewer::variables_contextMenu); + ui->watch->insertRow(0); for(int i = 0; i < ui->watch->columnCount(); i++) @@ -955,6 +965,87 @@ void ShaderViewer::debug_contextMenu(const QPoint &pos) RDDialog::show(&contextMenu, edit->viewport()->mapToGlobal(pos)); } +void ShaderViewer::variables_contextMenu(const QPoint &pos) +{ + QAbstractItemView *w = qobject_cast(QObject::sender()); + + QMenu contextMenu(this); + + QAction copyValue(tr("Copy"), this); + QAction addWatch(tr("Add Watch"), this); + QAction deleteWatch(tr("Delete Watch"), this); + QAction clearAll(tr("Clear All"), this); + + contextMenu.addAction(©Value); + contextMenu.addSeparator(); + contextMenu.addAction(&addWatch); + + if(QObject::sender() == ui->watch) + { + QObject::connect(©Value, &QAction::triggered, [this] { ui->watch->copySelection(); }); + + contextMenu.addAction(&deleteWatch); + contextMenu.addSeparator(); + contextMenu.addAction(&clearAll); + + // start with no row selected + int selRow = -1; + + QList items = ui->watch->selectedItems(); + for(QTableWidgetItem *item : items) + { + // if no row is selected, or the same as this item, set selected row to this item's + if(selRow == -1 || selRow == item->row()) + { + selRow = item->row(); + } + else + { + // we only get here if we see an item on a different row selected - that means too many rows + // so bail out + selRow = -1; + break; + } + } + + // if we have a selected row that isn't the last one, we can add/delete this item + deleteWatch.setEnabled(selRow >= 0 && selRow < ui->watch->rowCount() - 1); + addWatch.setEnabled(selRow >= 0 && selRow < ui->watch->rowCount() - 1); + + QObject::connect(&addWatch, &QAction::triggered, [this, selRow] { + QTableWidgetItem *item = ui->watch->item(selRow, 0); + + if(item) + AddWatch(item->text()); + }); + + QObject::connect(&deleteWatch, &QAction::triggered, + [this, selRow] { ui->watch->removeRow(selRow); }); + + QObject::connect(&clearAll, &QAction::triggered, [this] { + while(ui->watch->rowCount() > 1) + ui->watch->removeRow(0); + }); + } + else + { + RDTreeWidget *tree = qobject_cast(w); + + QObject::connect(©Value, &QAction::triggered, [this, tree] { tree->copySelection(); }); + + addWatch.setEnabled(tree->selectedItem() != NULL); + + QObject::connect(&addWatch, &QAction::triggered, [this, tree] { + if(tree == ui->locals) + AddWatch(tree->selectedItem()->tag().toString()); + else + AddWatch(tree->selectedItem()->text(0)); + }); + } + + RDDialog::show(&contextMenu, w->viewport()->mapToGlobal(pos)); +} + void ShaderViewer::disassembly_buttonReleased(QMouseEvent *event) { if(event->button() == Qt::LeftButton) @@ -2066,7 +2157,6 @@ void ShaderViewer::updateDebugging() for(int i = 0; i < ui->watch->rowCount() - 1; i++) { QTableWidgetItem *item = ui->watch->item(i, 0); - ui->watch->setItem(i, 1, new QTableWidgetItem(tr("register", "watch type"))); QString reg = item->text().trimmed(); @@ -2084,6 +2174,10 @@ void ShaderViewer::updateDebugging() if(match.hasMatch()) { + item = new QTableWidgetItem(tr("register", "watch type")); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 2, item); + QString regtype = match.captured(1); QString regidx = match.captured(2); QString swizzle = match.captured(3).replace(QLatin1Char('.'), QString()); @@ -2189,10 +2283,15 @@ void ShaderViewer::updateDebugging() val += lit(", "); } + item = new QTableWidgetItem(vr.name); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 1, item); + item = new QTableWidgetItem(val); item->setData(Qt::UserRole, QVariant::fromValue(VariableTag(varCat, regindex, arrIndex))); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); - ui->watch->setItem(i, 2, item); + ui->watch->setItem(i, 3, item); continue; } @@ -2213,14 +2312,26 @@ void ShaderViewer::updateDebugging() { // TODO apply swizzle/typecast ? + item = new QTableWidgetItem(local->text(1)); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 1, item); + + item = new QTableWidgetItem(local->text(2)); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 2, item); + if(local->childCount() > 0) { // can't display structs - ui->watch->setItem(i, 2, new QTableWidgetItem(lit("{...}"))); + item = new QTableWidgetItem(lit("{...}")); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 3, item); } else { - ui->watch->setItem(i, 2, new QTableWidgetItem(local->text(3))); + item = new QTableWidgetItem(local->text(3)); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 3, item); } continue; @@ -2228,7 +2339,13 @@ void ShaderViewer::updateDebugging() } } - ui->watch->setItem(i, 2, new QTableWidgetItem(tr("Error evaluating expression"))); + item = new QTableWidgetItem(); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 2, item); + + item = new QTableWidgetItem(tr("Error evaluating expression")); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->watch->setItem(i, 3, item); } ui->watch->setUpdatesEnabled(true); @@ -2417,6 +2534,18 @@ void ShaderViewer::ShowErrors(const rdcstr &errors) } } +void ShaderViewer::AddWatch(const rdcstr &variable) +{ + int newRow = ui->watch->rowCount() - 1; + ui->watch->insertRow(ui->watch->rowCount() - 1); + + ui->watch->setItem(newRow, 0, new QTableWidgetItem(variable)); + + ToolWindowManager::raiseToolWindow(ui->watch); + ui->watch->activateWindow(); + ui->watch->QWidget::setFocus(); +} + int ShaderViewer::snippetPos() { if(IsD3D(m_Ctx.APIProps().pipelineType)) diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index 7e37acef9..9c4c7c7d9 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -100,6 +100,8 @@ public: virtual void ShowErrors(const rdcstr &errors) override; + virtual void AddWatch(const rdcstr &variable) override; + // ICaptureViewer void OnCaptureLoaded() override; void OnCaptureClosed() override; @@ -120,6 +122,7 @@ private slots: void readonly_keyPressed(QKeyEvent *event); void editable_keyPressed(QKeyEvent *event); void debug_contextMenu(const QPoint &pos); + void variables_contextMenu(const QPoint &pos); void disassembly_buttonReleased(QMouseEvent *event); void disassemble_typeChanged(int index); void watch_keyPress(QKeyEvent *event); diff --git a/qrenderdoc/Windows/ShaderViewer.ui b/qrenderdoc/Windows/ShaderViewer.ui index 1e7a7e31a..5b4142af7 100644 --- a/qrenderdoc/Windows/ShaderViewer.ui +++ b/qrenderdoc/Windows/ShaderViewer.ui @@ -509,9 +509,9 @@ - 790 + 520 60 - 151 + 421 131 @@ -544,6 +544,11 @@ Name + + + Register(s) + + Type