mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 09:30:44 +00:00
ebaefc82a9
* We enforce a naming scheme more strongly - types, member functions, and enum values must be UpperCaseCamel, and member variables must be lowerCaseCamel. No underscores allowed. * eventId not eventID or EID, and Id preferred to ID in general. Also for resourceId. * Removed some lingering hungarian m_Foo naming. * Some pipeline state structs that are almost identical between the different APIs are pulled out into common structs. Where something doesn't make sense (e.g. viewport enable for vulkan) it will just be set to a sensible default (in that case always true). * Changed scissors to be x/y & width/height instead of sometimes left/top/right/bottom * Abbreviations are discouraged, e.g. operation not op, function not func.
485 lines
14 KiB
C++
485 lines
14 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2016-2017 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 "PerformanceCounterSelection.h"
|
|
#include <QMenu>
|
|
#include <QSet>
|
|
#include "Code/Interface/QRDInterface.h"
|
|
#include "Code/Resources.h"
|
|
#include "ui_PerformanceCounterSelection.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
#define JSON_ID "rdocPerformanceCounterSettings"
|
|
#define JSON_VER 1
|
|
|
|
// we can't specialise the template, but creating an overload works. This lets us use
|
|
// QSet<GPUCounter>
|
|
inline uint qHash(const GPUCounter &t)
|
|
{
|
|
return qHash(uint32_t(t));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
enum class CounterFamily
|
|
{
|
|
Unknown,
|
|
Generic,
|
|
AMD,
|
|
Intel,
|
|
NVIDIA,
|
|
};
|
|
|
|
CounterFamily GetCounterFamily(GPUCounter counter)
|
|
{
|
|
if(IsAMDCounter(counter))
|
|
{
|
|
return CounterFamily::AMD;
|
|
}
|
|
else if(IsIntelCounter(counter))
|
|
{
|
|
return CounterFamily::Intel;
|
|
}
|
|
else if(IsNvidiaCounter(counter))
|
|
{
|
|
return CounterFamily::NVIDIA;
|
|
}
|
|
|
|
return CounterFamily::Generic;
|
|
}
|
|
|
|
QString ToString(CounterFamily family)
|
|
{
|
|
switch(family)
|
|
{
|
|
case CounterFamily::AMD: return lit("AMD");
|
|
case CounterFamily::Generic: return lit("Generic");
|
|
case CounterFamily::Intel: return lit("Intel");
|
|
case CounterFamily::NVIDIA: return lit("NVIDIA");
|
|
case CounterFamily::Unknown: return lit("Unknown");
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
const int PerformanceCounterSelection::CounterDescriptionRole = Qt::UserRole + 1;
|
|
const int PerformanceCounterSelection::CounterIdRole = Qt::UserRole + 2;
|
|
const int PerformanceCounterSelection::PreviousCheckStateRole = Qt::UserRole + 3;
|
|
|
|
void PerformanceCounterSelection::uncheckAllChildren(RDTreeWidgetItem *item)
|
|
{
|
|
for(int i = 0; i < item->childCount(); i++)
|
|
{
|
|
uncheckAllChildren(item->child(i));
|
|
|
|
item->child(i)->setCheckState(0, Qt::Unchecked);
|
|
item->child(i)->setData(0, PreviousCheckStateRole, Qt::Unchecked);
|
|
}
|
|
}
|
|
|
|
void PerformanceCounterSelection::checkAllChildren(RDTreeWidgetItem *item)
|
|
{
|
|
for(int i = 0; i < item->childCount(); i++)
|
|
{
|
|
checkAllChildren(item->child(i));
|
|
|
|
item->child(i)->setCheckState(0, Qt::Checked);
|
|
item->child(i)->setData(0, PreviousCheckStateRole, Qt::Checked);
|
|
}
|
|
}
|
|
|
|
void PerformanceCounterSelection::updateParentCheckState(RDTreeWidgetItem *item)
|
|
{
|
|
if(!item)
|
|
return;
|
|
|
|
int numChecked = 0;
|
|
int numPartial = 0;
|
|
|
|
for(int i = 0; i < item->childCount(); i++)
|
|
{
|
|
Qt::CheckState state = item->child(i)->checkState(0);
|
|
if(state == Qt::PartiallyChecked)
|
|
numPartial++;
|
|
else if(state == Qt::Checked)
|
|
numChecked++;
|
|
}
|
|
|
|
if(numChecked == item->childCount())
|
|
item->setCheckState(0, Qt::Checked);
|
|
else if(numChecked > 0 || numPartial > 0)
|
|
item->setCheckState(0, Qt::PartiallyChecked);
|
|
else
|
|
item->setCheckState(0, Qt::Unchecked);
|
|
|
|
item->setData(0, PreviousCheckStateRole, item->checkState(0));
|
|
|
|
updateParentCheckState(item->parent());
|
|
}
|
|
|
|
void PerformanceCounterSelection::expandToNode(RDTreeWidgetItem *node)
|
|
{
|
|
RDTreeWidgetItem *n = node;
|
|
while(node != NULL)
|
|
{
|
|
ui->counterTree->expandItem(node);
|
|
node = node->parent();
|
|
}
|
|
|
|
if(n)
|
|
ui->counterTree->scrollToItem(n);
|
|
}
|
|
|
|
PerformanceCounterSelection::PerformanceCounterSelection(ICaptureContext &ctx,
|
|
const QList<GPUCounter> &selectedCounters,
|
|
QWidget *parent)
|
|
: QDialog(parent), ui(new Ui::PerformanceCounterSelection), m_Ctx(ctx)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
ui->counterTree->setColumns({QString()});
|
|
ui->counterTree->setHeaderHidden(true);
|
|
|
|
connect(ui->counterTree, &RDTreeWidget::currentItemChanged,
|
|
[this](RDTreeWidgetItem *item, RDTreeWidgetItem *) -> void {
|
|
const QVariant d = item->data(0, CounterDescriptionRole);
|
|
|
|
if(d.isValid())
|
|
{
|
|
ui->counterDescription->setText(
|
|
QString(lit("<b>%1</b><hr>%2")).arg(item->text(0)).arg(d.toString()));
|
|
}
|
|
});
|
|
|
|
connect(ui->save, &QPushButton::pressed, this, &PerformanceCounterSelection::Save);
|
|
connect(ui->load, &QPushButton::pressed, this, &PerformanceCounterSelection::Load);
|
|
connect(ui->sampleCounters, &QPushButton::pressed, this, &PerformanceCounterSelection::accept);
|
|
connect(ui->cancel, &QPushButton::pressed, this, &PerformanceCounterSelection::reject);
|
|
|
|
connect(ui->counterTree, &RDTreeWidget::itemChanged, [this](RDTreeWidgetItem *item, int) -> void {
|
|
const QVariant d = item->data(0, CounterIdRole);
|
|
|
|
static bool recurse = false;
|
|
|
|
if(d.isValid())
|
|
{
|
|
if(item->checkState(0) == Qt::Checked)
|
|
{
|
|
// Add
|
|
QListWidgetItem *listItem = new QListWidgetItem(ui->enabledCounters);
|
|
listItem->setText(item->text(0));
|
|
listItem->setData(CounterIdRole, d);
|
|
m_SelectedCounters.insert((GPUCounter)d.toUInt(), listItem);
|
|
}
|
|
else
|
|
{
|
|
// Remove
|
|
QListWidgetItem *listItem = m_SelectedCounters.take((GPUCounter)d.toUInt());
|
|
delete listItem;
|
|
}
|
|
|
|
if(!recurse)
|
|
{
|
|
recurse = true;
|
|
updateParentCheckState(item->parent());
|
|
recurse = false;
|
|
}
|
|
}
|
|
else if(!recurse)
|
|
{
|
|
Qt::CheckState prev = item->data(0, PreviousCheckStateRole).value<Qt::CheckState>();
|
|
|
|
if(item->checkState(0) != prev)
|
|
{
|
|
recurse = true;
|
|
|
|
if(item->checkState(0) == Qt::Checked)
|
|
{
|
|
checkAllChildren(item);
|
|
}
|
|
else
|
|
{
|
|
uncheckAllChildren(item);
|
|
}
|
|
|
|
item->setData(0, PreviousCheckStateRole, item->checkState(0));
|
|
|
|
updateParentCheckState(item);
|
|
|
|
recurse = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
ui->counterTree->setMouseTracking(true);
|
|
|
|
ctx.Replay().AsyncInvoke([this, selectedCounters](IReplayController *controller) -> void {
|
|
QVector<CounterDescription> counterDescriptions;
|
|
for(const GPUCounter counter : controller->EnumerateCounters())
|
|
{
|
|
counterDescriptions.append(controller->DescribeCounter(counter));
|
|
}
|
|
|
|
GUIInvoke::call([counterDescriptions, selectedCounters, this]() -> void {
|
|
SetCounters(counterDescriptions);
|
|
SetSelectedCounters(selectedCounters);
|
|
});
|
|
});
|
|
|
|
ui->counterTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
QObject::connect(ui->counterTree, &RDTreeWidget::customContextMenuRequested, this,
|
|
&PerformanceCounterSelection::counterTree_contextMenu);
|
|
}
|
|
|
|
PerformanceCounterSelection::~PerformanceCounterSelection()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void PerformanceCounterSelection::SetCounters(const QVector<CounterDescription> &descriptions)
|
|
{
|
|
ui->counterTree->clear();
|
|
ui->enabledCounters->clear();
|
|
|
|
RDTreeWidgetItem *currentRoot = NULL;
|
|
CounterFamily currentFamily = CounterFamily::Unknown;
|
|
|
|
std::unordered_map<std::string, RDTreeWidgetItem *> categories;
|
|
|
|
for(const CounterDescription &desc : descriptions)
|
|
{
|
|
m_CounterToUuid[desc.counter] = desc.uuid;
|
|
m_UuidToCounter[desc.uuid] = desc.counter;
|
|
|
|
const CounterFamily family = GetCounterFamily(desc.counter);
|
|
if(family != currentFamily)
|
|
{
|
|
currentRoot = new RDTreeWidgetItem();
|
|
currentRoot->setText(0, ToString(family));
|
|
currentRoot->setCheckState(0, Qt::Unchecked);
|
|
currentRoot->setData(0, PreviousCheckStateRole, Qt::Unchecked);
|
|
ui->counterTree->addTopLevelItem(currentRoot);
|
|
|
|
categories.clear();
|
|
|
|
currentFamily = family;
|
|
}
|
|
|
|
RDTreeWidgetItem *categoryItem = NULL;
|
|
|
|
const std::string category = desc.category;
|
|
auto categoryIterator = categories.find(category);
|
|
|
|
if(categoryIterator == categories.end())
|
|
{
|
|
RDTreeWidgetItem *item = new RDTreeWidgetItem();
|
|
item->setText(0, desc.category);
|
|
item->setCheckState(0, Qt::Unchecked);
|
|
item->setData(0, PreviousCheckStateRole, Qt::Unchecked);
|
|
currentRoot->addChild(item);
|
|
|
|
categories[category] = item;
|
|
categoryItem = item;
|
|
}
|
|
else
|
|
{
|
|
categoryItem = categoryIterator->second;
|
|
}
|
|
|
|
RDTreeWidgetItem *counterItem = new RDTreeWidgetItem();
|
|
counterItem->setText(0, desc.name);
|
|
counterItem->setData(0, CounterDescriptionRole, desc.description);
|
|
counterItem->setData(0, CounterIdRole, (uint32_t)desc.counter);
|
|
counterItem->setCheckState(0, Qt::Unchecked);
|
|
counterItem->setData(0, PreviousCheckStateRole, Qt::Unchecked);
|
|
categoryItem->addChild(counterItem);
|
|
|
|
m_CounterToTreeItem[desc.counter] = counterItem;
|
|
}
|
|
}
|
|
|
|
QList<GPUCounter> PerformanceCounterSelection::GetSelectedCounters() const
|
|
{
|
|
return m_SelectedCounters.keys();
|
|
}
|
|
|
|
void PerformanceCounterSelection::SetSelectedCounters(const QList<GPUCounter> &counters)
|
|
{
|
|
// We we walk over the complete tree, and toggle everything so it
|
|
// matches the settings
|
|
RDTreeWidgetItemIterator it(ui->counterTree);
|
|
while(*it)
|
|
{
|
|
const QVariant id = (*it)->data(0, CounterIdRole);
|
|
if(id.isValid())
|
|
{
|
|
const GPUCounter counter = (GPUCounter)id.toUInt();
|
|
|
|
(*it)->setCheckState(0, counters.contains(counter) ? Qt::Checked : Qt::Unchecked);
|
|
}
|
|
|
|
// The loop above will uncheck all unknown counters, and not crash if some counter is no
|
|
// longer present, or unknown
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void PerformanceCounterSelection::Save()
|
|
{
|
|
QString filename = RDDialog::getSaveFileName(this, tr("Save File"), QDir::homePath(),
|
|
tr("Performance Counter Settings (*.json)"));
|
|
|
|
if(filename.isEmpty())
|
|
return;
|
|
|
|
QVariantList counterIds;
|
|
for(const GPUCounter v : m_SelectedCounters.keys())
|
|
{
|
|
const Uuid uuid = m_CounterToUuid[v];
|
|
QVariantList e;
|
|
|
|
for(const uint32_t b : uuid.words)
|
|
{
|
|
e.append(b);
|
|
}
|
|
|
|
counterIds.append(QVariant(e));
|
|
}
|
|
|
|
QVariantMap doc;
|
|
doc[lit("counters")] = counterIds;
|
|
|
|
QFile f(filename);
|
|
if(f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
|
|
{
|
|
SaveToJSON(doc, f, JSON_ID, JSON_VER);
|
|
}
|
|
else
|
|
{
|
|
RDDialog::critical(this, tr("Error saving config"),
|
|
tr("Couldn't open path %1 for write.").arg(filename));
|
|
}
|
|
}
|
|
|
|
void PerformanceCounterSelection::Load()
|
|
{
|
|
QString filename = RDDialog::getOpenFileName(this, tr("Load file"), QDir::homePath(),
|
|
tr("Performance Counter Settings (*.json)"));
|
|
|
|
if(filename.isEmpty())
|
|
return;
|
|
|
|
QVariantMap doc;
|
|
QFile f(filename);
|
|
if(f.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
{
|
|
LoadFromJSON(doc, f, JSON_ID, JSON_VER);
|
|
|
|
QSet<GPUCounter> selectedCounters;
|
|
|
|
QVariantList counters = doc[lit("counters")].toList();
|
|
|
|
for(const QVariant &counter : counters)
|
|
{
|
|
QVariantList bytes = counter.toList();
|
|
Uuid uuid;
|
|
|
|
if(bytes.size() != 4)
|
|
{
|
|
qWarning() << "Counter ID doesn't count 4 words";
|
|
continue;
|
|
}
|
|
|
|
for(int i = 0; i < 4; ++i)
|
|
{
|
|
uuid.words[i] = bytes[i].toUInt();
|
|
}
|
|
|
|
if(!m_UuidToCounter.contains(uuid))
|
|
continue;
|
|
|
|
selectedCounters.insert(m_UuidToCounter[uuid]);
|
|
}
|
|
|
|
SetSelectedCounters(selectedCounters.toList());
|
|
}
|
|
else
|
|
{
|
|
RDDialog::critical(this, tr("Error loading config"),
|
|
tr("Couldn't open path %1 for reading.").arg(filename));
|
|
}
|
|
}
|
|
void PerformanceCounterSelection::counterTree_contextMenu(const QPoint &pos)
|
|
{
|
|
RDTreeWidgetItem *item = ui->counterTree->itemAt(pos);
|
|
|
|
QMenu contextMenu(this);
|
|
|
|
QAction expandAll(tr("&Expand All"), this);
|
|
QAction collapseAll(tr("&Collapse All"), this);
|
|
|
|
contextMenu.addAction(&expandAll);
|
|
contextMenu.addAction(&collapseAll);
|
|
|
|
expandAll.setIcon(Icons::arrow_out());
|
|
collapseAll.setIcon(Icons::arrow_in());
|
|
|
|
expandAll.setEnabled(item && item->childCount() > 0);
|
|
collapseAll.setEnabled(item && item->childCount() > 0);
|
|
|
|
QObject::connect(&expandAll, &QAction::triggered,
|
|
[this, item]() { ui->counterTree->expandAllItems(item); });
|
|
|
|
QObject::connect(&collapseAll, &QAction::triggered,
|
|
[this, item]() { ui->counterTree->collapseAllItems(item); });
|
|
|
|
RDDialog::show(&contextMenu, ui->counterTree->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
void PerformanceCounterSelection::on_enabledCounters_activated(const QModelIndex &index)
|
|
{
|
|
QListWidgetItem *item = ui->enabledCounters->item(index.row());
|
|
|
|
if(!item)
|
|
return;
|
|
|
|
QVariant counterID = item->data(CounterIdRole);
|
|
|
|
// locate the item in the description tree
|
|
auto it = m_CounterToTreeItem.find((GPUCounter)counterID.toUInt());
|
|
|
|
if(it != m_CounterToTreeItem.end())
|
|
{
|
|
ui->counterTree->setCurrentItem(it.value());
|
|
ui->counterTree->setSelectedItem(it.value());
|
|
|
|
expandToNode(it.value());
|
|
}
|
|
}
|