diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index 5bb6be188..644a096f6 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -153,6 +153,16 @@ bool PersistantConfig::Serialize(const rdcstr &filename) return false; } +struct LegacyData +{ + QString _Android_SDKPath; + QString _Android_JDKPath; + uint32_t _Android_MaxConnectTimeout = 30; + bool _ExternalTool_RGPIntegration = false; + bool _ShaderViewer_FriendlyNaming = true; + QVariantMap _ConfigSettings; +}; + QVariantMap PersistantConfig::storeValues() const { QVariantMap ret; @@ -167,6 +177,15 @@ QVariantMap PersistantConfig::storeValues() const CONFIG_SETTINGS() + // store any legacy values even though we don't need them + + ret[lit("Android_SDKPath")] = m_Legacy->_Android_SDKPath; + ret[lit("Android_JDKPath")] = m_Legacy->_Android_JDKPath; + ret[lit("Android_MaxConnectTimeout")] = m_Legacy->_Android_MaxConnectTimeout; + ret[lit("ExternalTool_RGPIntegration")] = m_Legacy->_ExternalTool_RGPIntegration; + ret[lit("ShaderViewer_FriendlyNaming")] = m_Legacy->_ShaderViewer_FriendlyNaming; + ret[lit("ConfigSettings")] = m_Legacy->_ConfigSettings; + return ret; } @@ -197,6 +216,85 @@ void PersistantConfig::applyValues(const QVariantMap &values) // apply reasonable bounds to font scale to avoid invalid values // 25% - 400% Font_GlobalScale = qBound(0.25f, Font_GlobalScale, 4.0f); + + // port old values that were saved here but are now saved in core. + // We only want to do this once, but we want to leave these values in the config to allow for + // people running old versions after running a new version - we don't want to remove all of their + // settings yet. + // So what we do is store an extra setting indicating that it's been ported - if that setting is + // present we just store/save them blindly. This does mean they can change but we won't re-port + // them. + // After the new config ships in a stable release we can remove this as we don't generally support + // forwards compatibility, only backwards compatibility, so it will be OK to drop the config + // settings. + bool processed = false; + if(values.contains(lit("ConfigSettings")) && + values[lit("ConfigSettings")].toMap().contains(lit("modern.config.ported"))) + processed = true; + + bool saveConfig = false; + +#define CORE_SETTING(variantType, oldName, newName, data) \ + if(values.contains(lit(#oldName))) \ + { \ + m_Legacy->_##oldName = values[lit(#oldName)].value(); \ + if(!processed) \ + { \ + SDObject *setting = RENDERDOC_SetConfigSetting(newName); \ + if(setting) \ + setting->data = m_Legacy->_##oldName; \ + saveConfig = true; \ + } \ + } + + CORE_SETTING(QString, Android_SDKPath, "Android.SDKDirPath", data.str); + CORE_SETTING(QString, Android_JDKPath, "Android.JDKDirPath", data.str); + CORE_SETTING(uint32_t, Android_MaxConnectTimeout, "Android.MaxConnectTimeout", data.basic.u); + CORE_SETTING(bool, ExternalTool_RGPIntegration, "AMD.RGP.Enable", data.basic.b); + CORE_SETTING(bool, ShaderViewer_FriendlyNaming, "DXBC.Disassembly.FriendlyNaming", data.basic.b); + + if(values.contains(lit("ConfigSettings"))) + { + QVariantMap &settings = m_Legacy->_ConfigSettings; + settings = values[lit("ConfigSettings")].toMap(); + + if(!processed) + { + if(settings.contains(lit("shader.debug.searchPaths"))) + { + QStringList searchPaths = settings[lit("shader.debug.searchPaths")].toString().split( + QLatin1Char(';'), QString::SkipEmptyParts); + + SDObject *debug = RENDERDOC_SetConfigSetting("DXBC.Debug.SearchDirPaths"); + + debug->DeleteChildren(); + debug->data.children.resize(searchPaths.size()); + + for(int i = 0; i < searchPaths.size(); i++) + debug->data.children[i] = makeSDString("$el", searchPaths[i]); + } + + if(settings.contains(lit("d3d12ShaderDebugging"))) + { + RENDERDOC_SetConfigSetting("D3D12_ShaderDebugging")->data.basic.b = + settings[lit("d3d12ShaderDebugging")].toBool(); + } + + if(settings.contains(lit("vulkanShaderDebugging"))) + { + RENDERDOC_SetConfigSetting("Vulkan_ShaderDebugging")->data.basic.b = + settings[lit("vulkanShaderDebugging")].toBool(); + } + + saveConfig = true; + } + + // mark the settings as ported so we don't do it again + settings[lit("modern.config.ported")] = true; + } + + if(saveConfig) + RENDERDOC_SaveConfigSettings(); } static QMutex RemoteHostLock; @@ -265,20 +363,6 @@ void PersistantConfig::RemoveRemoteHost(RemoteHost host) void PersistantConfig::UpdateEnumeratedProtocolDevices() { - QString androidSDKPath = (!Android_SDKPath.isEmpty() && QFile::exists(Android_SDKPath)) - ? QString(Android_SDKPath) - : QString(); - - SetConfigSetting("androidSDKPath", androidSDKPath); - - QString androidJDKPath = (!Android_JDKPath.isEmpty() && QFile::exists(Android_JDKPath)) - ? QString(Android_JDKPath) - : QString(); - - SetConfigSetting("androidJDKPath", androidJDKPath); - - SetConfigSetting("MaxConnectTimeout", QString::number(Android_MaxConnectTimeout)); - rdcarray enumeratedDevices; rdcarray protocols; @@ -350,24 +434,20 @@ bool PersistantConfig::SetStyle() return false; } +PersistantConfig::PersistantConfig() +{ + m_Legacy = new LegacyData; +} + PersistantConfig::~PersistantConfig() { + delete m_Legacy; } bool PersistantConfig::Load(const rdcstr &filename) { bool ret = Deserialize(filename); - // perform some sanitisation to make sure config is always in sensible state - for(const rdcstrpair &key : ConfigSettings) - { - // redundantly set each setting so it is flushed to the core dll - SetConfigSetting(key.first, key.second); - } - - RENDERDOC_SetConfigSetting("Disassembly_FriendlyNaming", ShaderViewer_FriendlyNaming ? "1" : "0"); - RENDERDOC_SetConfigSetting("ExternalTool_RGPIntegration", ExternalTool_RGPIntegration ? "1" : "0"); - RDDialog::DefaultBrowsePath = LastFileBrowsePath; // localhost should always be available as a remote host @@ -501,9 +581,6 @@ bool PersistantConfig::Save() if(m_Filename.isEmpty()) return true; - RENDERDOC_SetConfigSetting("Disassembly_FriendlyNaming", ShaderViewer_FriendlyNaming ? "1" : "0"); - RENDERDOC_SetConfigSetting("ExternalTool_RGPIntegration", ExternalTool_RGPIntegration ? "1" : "0"); - LastFileBrowsePath = RDDialog::DefaultBrowsePath; // truncate the lists to a maximum of 9 items, allow more to exist in memory @@ -557,36 +634,6 @@ void AddRecentFile(rdcarray &recentList, const rdcstr &file) recentList.push_back(path); } -void PersistantConfig::SetConfigSetting(const rdcstr &name, const rdcstr &value) -{ - if(name.isEmpty()) - return; - - bool found = false; - for(rdcstrpair &kv : ConfigSettings) - { - if(kv.first == name) - { - kv.second = value; - found = true; - break; - } - } - - if(!found) - ConfigSettings.push_back(make_rdcpair(name, value)); - RENDERDOC_SetConfigSetting(name.data(), value.data()); -} - -rdcstr PersistantConfig::GetConfigSetting(const rdcstr &name) -{ - for(const rdcstrpair &kv : ConfigSettings) - if(kv.first == name) - return kv.second; - - return ""; -} - ShaderProcessingTool::ShaderProcessingTool(const QVariant &var) { QVariantMap map = var.toMap(); diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index a0543f39e..7d08eceeb 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -302,8 +302,6 @@ DECLARE_REFLECTION_STRUCT(BugReport); \ CONFIG_SETTING_VAL(public, bool, bool, TextureViewer_PerTexYFlip, false) \ \ - CONFIG_SETTING_VAL(public, bool, bool, ShaderViewer_FriendlyNaming, true) \ - \ CONFIG_SETTING_VAL(public, bool, bool, AlwaysReplayLocally, false) \ \ CONFIG_SETTING_VAL(public, int, int, LocalProxyAPI, -1) \ @@ -336,12 +334,6 @@ DECLARE_REFLECTION_STRUCT(BugReport); \ CONFIG_SETTING_VAL(public, bool, bool, Font_PreferMonospaced, false) \ \ - CONFIG_SETTING_VAL(public, QString, rdcstr, Android_SDKPath, "") \ - \ - CONFIG_SETTING_VAL(public, QString, rdcstr, Android_JDKPath, "") \ - \ - CONFIG_SETTING_VAL(public, int, int, Android_MaxConnectTimeout, 30) \ - \ CONFIG_SETTING_VAL(public, QDateTime, rdcdatetime, UnsupportedAndroid_LastUpdate, \ rdcdatetime(2015, 01, 01)) \ \ @@ -359,8 +351,6 @@ DECLARE_REFLECTION_STRUCT(BugReport); CONFIG_SETTING_VAL(public, QDateTime, rdcdatetime, DegradedCapture_LastUpdate, \ rdcdatetime(2015, 01, 01)) \ \ - CONFIG_SETTING_VAL(public, bool, bool, ExternalTool_RGPIntegration, false) \ - \ CONFIG_SETTING_VAL(public, QString, rdcstr, ExternalTool_RadeonGPUProfiler, "") \ \ CONFIG_SETTING_VAL(public, bool, bool, Tips_HasSeenFirst, false) \ @@ -385,8 +375,6 @@ DECLARE_REFLECTION_STRUCT(BugReport); \ CONFIG_SETTING(public, QVariantList, rdcarray, AlwaysLoad_Extensions) \ \ - CONFIG_SETTING(private, QVariantMap, rdcstrpairs, ConfigSettings) \ - \ CONFIG_SETTING(private, QVariantList, rdcarray, RemoteHostList) DOCUMENT(R"(The unit that GPU durations are displayed in. @@ -458,6 +446,8 @@ As the name suggests, this is used for tracking a 'recent file' list. )"); void RemoveRecentFile(rdcarray &recentList, const rdcstr &file); +struct LegacyData; + DOCUMENT2(R"(A persistant config file that is automatically loaded and saved, which contains any settings and information that needs to be preserved from one run to the next. @@ -533,13 +523,6 @@ For more information about some of these settings that are user-facing see Defaults to ``False``. -.. data:: ShaderViewer_FriendlyNaming - - ``True`` if the :class:`ShaderViewer` should replace register names with the high-level language - variable names where possible. - - Defaults to ``True``. - .. data:: AlwaysReplayLocally ``True`` if when loading a capture that was originally captured on a remote device but uses an @@ -655,24 +638,6 @@ For more information about some of these settings that are user-facing see Defaults to ``False``. -.. data:: Android_SDKPath - - The path to the root of the android SDK, to locate android tools to use for android remote hosts. - - Defaults to using the tools distributed with RenderDoc. - -.. data:: Android_JDKPath - - The path to the root of the Java JDK, to locate java for running android java tools. - - Defaults to using the JAVA_HOME environment variable, if set. - -.. data:: Android_MaxConnectTimeout - - The maximum timeout in seconds to wait when launching an Android package. - - Defaults to ``30``. - .. data:: UnsupportedAndroid_LastUpdate A date containing the last time that the user was warned about an Android device being older than @@ -713,10 +678,6 @@ 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_RGPIntegration - - Whether to enable integration with the external Radeon GPU Profiler tool. - .. data:: ExternalTool_RadeonGPUProfiler The path to the executable of the external Radeon GPU Profiler tool. @@ -788,7 +749,6 @@ For more information about some of these settings that are user-facing see A list of strings with extension packages to always load on startup, without needing manual enabling. - )"); class PersistantConfig { @@ -822,7 +782,7 @@ public: DOCUMENT(""); CONFIG_SETTINGS() public: - PersistantConfig() {} + PersistantConfig(); ~PersistantConfig(); DOCUMENT(R"(Loads the config from a given filename. This happens automatically on startup, so it's @@ -854,21 +814,6 @@ loading. It can explicitly save and close before relaunching. DOCUMENT("Configures the :class:`Formatter` class with the settings from this config."); void SetupFormatting(); - DOCUMENT(R"(Sets an arbitrary dynamic setting similar to a key-value store. This can be used for -storing custom settings to be persisted without needing to modify code. - -:param str name: The name of the setting. Any existing setting will be overwritten. -:param str value: The contents of the setting. -)"); - void SetConfigSetting(const rdcstr &name, const rdcstr &value); - DOCUMENT(R"(Retrieves an arbitrary dynamic setting. See :meth:`SetConfigSetting`. - -:param str name: The name of the setting. -:return: The value of the setting, or the empty string if the setting did not exist. -:rtype: ``str`` -)"); - rdcstr GetConfigSetting(const rdcstr &name); - DOCUMENT(R"(Sets the UI style to the value in :data:`UIStyle`. Changing the style after the application has started may not properly update everything, so to be @@ -891,4 +836,8 @@ private: #endif rdcstr m_Filename; + + // legacy storage for config settings that were ported into the core config. We keep them here + // so we can store them out again, to keep the config the same for a while even if it's unused + LegacyData *m_Legacy; }; diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp index 3dd77f32d..0b9a395f9 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp @@ -107,6 +107,10 @@ bool PersistantConfig::SetStyle() return false; } +PersistantConfig::PersistantConfig() +{ +} + PersistantConfig::~PersistantConfig() { } @@ -159,15 +163,6 @@ void RemoveRecentFile(rdcarray &recentList, const rdcstr &file) { } -void PersistantConfig::SetConfigSetting(const rdcstr &name, const rdcstr &value) -{ -} - -rdcstr PersistantConfig::GetConfigSetting(const rdcstr &name) -{ - return ""; -} - //////////////////////////////////////////////////////////////////////////////// // RemoteHost.cpp stubs //////////////////////////////////////////////////////////////////////////////// diff --git a/qrenderdoc/Windows/Dialogs/ConfigEditor.cpp b/qrenderdoc/Windows/Dialogs/ConfigEditor.cpp new file mode 100644 index 000000000..d973de35d --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/ConfigEditor.cpp @@ -0,0 +1,701 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 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 "ConfigEditor.h" +#include +#include +#include +#include "Code/QRDUtils.h" +#include "Code/Resources.h" +#include "Widgets/Extended/RDHeaderView.h" +#include "Widgets/Extended/RDLineEdit.h" +#include "Widgets/OrderedListEditor.h" +#include "ui_ConfigEditor.h" + +static QString valueString(const SDObject *o) +{ + if(o->type.basetype == SDBasic::String) + return o->data.str; + + if(o->type.basetype == SDBasic::UnsignedInteger) + return Formatter::Format(o->data.basic.u); + + if(o->type.basetype == SDBasic::SignedInteger) + return Formatter::Format(o->data.basic.i); + + if(o->type.basetype == SDBasic::Float) + return Formatter::Format(o->data.basic.d); + + if(o->type.basetype == SDBasic::Boolean) + return o->data.basic.b ? lit("True") : lit("False"); + + if(o->type.basetype == SDBasic::Array) + return lit("{...}"); + + return lit("??"); +} + +static bool anyChildChanged(const SDObject *o) +{ + SDObject *def = o->FindChild("default"); + SDObject *val = o->FindChild("value"); + + if(val && def) + return !val->HasEqualValue(def); + + for(const SDObject *c : o->data.children) + { + if(anyChildChanged(c)) + return true; + } + + return false; +} + +class SettingModel : public QAbstractItemModel +{ +public: + SettingModel(ConfigEditor *view) : QAbstractItemModel(view), m_Viewer(view) + { + populateParents(m_Viewer->m_Config, QModelIndex()); + } + + void refresh() + { + emit beginResetModel(); + emit endResetModel(); + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override + { + SDObject *o = obj(parent); + + if(row < 0 || row > rowCount(parent)) + return QModelIndex(); + + return createIndex(row, column, o->data.children[row]); + } + + QModelIndex parent(const QModelIndex &index) const override + { + SDObject *o = obj(index); + + if(o == m_Viewer->m_Config) + return QModelIndex(); + + QModelIndex ret = parents[o]; + + if(!ret.isValid()) + return ret; + + return createIndex(ret.row(), index.column(), ret.internalPointer()); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + SDObject *o = obj(parent); + + // values don't have children + if(o->FindChild("value")) + return 0; + + return o->data.children.count(); + } + + enum Columns + { + Column_Name, + Column_Value, + Column_ResetButton, + Column_Count, + }; + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + if(!index.isValid()) + return 0; + + Qt::ItemFlags ret = QAbstractItemModel::flags(index); + + if(index.column() == Column_Value) + { + SDObject *o = obj(index); + SDObject *value = o->FindChild("value"); + + if(value) + { + ret |= Qt::ItemIsEditable; + if(value->type.basetype == SDBasic::Boolean) + ret |= Qt::ItemIsUserCheckable; + } + } + + return ret; + } + + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return Column_Count; } + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch(section) + { + case Column_Name: return lit("Setting"); + case Column_Value: return lit("Value"); + case Column_ResetButton: return lit("Reset"); + default: break; + } + } + + return QVariant(); + } + + bool setData(const QModelIndex &index, const QVariant &val, int role) override + { + SDObject *o = NULL; + + if(role == Qt::UserRole) + { + // if we have setData for user role, that means we got reset. Just need to emit data changed + o = obj(index); + } + else if(index.column() == Column_Value) + { + o = obj(index); + + SDObject *value = o->FindChild("value"); + + if(role == Qt::CheckStateRole && value) + { + value->data.basic.b = (val.toInt() == Qt::CheckState::Checked); + } + else + { + // didn't change anything we care about + o = NULL; + } + } + + if(o) + { + // dataChanged this index and all parents (in case a section became non-customised, or + // customised, and it wasn't before) + QModelIndex idx = index; + while(idx.isValid()) + { + o = obj(idx); + emit dataChanged(createIndex(idx.row(), 0, o), createIndex(idx.row(), Column_Count, o), + {Qt::DisplayRole, Qt::CheckStateRole, Qt::FontRole}); + + idx = parents[o]; + } + + return true; + } + + return false; + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if(index.isValid()) + { + SDObject *o = obj(index); + int col = index.column(); + + SDObject *value = o->FindChild("value"); + + if(role == Qt::UserRole) + { + return QVariant::fromValue((quintptr)o); + } + else if(role == Qt::DisplayRole) + { + switch(col) + { + case Column_Name: return o->name; + case Column_Value: + return value && value->type.basetype != SDBasic::Boolean ? valueString(value) + : QVariant(); + case Column_ResetButton: return anyChildChanged(o) ? lit("...") : QVariant(); + default: break; + } + } + else if(role == Qt::CheckStateRole && col == Column_Value) + { + if(value && value->type.basetype == SDBasic::Boolean) + return value->data.basic.b ? Qt::CheckState::Checked : Qt::CheckState::Unchecked; + return QVariant(); + } + else if(role == Qt::TextAlignmentRole && col == Column_ResetButton) + { + return Qt::AlignHCenter + Qt::AlignTop; + } + else if(role == Qt::ToolTipRole) + { + SDObject *desc = o->FindChild("description"); + if(desc) + return desc->AsString(); + } + else if(role == Qt::FontRole) + { + if(anyChildChanged(o)) + { + QFont font; + font.setBold(true); + return font; + } + } + } + + return QVariant(); + } + +private: + SDObject *obj(const QModelIndex &parent) const + { + SDObject *ret = (SDObject *)parent.internalPointer(); + if(ret == NULL) + ret = m_Viewer->m_Config; + return ret; + } + + // Qt models need child->parent relationships. We don't have that with SDObject but they are + // immutable so we can cache them + QMap parents; + + void populateParents(SDObject *o, QModelIndex parent) + { + if(o->FindChild("value")) + return; + + int i = 0; + for(SDObject *c : o->GetChildren()) + { + parents[c] = parent; + populateParents(c, index(i++, 0, parent)); + } + } + + ConfigEditor *m_Viewer; +}; + +class SettingFilterModel : public QSortFilterProxyModel +{ +public: + explicit SettingFilterModel(ConfigEditor *view) : QSortFilterProxyModel(view), m_Viewer(view) {} + void setFilter(QString text) + { + m_Text = text; + m_KeyText = m_Text; + m_KeyText.replace(QLatin1Char('.'), QLatin1Char('_')); + emit beginResetModel(); + emit endResetModel(); + } + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override + { + if(m_Text.isEmpty()) + return true; + + SDObject *o = obj(source_parent); + + return matchesAnyChild(o->data.children[source_row]); + } + + bool matchesAnyChild(SDObject *o) const + { + if(QString(o->name).contains(m_Text, Qt::CaseInsensitive)) + return true; + + if(o->FindChild("value")) + { + if(QString(o->FindChild("key")->AsString()).contains(m_KeyText, Qt::CaseInsensitive)) + return true; + + return false; + } + + for(SDObject *c : o->GetChildren()) + if(matchesAnyChild(c)) + return true; + + return false; + } + +private: + SDObject *obj(const QModelIndex &parent) const + { + SDObject *ret = (SDObject *)parent.internalPointer(); + if(ret == NULL) + ret = m_Viewer->m_Config; + return ret; + } + ConfigEditor *m_Viewer; + + QString m_Text; + QString m_KeyText; +}; + +SettingDelegate::SettingDelegate(ConfigEditor *editor, RDTreeView *parent) + : QStyledItemDelegate(parent), m_Editor(editor), m_View(parent) +{ +} + +SettingDelegate::~SettingDelegate() +{ +} + +void SettingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if(index.column() == SettingModel::Column_ResetButton) + { + SDObject *o = (SDObject *)index.data(Qt::UserRole).toULongLong(); + + SDObject *def = o->FindChild("default"); + SDObject *val = o->FindChild("value"); + + if(val && def && !val->HasEqualValue(def)) + { + // draw the item without text, so we get the proper background/selection/etc. + // we'd like to be able to use the parent delegate's paint here, but either it calls to + // QStyledItemDelegate which will re-fetch the text (bleh), or it calls to the manual + // delegate which could do anything. So for this case we just use the style and skip the + // delegate and hope it works out. + QStyleOptionViewItem opt = option; + QStyledItemDelegate::initStyleOption(&opt, index); + opt.text.clear(); + m_Editor->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, m_Editor); + + QStyleOptionToolButton buttonOpt; + + int size = m_Editor->style()->pixelMetric(QStyle::PM_SmallIconSize, 0, m_Editor); + + buttonOpt.iconSize = QSize(size, size); + buttonOpt.subControls = 0; + buttonOpt.activeSubControls = 0; + buttonOpt.features = QStyleOptionToolButton::None; + buttonOpt.arrowType = Qt::NoArrow; + buttonOpt.state = QStyle::State_Active | QStyle::State_Enabled | QStyle::State_AutoRaise; + + buttonOpt.rect = option.rect.adjusted(0, 0, -1, -1); + buttonOpt.icon = Icons::arrow_undo(); + + if(m_View->currentHoverIndex() == index) + buttonOpt.state |= QStyle::State_MouseOver; + + m_Editor->style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, painter, m_Editor); + return; + } + } + + return QStyledItemDelegate::paint(painter, option, index); +} + +QSize SettingDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + return QStyledItemDelegate::sizeHint(option, index); +} + +bool SettingDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if(event->type() == QEvent::MouseButtonRelease && index.column() == SettingModel::Column_ResetButton) + { + SDObject *o = (SDObject *)index.data(Qt::UserRole).toULongLong(); + + SDObject *def = o->FindChild("default"); + SDObject *val = o->FindChild("value"); + + if(def && val) + { + val->data.str = def->data.str; + memcpy(&val->data.basic, &def->data.basic, sizeof(val->data.basic)); + + val->DeleteChildren(); + + for(size_t c = 0; c < def->data.children.size(); c++) + val->data.children.push_back(def->data.children[c]->Duplicate()); + + // call setData() to emit the dataChanged for this element and all parents + model->setData(index, QVariant(), Qt::UserRole); + + return true; + } + } + + return QStyledItemDelegate::editorEvent(event, model, option, index); +} + +QWidget *SettingDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QWidget *ret = NULL; + + SDObject *o = (SDObject *)index.data(Qt::UserRole).toULongLong(); + SDObject *val = o->FindChild("value"); + if(val) + { + // bools should have checkboxes + if(val->type.basetype == SDBasic::Boolean) + { + qWarning() << "Unexpected createEditor for boolean " << QString(o->name); + return ret; + } + + // for numbers, provide a spinbox + if(val->type.basetype == SDBasic::UnsignedInteger || val->type.basetype == SDBasic::SignedInteger) + { + QSpinBox *spin = new QSpinBox(parent); + ret = spin; + if(val->type.basetype == SDBasic::UnsignedInteger) + spin->setMinimum(0); + else + spin->setMinimum(INT_MIN); + spin->setMaximum(INT_MAX); + } + else if(val->type.basetype == SDBasic::Float) + { + QDoubleSpinBox *spin = new QDoubleSpinBox(parent); + ret = spin; + spin->setSingleStep(0.1); + spin->setMinimum(-FLT_MAX); + spin->setMaximum(FLT_MAX); + } + else if(val->type.basetype == SDBasic::String) + { + if(QString(o->name).contains(lit("DirPath"), Qt::CaseSensitive)) + { + QString dir = RDDialog::getExistingDirectory( + m_Editor, tr("Browse for %1").arg(o->FindChild("key")->AsString())); + + if(!dir.isEmpty()) + { + val->data.str = dir; + + // we've handled the edit synchronously, don't create an edit widget + ret = NULL; + + // call setData() to emit the dataChanged for this element and all parents + m_View->model()->setData(index, QVariant(), Qt::UserRole); + } + } + else if(QString(o->name).contains(lit("Path"), Qt::CaseSensitive)) + { + QString file = RDDialog::getOpenFileName( + m_Editor, tr("Browse for %1").arg(o->FindChild("key")->AsString())); + + if(!file.isEmpty()) + { + val->data.str = file; + + // we've handled the edit synchronously, don't create an edit widget + ret = NULL; + + // call setData() to emit the dataChanged for this element and all parents + m_View->model()->setData(index, QVariant(), Qt::UserRole); + } + } + else + { + RDLineEdit *line = new RDLineEdit(parent); + ret = line; + + QObject::connect(line, &RDLineEdit::keyPress, this, &SettingDelegate::editorKeyPress); + } + } + else if(val->type.basetype == SDBasic::Array) + { + // only support arrays of strings. Pop up a separate editor to handle this + QDialog listEditor; + + listEditor.setWindowTitle(tr("Edit values of %1").arg(QString(o->FindChild("key")->AsString()))); + listEditor.setWindowFlags(listEditor.windowFlags() & ~Qt::WindowContextHelpButtonHint); + + BrowseMode mode = BrowseMode::None; + + if(QString(o->name).contains(lit("DirPath"), Qt::CaseSensitive)) + mode = BrowseMode::Folder; + else if(QString(o->name).contains(lit("Path"), Qt::CaseSensitive)) + mode = BrowseMode::File; + + OrderedListEditor list(tr("Entry"), mode); + + QVBoxLayout layout; + QDialogButtonBox okCancel; + okCancel.setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + layout.addWidget(&list); + layout.addWidget(&okCancel); + + QObject::connect(&okCancel, &QDialogButtonBox::accepted, &listEditor, &QDialog::accept); + QObject::connect(&okCancel, &QDialogButtonBox::rejected, &listEditor, &QDialog::reject); + + listEditor.setLayout(&layout); + + QStringList items; + + for(SDObject *c : val->data.children) + items << c->data.str; + + list.setItems(items); + + int res = RDDialog::show(&listEditor); + + if(res) + { + items = list.getItems(); + + val->DeleteChildren(); + val->data.children.resize(items.size()); + + for(int i = 0; i < items.size(); i++) + val->data.children[i] = makeSDString("$el", items[i]); + } + + // we've handled the edit synchronously, don't create an edit widget + ret = NULL; + + // call setData() to emit the dataChanged for this element and all parents + m_View->model()->setData(index, QVariant(), Qt::UserRole); + } + else + { + qWarning() << "Unexpected type of " << QString(o->name) + << " to edit: " << ToQStr(val->type.basetype); + } + } + + return ret; +} + +void SettingDelegate::editorKeyPress(QKeyEvent *ev) +{ + QLineEdit *line = qobject_cast(QObject::sender()); + + if(ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Enter) + { + commitData(line); + closeEditor(line); + } + else if(ev->key() == Qt::Key_Escape) + { + closeEditor(line); + } +} + +void SettingDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + SDObject *o = (SDObject *)index.data(Qt::UserRole).toULongLong(); + SDObject *val = o->FindChild("value"); + if(val) + { + if(val->type.basetype == SDBasic::Boolean) + { + qWarning() << "Unexpected setEditorData for boolean " << QString(o->name); + return; + } + + if(val->type.basetype == SDBasic::UnsignedInteger) + qobject_cast(editor)->setValue(val->AsUInt32() & 0x7fffffffU); + else if(val->type.basetype == SDBasic::SignedInteger) + qobject_cast(editor)->setValue(val->AsInt32()); + else if(val->type.basetype == SDBasic::Float) + qobject_cast(editor)->setValue(val->AsDouble()); + else if(val->type.basetype == SDBasic::String) + qobject_cast(editor)->setText(val->AsString()); + else + qWarning() << "Unexpected type of " << QString(o->name) << ": " << ToQStr(val->type.basetype); + } +} + +void SettingDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + SDObject *o = (SDObject *)index.data(Qt::UserRole).toULongLong(); + SDObject *val = o->FindChild("value"); + if(val) + { + if(val->type.basetype == SDBasic::Boolean) + { + qWarning() << "Unexpected setModelData for boolean " << QString(o->name); + return; + } + + if(val->type.basetype == SDBasic::UnsignedInteger) + val->data.basic.u = qMax(0, qobject_cast(editor)->value()); + else if(val->type.basetype == SDBasic::SignedInteger) + val->data.basic.i = qobject_cast(editor)->value(); + else if(val->type.basetype == SDBasic::Float) + val->data.basic.d = qobject_cast(editor)->value(); + else if(val->type.basetype == SDBasic::String) + val->data.str = qobject_cast(editor)->text(); + else + qWarning() << "Unexpected type of " << QString(o->name) << ": " << ToQStr(val->type.basetype); + } +} + +ConfigEditor::ConfigEditor(QWidget *parent) : QDialog(parent), ui(new Ui::ConfigEditor) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + m_Config = RENDERDOC_SetConfigSetting(""); + + m_SettingModel = new SettingModel(this); + m_FilterModel = new SettingFilterModel(this); + + m_FilterModel->setSourceModel(m_SettingModel); + ui->settings->setModel(m_FilterModel); + + { + RDHeaderView *header = new RDHeaderView(Qt::Horizontal, ui->settings); + ui->settings->setHeader(header); + + header->setColumnStretchHints({-1, 1, -1}); + } + + ui->settings->setItemDelegate(new SettingDelegate(this, ui->settings)); +} + +ConfigEditor::~ConfigEditor() +{ + delete ui; +} + +void ConfigEditor::on_filter_textChanged(const QString &text) +{ + RDTreeViewExpansionState state; + ui->settings->saveExpansion(state, 0); + m_FilterModel->setFilter(text); + ui->settings->applyExpansion(state, 0); +} + +void ConfigEditor::keyPressEvent(QKeyEvent *e) +{ + if(e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) + return; + QDialog::keyPressEvent(e); +} diff --git a/qrenderdoc/Windows/Dialogs/ConfigEditor.h b/qrenderdoc/Windows/Dialogs/ConfigEditor.h new file mode 100644 index 000000000..598649f4a --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/ConfigEditor.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 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 +#include +#include +#include + +struct SDObject; + +namespace Ui +{ +class ConfigEditor; +} + +class ConfigEditor; +class SettingModel; +class SettingFilterModel; +class RDTreeView; + +class SettingDelegate : public QStyledItemDelegate +{ + Q_OBJECT + + ConfigEditor *m_Editor; + RDTreeView *m_View; + +public: + explicit SettingDelegate(ConfigEditor *editor, RDTreeView *parent); + ~SettingDelegate(); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, + const QModelIndex &index) override; + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const override; + +public slots: + void editorKeyPress(QKeyEvent *ev); +}; + +class ConfigEditor : public QDialog +{ + Q_OBJECT + +public: + explicit ConfigEditor(QWidget *parent = 0); + ~ConfigEditor(); + +private slots: + // automatic slots + void on_filter_textChanged(const QString &text); + +private: + void keyPressEvent(QKeyEvent *e) override; + + SettingModel *m_SettingModel = NULL; + SettingFilterModel *m_FilterModel = NULL; + + SDObject *m_Config = NULL; + + friend class SettingModel; + friend class SettingFilterModel; + friend class SettingDelegate; + + Ui::ConfigEditor *ui; +}; diff --git a/qrenderdoc/Windows/Dialogs/ConfigEditor.ui b/qrenderdoc/Windows/Dialogs/ConfigEditor.ui new file mode 100644 index 000000000..a5afc825a --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/ConfigEditor.ui @@ -0,0 +1,136 @@ + + + ConfigEditor + + + Qt::ApplicationModal + + + + 0 + 0 + 800 + 600 + + + + Advanced Config Editor + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 106 + 104 + 100 + + + + + + + + + 14 + + + + Editing any of these settings could cause crashes or unpredictable behaviour. Be sure you know what you are doing before editing any of them! + + + true + + + + + + + Filter the settings + + + + + + + + + + QDialogButtonBox::Close + + + true + + + + + + + + RDTreeView + QTreeView +
Widgets/Extended/RDTreeView.h
+
+
+ + + + buttons + accepted() + ConfigEditor + accept() + + + 225 + 379 + + + 225 + 200 + + + + + buttons + rejected() + ConfigEditor + reject() + + + 225 + 379 + + + 225 + 200 + + + + +
diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp index ba2911c01..20871f309 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp @@ -34,6 +34,7 @@ #include "Widgets/OrderedListEditor.h" #include "Widgets/ReplayOptionsSelector.h" #include "CaptureDialog.h" +#include "ConfigEditor.h" #include "ui_SettingsDialog.h" SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) @@ -121,17 +122,19 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) ui->deleteShaderTool->setEnabled(false); ui->editShaderTool->setEnabled(false); - ui->ExternalTool_RGPIntegration->setChecked(m_Ctx.Config().ExternalTool_RGPIntegration); + ui->ExternalTool_RGPIntegration->setChecked(RENDERDOC_GetConfigSetting("AMD.RGP.Enable")->AsBool()); 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); + ui->Android_SDKPath->setText(RENDERDOC_GetConfigSetting("Android.SDKDirPath")->AsString()); + ui->Android_JDKPath->setText(RENDERDOC_GetConfigSetting("Android.JDKDirPath")->AsString()); + ui->Android_MaxConnectTimeout->setValue( + RENDERDOC_GetConfigSetting("Android.MaxConnectTimeout")->AsUInt32()); ui->TextureViewer_ResetRange->setChecked(m_Ctx.Config().TextureViewer_ResetRange); ui->TextureViewer_PerTexSettings->setChecked(m_Ctx.Config().TextureViewer_PerTexSettings); ui->TextureViewer_PerTexYFlip->setChecked(m_Ctx.Config().TextureViewer_PerTexYFlip); - ui->ShaderViewer_FriendlyNaming->setChecked(m_Ctx.Config().ShaderViewer_FriendlyNaming); + ui->ShaderViewer_FriendlyNaming->setChecked( + RENDERDOC_GetConfigSetting("DXBC.Disassembly.FriendlyNaming")->AsBool()); ui->CheckUpdate_AllowChecks->setChecked(m_Ctx.Config().CheckUpdate_AllowChecks); ui->Font_PreferMonospaced->setChecked(m_Ctx.Config().Font_PreferMonospaced); @@ -411,6 +414,15 @@ void SettingsDialog::on_analyticsDescribeLabel_linkActivated(const QString &link } // core +void SettingsDialog::on_configEditor_clicked() +{ + ConfigEditor editor; + + RDDialog::show(&editor); + + RENDERDOC_SaveConfigSettings(); +} + void SettingsDialog::on_chooseSearchPaths_clicked() { QDialog listEditor; @@ -431,22 +443,38 @@ void SettingsDialog::on_chooseSearchPaths_clicked() listEditor.setLayout(&layout); - QString setting = m_Ctx.Config().GetConfigSetting("shader.debug.searchPaths"); + const SDObject *getPaths = RENDERDOC_GetConfigSetting("DXBC.Debug.SearchDirPaths"); - list.setItems(setting.split(QLatin1Char(';'), QString::SkipEmptyParts)); + QStringList items; + + for(SDObject *c : getPaths->data.children) + items << c->data.str; + + list.setItems(items); int res = RDDialog::show(&listEditor); if(res) - m_Ctx.Config().SetConfigSetting(lit("shader.debug.searchPaths"), - list.getItems().join(QLatin1Char(';'))); + { + items = list.getItems(); + + SDObject *setPaths = RENDERDOC_SetConfigSetting("DXBC.Debug.SearchDirPaths"); + + setPaths->DeleteChildren(); + setPaths->data.children.resize(items.size()); + + for(int i = 0; i < items.size(); i++) + setPaths->data.children[i] = makeSDString("$el", items[i]); + + RENDERDOC_SaveConfigSettings(); + } } void SettingsDialog::on_ExternalTool_RGPIntegration_toggled(bool checked) { - m_Ctx.Config().ExternalTool_RGPIntegration = checked; + RENDERDOC_SetConfigSetting("AMD.RGP.Enable")->data.basic.b = checked; - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); } void SettingsDialog::on_ExternalTool_RadeonGPUProfiler_textEdited(const QString &rgp) @@ -499,9 +527,9 @@ void SettingsDialog::on_TextureViewer_ResetRange_toggled(bool checked) // shader viewer void SettingsDialog::on_ShaderViewer_FriendlyNaming_toggled(bool checked) { - m_Ctx.Config().ShaderViewer_FriendlyNaming = ui->ShaderViewer_FriendlyNaming->isChecked(); + RENDERDOC_SetConfigSetting("DXBC.Disassembly.FriendlyNaming")->data.basic.b = checked; - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); } void SettingsDialog::addProcessor(const ShaderProcessingTool &tool) @@ -926,55 +954,60 @@ void SettingsDialog::on_browseTempCaptureDirectory_clicked() void SettingsDialog::on_browseAndroidSDKPath_clicked() { - QString adb = RDDialog::getExistingDirectory( + QString sdk = RDDialog::getExistingDirectory( this, tr("Locate SDK root folder (containing build-tools, platform-tools)"), - QFileInfo(m_Ctx.Config().Android_SDKPath).absoluteDir().path()); + QFileInfo(RENDERDOC_GetConfigSetting("Android.SDKDirPath")->AsString()).absoluteDir().path()); - if(!adb.isEmpty()) + if(!sdk.isEmpty()) { - ui->Android_SDKPath->setText(adb); - m_Ctx.Config().Android_SDKPath = adb; - } + ui->Android_SDKPath->setText(sdk); + RENDERDOC_SetConfigSetting("Android.SDKDirPath")->data.str = sdk; - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); + } } -void SettingsDialog::on_Android_SDKPath_textEdited(const QString &adb) +void SettingsDialog::on_Android_SDKPath_textEdited(const QString &sdk) { - if(QFileInfo::exists(adb) || adb.isEmpty()) - m_Ctx.Config().Android_SDKPath = adb; + if(QFileInfo::exists(sdk) || sdk.isEmpty()) + { + RENDERDOC_SetConfigSetting("Android.SDKDirPath")->data.str = sdk; - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); + } } void SettingsDialog::on_browseJDKPath_clicked() { - QString adb = - RDDialog::getExistingDirectory(this, tr("Locate JDK root folder (containing bin, jre, lib)"), - QFileInfo(m_Ctx.Config().Android_JDKPath).absoluteDir().path()); + QString jdk = RDDialog::getExistingDirectory( + this, tr("Locate JDK root folder (containing bin, jre, lib)"), + QFileInfo(RENDERDOC_GetConfigSetting("Android.JDKDirPath")->AsString()).absoluteDir().path()); - if(!adb.isEmpty()) + if(!jdk.isEmpty()) { - ui->Android_JDKPath->setText(adb); - m_Ctx.Config().Android_JDKPath = adb; - } + ui->Android_JDKPath->setText(jdk); + RENDERDOC_SetConfigSetting("Android.JDKDirPath")->data.str = jdk; - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); + } } -void SettingsDialog::on_Android_JDKPath_textEdited(const QString &adb) +void SettingsDialog::on_Android_JDKPath_textEdited(const QString &jdk) { - if(QFileInfo::exists(adb) || adb.isEmpty()) - m_Ctx.Config().Android_JDKPath = adb; + if(QFileInfo::exists(jdk) || jdk.isEmpty()) + { + RENDERDOC_SetConfigSetting("Android.JDKDirPath")->data.str = jdk; - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); + } } void SettingsDialog::on_Android_MaxConnectTimeout_valueChanged(double timeout) { - m_Ctx.Config().Android_MaxConnectTimeout = ui->Android_MaxConnectTimeout->value(); + RENDERDOC_SetConfigSetting("Android.MaxConnectTimeout")->data.basic.u = + (uint32_t)ui->Android_MaxConnectTimeout->value(); - m_Ctx.Config().Save(); + RENDERDOC_SaveConfigSettings(); } void SettingsDialog::on_UIStyle_currentIndexChanged(int index) diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.h b/qrenderdoc/Windows/Dialogs/SettingsDialog.h index 5809672c4..149b1705e 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.h +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.h @@ -71,6 +71,7 @@ private slots: void on_analyticsOptOut_toggled(bool checked); // core + void on_configEditor_clicked(); void on_chooseSearchPaths_clicked(); void on_ExternalTool_RGPIntegration_toggled(bool checked); void on_ExternalTool_RadeonGPUProfiler_textEdited(const QString &rgp); @@ -108,8 +109,8 @@ private slots: void on_browseAndroidSDKPath_clicked(); void on_browseJDKPath_clicked(); void on_Android_MaxConnectTimeout_valueChanged(double timeout); - void on_Android_SDKPath_textEdited(const QString &path); - void on_Android_JDKPath_textEdited(const QString &path); + void on_Android_SDKPath_textEdited(const QString &sdk); + void on_Android_JDKPath_textEdited(const QString &jdk); // manual slots void formatter_valueChanged(int value); diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui index b3c389e6e..84594e55a 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui @@ -482,27 +482,7 @@ e.g. 1000 * 10 = 1e4 Core - - - - - 0 - 0 - - - - Shader debug search paths - - - - - - - Choose paths - - - - + Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. @@ -512,24 +492,14 @@ e.g. 1000 * 10 = 1e4 - + Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. - - - - Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. - - - Browse - - - - + Qt::Vertical @@ -542,7 +512,49 @@ e.g. 1000 * 10 = 1e4 + + + + Locates the RadeonGPUProfiler.exe which will be used to interop with when generating and opening RGP profiles. + + + Browse + + + + + + + RenderDoc can optionally have integration with AMD's Radeon GPU Profiler, to allow capturing RGP from RenderDoc and allowing interop between the two. + +After interop is enabled you will need to reload any capture. + + + Enable Radeon GPU Profiler integration (requires capture reload) + + + + + + + + 0 + 0 + + + + Shader debug search paths + + + + + + Choose paths + + + + RenderDoc can optionally have integration with AMD's Radeon GPU Profiler, to allow capturing RGP from RenderDoc and allowing interop between the two. @@ -554,15 +566,17 @@ After interop is enabled you will need to reload any capture. - - - - RenderDoc can optionally have integration with AMD's Radeon GPU Profiler, to allow capturing RGP from RenderDoc and allowing interop between the two. - -After interop is enabled you will need to reload any capture. - + + - Enable Radeon GPU Profiler integration (requires capture reload) + Config Editor + + + + + + + Open Advanced Config Editor diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 77fe2c8de..9d494fce2 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -237,6 +237,7 @@ SOURCES += Code/qrenderdoc.cpp \ Widgets/FindReplace.cpp \ Widgets/Extended/RDSplitter.cpp \ Windows/Dialogs/TipsDialog.cpp \ + Windows/Dialogs/ConfigEditor.cpp \ Windows/PythonShell.cpp \ Windows/Dialogs/PerformanceCounterSelection.cpp \ Windows/PerformanceCounterViewer.cpp \ @@ -315,6 +316,7 @@ HEADERS += Code/CaptureContext.h \ Widgets/FindReplace.h \ Widgets/Extended/RDSplitter.h \ Windows/Dialogs/TipsDialog.h \ + Windows/Dialogs/ConfigEditor.h \ Windows/PythonShell.h \ Windows/Dialogs/PerformanceCounterSelection.h \ Windows/PerformanceCounterViewer.h \ @@ -356,6 +358,7 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/Dialogs/EnvironmentEditor.ui \ Widgets/FindReplace.ui \ Windows/Dialogs/TipsDialog.ui \ + Windows/Dialogs/ConfigEditor.ui \ Windows/PythonShell.ui \ Windows/Dialogs/PerformanceCounterSelection.ui \ Windows/PerformanceCounterViewer.ui \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index ee78d985a..bb56be906 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -663,6 +663,7 @@ + @@ -738,6 +739,7 @@ + @@ -957,6 +959,7 @@ + @@ -1262,6 +1265,12 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) + "$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015" -I"$(ProjectDir)3rdparty\qt\include" -I"$(ProjectDir)3rdparty\qt\include\QtWidgets" -I"$(ProjectDir)3rdparty\qt\include\QtGui" -I"$(ProjectDir)3rdparty\qt\include\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" + MOC %(Filename).h + $(IntDir)generated\moc_%(Filename).cpp + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs) "$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015" -I"$(ProjectDir)3rdparty\qt\include" -I"$(ProjectDir)3rdparty\qt\include\QtWidgets" -I"$(ProjectDir)3rdparty\qt\include\QtGui" -I"$(ProjectDir)3rdparty\qt\include\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" @@ -1540,6 +1549,12 @@ UIC %(Filename).ui $(IntDir)generated\ui_%(Filename).h + + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) + "$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" + UIC %(Filename).ui + $(IntDir)generated\ui_%(Filename).h + %(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs) "$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 2afc0973b..3fb83c5ce 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -729,6 +729,12 @@ Code + + Generated Files + + + Windows\Dialogs + @@ -1088,6 +1094,9 @@ Generated Files + + Generated Files + @@ -1478,7 +1487,15 @@ Widgets - + + Widgets + + + Windows\Dialogs + + + Windows\Dialogs + diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index ce154f132..3415e8fc9 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -132,6 +132,8 @@ set(sources core/target_control.cpp core/remote_server.cpp core/remote_server.h + core/settings.cpp + core/settings.h core/replay_proxy.cpp core/replay_proxy.h core/intervals.h diff --git a/renderdoc/android/android.cpp b/renderdoc/android/android.cpp index c5ebc5db7..9b5937ad4 100644 --- a/renderdoc/android/android.cpp +++ b/renderdoc/android/android.cpp @@ -30,10 +30,15 @@ #include "common/threading.h" #include "core/core.h" #include "core/remote_server.h" +#include "core/settings.h" #include "replay/replay_driver.h" #include "strings/string_utils.h" #include "android_utils.h" +RDOC_CONFIG(uint32_t, Android_MaxConnectTimeout, 30, + "Maximum time in seconds to try connecting to the target app before giving up. " + "Useful primarily for apps that take a very long time to start up."); + namespace Android { void adbForwardPorts(uint16_t portbase, const rdcstr &deviceID, uint16_t jdwpPort, int pid, @@ -1166,10 +1171,7 @@ ExecuteResult AndroidRemoteServer::ExecuteAndInject(const char *a, const char *w ret.status = ReplayStatus::InjectionFailed; - uint32_t elapsed = 0, - timeout = - 1000 * - RDCMAX(5, atoi(RenderDoc::Inst().GetConfigSetting("MaxConnectTimeout").c_str())); + uint32_t elapsed = 0, timeout = 1000 * RDCMAX(5U, Android_MaxConnectTimeout); while(elapsed < timeout) { // Check if the target app has started yet and we can connect to it. diff --git a/renderdoc/android/android_tools.cpp b/renderdoc/android/android_tools.cpp index 6416ddbdd..7c62cd114 100644 --- a/renderdoc/android/android_tools.cpp +++ b/renderdoc/android/android_tools.cpp @@ -24,9 +24,18 @@ #include "common/formatting.h" #include "core/core.h" +#include "core/settings.h" #include "strings/string_utils.h" #include "android_utils.h" +RDOC_CONFIG(rdcstr, Android_SDKDirPath, "", + "The location of the root of the Android SDK. This path " + "should contain folders such as build-tools and platform-tools."); + +RDOC_CONFIG(rdcstr, Android_JDKDirPath, "", + "The location of the root of the Java JDK. This path " + "should contain folders such as bin and lib."); + namespace Android { static bool adbKillServer = false; @@ -178,8 +187,8 @@ rdcstr getToolPath(ToolDir subdir, const rdcstr &toolname, bool checkExist) // its client-server setup, so if we run our bundled adb that might be newer than the user's, they // will then get fighting back and forth when trying to run their own. - rdcstr sdk = RenderDoc::Inst().GetConfigSetting("androidSDKPath"); - rdcstr jdk = RenderDoc::Inst().GetConfigSetting("androidJDKPath"); + rdcstr sdk = Android_SDKDirPath; + rdcstr jdk = Android_JDKDirPath; ToolPathCache &cache = getCache(); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index e65b7db83..0dc00c04e 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -1987,12 +1987,32 @@ DOCUMENT(R"(Returns the current process's memory usage in bytes )"); extern "C" RENDERDOC_API uint64_t RENDERDOC_CC RENDERDOC_GetCurrentProcessMemoryUsage(); -DOCUMENT("Internal function for retrieving a config setting."); -extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetConfigSetting(const char *name); +DOCUMENT(R"(Return a read-only handle to the :class:`SDObject` corresponding to a given setting's +value object. -DOCUMENT("Internal function for setting a config setting."); -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SetConfigSetting(const char *name, - const char *value); +If an empty string is passed, the root object is returned containing all settings and setting +categories. Categories contain other categories and settings, settings contain children that include +the setting's value, description, etc. + +If no such setting exists, `None` is returned. + +:return: The specified setting. +:rtype: ``SDObject`` +)"); +extern "C" RENDERDOC_API const SDObject *RENDERDOC_CC RENDERDOC_GetConfigSetting(const char *name); + +DOCUMENT(R"(Return a mutable handle to the :class:`SDObject` corresponding to a given setting's +value object. + +If no such setting exists, `None` is returned. + +:return: The specified setting. +:rtype: ``SDObject`` +)"); +extern "C" RENDERDOC_API SDObject *RENDERDOC_CC RENDERDOC_SetConfigSetting(const char *name); + +DOCUMENT("Internal function for saving config settings."); +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SaveConfigSettings(); DOCUMENT("Internal function for setting UI theme colors."); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SetColors(FloatVector darkChecker, diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index b9c1af58c..2d4c8b198 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -22,6 +22,27 @@ * THE SOFTWARE. ******************************************************************************/ +template <> +rdcstr DoStringise(const SDBasic &el) +{ + BEGIN_ENUM_STRINGISE(SDBasic); + { + STRINGISE_ENUM_CLASS(Chunk); + STRINGISE_ENUM_CLASS(Struct); + STRINGISE_ENUM_CLASS(Array); + STRINGISE_ENUM_CLASS(Null); + STRINGISE_ENUM_CLASS(Buffer); + STRINGISE_ENUM_CLASS(String); + STRINGISE_ENUM_CLASS(Enum); + STRINGISE_ENUM_CLASS(UnsignedInteger); + STRINGISE_ENUM_CLASS(SignedInteger); + STRINGISE_ENUM_CLASS(Float); + STRINGISE_ENUM_CLASS(Boolean); + STRINGISE_ENUM_CLASS(Character); + } + END_ENUM_STRINGISE(); +} + template <> rdcstr DoStringise(const ReplayStatus &el) { diff --git a/renderdoc/api/replay/structured_data.h b/renderdoc/api/replay/structured_data.h index c9232225a..ebb001e1d 100644 --- a/renderdoc/api/replay/structured_data.h +++ b/renderdoc/api/replay/structured_data.h @@ -404,16 +404,9 @@ struct SDObject data.basic.u = 0; } - ~SDObject() - { - for(size_t i = 0; i < data.children.size(); i++) - delete data.children[i]; - - data.children.clear(); - } - + ~SDObject() { DeleteChildren(); } DOCUMENT("Create a deep copy of this object."); - SDObject *Duplicate() + SDObject *Duplicate() const { SDObject *ret = new SDObject(); ret->name = name; @@ -437,6 +430,32 @@ struct SDObject DOCUMENT("The :class:`SDObjectData` with the contents of this object."); SDObjectData data; + DOCUMENT("Checks if the given object has the same value as this one."); + bool HasEqualValue(const SDObject *o) const + { + bool ret = true; + + if(data.str != o->data.str) + { + ret = false; + } + else if(data.basic.u != o->data.basic.u) + { + ret = false; + } + else if(data.children.size() != o->data.children.size()) + { + ret = false; + } + else + { + for(size_t c = 0; c < o->data.children.size(); c++) + ret &= data.children[c]->HasEqualValue(o->data.children[c]); + } + + return ret; + } + DOCUMENT("Add a new child object by duplicating it."); inline void AddChild(SDObject *child) { data.children.push_back(child->Duplicate()); } DOCUMENT("Find a child object by a given name."); @@ -456,6 +475,15 @@ struct SDObject return NULL; } + DOCUMENT("Delete all child objects."); + inline void DeleteChildren() + { + for(size_t i = 0; i < data.children.size(); i++) + delete data.children[i]; + + data.children.clear(); + } + DOCUMENT("Get the number of child objects."); inline size_t NumChildren() const { return data.children.size(); } DOCUMENT("Get a ``list`` of :class:`SDObject` children."); @@ -479,7 +507,7 @@ struct SDObject inline double AsDouble() const { return data.basic.d; } inline float AsFloat() const { return (float)data.basic.d; } inline char AsChar() const { return data.basic.c; } - inline rdcstr AsString() const { return data.str; } + inline const rdcstr &AsString() const { return data.str; } inline uint64_t AsUInt64() const { return (uint64_t)data.basic.u; } inline int64_t AsInt64() const { return (int64_t)data.basic.i; } inline uint32_t AsUInt32() const { return (uint32_t)data.basic.u; } @@ -554,6 +582,7 @@ struct SDObject return this; } + void AddAndOwnChild(SDObject *child) { data.children.push_back(child); } #endif // these are common to both python and C++ @@ -780,11 +809,11 @@ inline SDObject *makeSDBool(const char *name, bool val) } DOCUMENT("Make a structured object out of a string"); -inline SDObject *makeSDString(const char *name, const char *val) +inline SDObject *makeSDString(const char *name, const rdcstr &val) { SDObject *ret = new SDObject(name, "string"_lit); ret->type.basetype = SDBasic::String; - ret->type.byteSize = strlen(val); + ret->type.byteSize = val.size(); ret->data.str = val; return ret; } @@ -848,6 +877,7 @@ SDOBJECT_MAKER(uint32_t, makeSDUInt32); SDOBJECT_MAKER(float, makeSDFloat); SDOBJECT_MAKER(bool, makeSDBool); SDOBJECT_MAKER(const char *, makeSDString); +SDOBJECT_MAKER(const rdcstr &, makeSDString); SDOBJECT_MAKER(ResourceId, makeSDResourceId); #undef SDOBJECT_MAKER @@ -887,7 +917,7 @@ struct SDChunk : public SDObject SDChunkMetaData metadata; DOCUMENT("Create a deep copy of this chunk."); - SDChunk *Duplicate() + SDChunk *Duplicate() const { SDChunk *ret = new SDChunk(); ret->name = name; diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index f3edde14b..17b6cf6d7 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -454,6 +454,8 @@ void RenderDoc::Initialise() // information to stdout/stderr and being piped around and processed! if(IsReplayApp()) RDCLOGOUTPUT(); + + ProcessConfig(); } RenderDoc::~RenderDoc() @@ -494,6 +496,8 @@ RenderDoc::~RenderDoc() m_RemoteThread = 0; } + delete m_Config; + Process::Shutdown(); Network::Shutdown(); diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 86e4ed308..c5919f390 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -39,6 +39,7 @@ class Chunk; struct RDCThumb; struct ReplayOptions; +struct SDObject; // not provided by tinyexr, just do by hand bool is_exr_file(FILE *f); @@ -418,11 +419,15 @@ public: void RegisterShutdownFunction(ShutdownFunction func); void SetReplayApp(bool replay) { m_Replay = replay; } bool IsReplayApp() const { return m_Replay; } - const rdcstr &GetConfigSetting(rdcstr name) { return m_ConfigSettings[name]; } - void SetConfigSetting(rdcstr name, rdcstr value) { m_ConfigSettings[name] = value; } void BecomeRemoteServer(const char *listenhost, uint16_t port, RENDERDOC_KillCallback killReplay, RENDERDOC_PreviewWindowCallback previewWindow); + const SDObject *GetConfigSetting(const rdcstr &name); + SDObject *SetConfigSetting(const rdcstr &name); + void SaveConfigSettings(); + + void RegisterSetting(const rdcstr &settingPath, SDObject *setting); + DriverInformation GetDriverInformation(GraphicsAPI api); // can't be disabled, only enabled then latched @@ -621,8 +626,6 @@ private: Threading::CriticalSection m_ChildLock; rdcarray > m_Children; - std::map m_ConfigSettings; - std::map m_ReplayDriverProviders; std::map m_RemoteDriverProviders; @@ -698,6 +701,12 @@ private: static void TargetControlClientThread(uint32_t version, Network::Socket *client); ICrashHandler *m_ExHandler; + + void ProcessConfig(); + + SDObject *FindConfigSetting(const rdcstr &name); + + SDObject *m_Config = NULL; }; struct DriverRegistration diff --git a/renderdoc/core/remote_server.cpp b/renderdoc/core/remote_server.cpp index db8e446b4..ea7b39598 100644 --- a/renderdoc/core/remote_server.cpp +++ b/renderdoc/core/remote_server.cpp @@ -29,6 +29,7 @@ #include "api/replay/renderdoc_replay.h" #include "api/replay/version.h" #include "core/core.h" +#include "core/settings.h" #include "os/os_specific.h" #include "replay/replay_controller.h" #include "serialise/rdcfile.h" @@ -36,11 +37,12 @@ #include "strings/string_utils.h" #include "replay_proxy.h" -#if ENABLED(RDOC_DEVEL) -static const uint32_t RemoteServerTimeoutMS = 5000; -#endif +RDOC_CONFIG(uint32_t, RemoteServer_TimeoutMS, 5000, + "Timeout in milliseconds for remote server operations."); -#define DEBUG_REMOTE_SERVER OPTION_OFF +RDOC_DEBUG_CONFIG(bool, RemoteServer_DebugLogging, false, + "Where possible (i.e. it is completely unambiguous) replace register names with " + "high-level variable names."); static const uint32_t RemoteServerProtocolVersion = uint32_t(RENDERDOC_VERSION_MAJOR * 1000) | RENDERDOC_VERSION_MINOR; @@ -215,9 +217,7 @@ static void ActiveRemoteClientThread(ClientThread *threadData, Network::Socket *&client = threadData->socket; -#if ENABLED(RDOC_DEVEL) - client->SetTimeout(RemoteServerTimeoutMS); -#endif + client->SetTimeout(RemoteServer_TimeoutMS); uint32_t ip = client->GetRemoteIP(); @@ -1156,9 +1156,7 @@ RENDERDOC_CreateRemoteServerConnection(const char *URL, IRemoteServer **rend) uint32_t version = RemoteServerProtocolVersion; -#if ENABLED(RDOC_DEVEL) - sock->SetTimeout(RemoteServerTimeoutMS); -#endif + sock->SetTimeout(RemoteServer_TimeoutMS); { WriteSerialiser ser(new StreamWriter(sock, Ownership::Nothing), Ownership::Stream); @@ -1218,22 +1216,25 @@ RemoteServer::RemoteServer(Network::Socket *sock, const rdcstr &deviceID) reader = new ReadSerialiser(new StreamReader(sock, Ownership::Nothing), Ownership::Stream); writer = new WriteSerialiser(new StreamWriter(sock, Ownership::Nothing), Ownership::Stream); -#if ENABLED(RDOC_DEVEL) && ENABLED(DEBUG_REMOTE_SERVER) - reader->ConfigureStructuredExport(&GetRemoteServerChunkName, false); - writer->ConfigureStructuredExport(&GetRemoteServerChunkName, false); + if(RemoteServer_DebugLogging) + { + reader->ConfigureStructuredExport(&GetRemoteServerChunkName, false); + writer->ConfigureStructuredExport(&GetRemoteServerChunkName, false); - rdcstr filename = FileIO::GetTempFolderFilename() + "/RenderDoc/RemoteServer.log"; + rdcstr filename = FileIO::GetTempFolderFilename() + "/RenderDoc/RemoteServer.log"; - // truncate the log - debugLog = FileIO::logfile_open(filename.c_str()); - FileIO::logfile_close(debugLog, filename.c_str()); - debugLog = FileIO::logfile_open(filename.c_str()); + // truncate the log + debugLog = FileIO::logfile_open(filename.c_str()); + FileIO::logfile_close(debugLog, filename.c_str()); + debugLog = FileIO::logfile_open(filename.c_str()); - reader->EnableDumping(debugLog); - writer->EnableDumping(debugLog); -#else - debugLog = NULL; -#endif + reader->EnableDumping(debugLog); + writer->EnableDumping(debugLog); + } + else + { + debugLog = NULL; + } writer->SetStreamingMode(true); reader->SetStreamingMode(true); diff --git a/renderdoc/core/settings.cpp b/renderdoc/core/settings.cpp new file mode 100644 index 000000000..c093a37c5 --- /dev/null +++ b/renderdoc/core/settings.cpp @@ -0,0 +1,595 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "settings.h" +#include "api/replay/structured_data.h" +#include "common/formatting.h" +#include "serialise/streamio.h" +#include "core.h" + +#include "3rdparty/pugixml/pugixml.hpp" + +static const rdcliteral debugOnlyString = "DEBUG VARIABLE: Read-only in stable builds."_lit; + +static rdcstr valueString(const SDObject *o) +{ + if(o->type.basetype == SDBasic::String) + return o->data.str; + + if(o->type.basetype == SDBasic::UnsignedInteger) + return StringFormat::Fmt("%llu", o->data.basic.u); + + if(o->type.basetype == SDBasic::SignedInteger) + return StringFormat::Fmt("%lld", o->data.basic.i); + + if(o->type.basetype == SDBasic::Float) + return StringFormat::Fmt("%lf", o->data.basic.d); + + if(o->type.basetype == SDBasic::Boolean) + return o->data.basic.b ? "True" : "False"; + + if(o->type.basetype == SDBasic::Array) + return StringFormat::Fmt("[%zu]", o->NumChildren()); + + return "{}"; +} + +struct xml_stream_writer : pugi::xml_writer +{ + StreamWriter &stream; + + xml_stream_writer(StreamWriter &writer) : stream(writer) {} + void write(const void *data, size_t size) { stream.Write(data, size); } +}; + +static SDObject *makeSDObject(const char *name, SDBasic type, pugi::xml_node &value) +{ + switch(type) + { + case SDBasic::UnsignedInteger: return makeSDObject(name, (uint64_t)value.text().as_ullong()); + case SDBasic::SignedInteger: return makeSDObject(name, (int64_t)value.text().as_llong()); + case SDBasic::String: return makeSDObject(name, value.text().as_string()); + case SDBasic::Float: return makeSDObject(name, value.text().as_float()); + case SDBasic::Boolean: return makeSDObject(name, value.text().as_bool()); + case SDBasic::Character: return makeSDObject(name, value.text().as_string()[0]); + default: break; + } + + return NULL; +} + +static void saveSDObject(SDObject &value, pugi::xml_node obj) +{ + switch(value.type.basetype) + { + case SDBasic::Resource: + case SDBasic::Enum: + case SDBasic::UnsignedInteger: obj.text() = value.data.basic.u; break; + case SDBasic::SignedInteger: obj.text() = value.data.basic.i; break; + case SDBasic::String: obj.text() = value.data.str.c_str(); break; + case SDBasic::Float: obj.text() = value.data.basic.d; break; + case SDBasic::Boolean: obj.text() = value.data.basic.b; break; + case SDBasic::Character: + { + char str[2] = {value.data.basic.c, '\0'}; + obj.text().set(str); + break; + } + default: RDCERR("Unexpected case"); + } +} + +static void Config2XML(pugi::xml_node &parent, SDObject &child) +{ + pugi::xml_node obj = parent.append_child(child.name.c_str()); + + if(child.type.name == "category"_lit) + { + for(size_t i = 0; i < child.NumChildren(); i++) + Config2XML(obj, *child.GetChild(i)); + } + else + { + SDObject *value = child.FindChild("value"); + + parent.insert_child_before(pugi::node_comment, obj) + .set_value((" " + child.FindChild("description")->data.str + " ").c_str()); + + obj.append_attribute("type") = ToStr(value->type.basetype).c_str(); + if(value->type.basetype == SDBasic::Array) + { + if(!value->data.children.empty()) + obj.append_attribute("elemtype") = ToStr(value->data.children[0]->type.basetype).c_str(); + else + obj.append_attribute("elemtype") = ""; + + for(size_t o = 0; o < value->data.children.size(); o++) + saveSDObject(*value->data.children[o], obj.append_child("item")); + } + else + { + saveSDObject(*value, obj); + } + } +} + +static SDObject *XML2Config(pugi::xml_node &obj) +{ + SDObject *ret = new SDObject(obj.name(), obj.attribute("type") ? "setting"_lit : "category"_lit); + + if(ret->type.name == "category"_lit) + { + uint32_t i = 0; + for(pugi::xml_node child = obj.first_child(); child; child = child.next_sibling()) + { + if(child.type() == pugi::node_comment) + continue; + + SDObject *childObj = XML2Config(child); + if(childObj) + { + ret->data.children.push_back(childObj); + } + else + { + RDCERR("Error converting child %u config option '%s'", i, ret->name.c_str()); + delete ret; + return NULL; + } + + i++; + } + } + else + { + pugi::xml_node value = obj.first_child(); + rdcstr description = obj.previous_sibling().value(); + description.trim(); + + ret->AddAndOwnChild(makeSDObject("description", description)); + + SDObject *valueObj = NULL; + + const SDBasic types[] = { + SDBasic::Array, SDBasic::String, SDBasic::UnsignedInteger, + SDBasic::SignedInteger, SDBasic::Float, SDBasic::Boolean, + }; + + static rdcarray basicTypeStrings; + for(SDBasic t : types) + basicTypeStrings.push_back(ToStr(t)); + + SDBasic type = types[basicTypeStrings.indexOf(obj.attribute("type").as_string())]; + + if(type == SDBasic::Array) + { + type = types[basicTypeStrings.indexOf(obj.attribute("elemtype").as_string())]; + valueObj = makeSDArray("value"); + + uint32_t i = 0; + for(pugi::xml_node el = value.first_child(); el; el = el.next_sibling()) + { + SDObject *childObj = makeSDObject("$el", type, el); + + if(childObj) + { + valueObj->data.children.push_back(childObj); + } + else + { + RDCERR("Error converting array value %u in config option '%s'", i, ret->name.c_str()); + delete valueObj; + delete ret; + return NULL; + } + + i++; + } + } + else + { + valueObj = makeSDObject("value", type, value); + + if(!valueObj) + { + RDCERR("Unexpected type %u of attribute %s", type, ret->name.c_str()); + delete ret; + return NULL; + } + } + + ret->AddAndOwnChild(valueObj); + } + + return ret; +} + +static SDObject *importXMLConfig(StreamReader &stream) +{ + rdcstr buf; + buf.resize((size_t)stream.GetSize()); + stream.Read(buf.data(), buf.size()); + + pugi::xml_document doc; + doc.load_string(buf.c_str(), pugi::parse_default | pugi::parse_comments); + + pugi::xml_node root = doc.child("config"); + + SDObject *ret = new SDObject("config"_lit, "config"_lit); + + if(root) + { + for(pugi::xml_node child = root.first_child(); child; child = child.next_sibling()) + { + SDObject *childObj = XML2Config(child); + if(childObj) + ret->data.children.push_back(XML2Config(child)); + } + } + + return ret; +} + +static void exportXMLConfig(StreamWriter &stream, const SDObject *obj) +{ + pugi::xml_document doc; + + pugi::xml_node xRoot = doc.append_child("config"); + xRoot.append_attribute("version") = (uint32_t)1; + + for(size_t o = 0; o < obj->data.children.size(); o++) + Config2XML(xRoot, *obj->data.children[o]); + + xml_stream_writer writer(stream); + doc.save(writer, " ", pugi::format_default | pugi::format_no_empty_element_tags); +} + +static bool MergeConfigValues(const rdcstr &prefix, SDObject *dstConfig, const SDObject *srcConfig, + bool updateDescs) +{ + bool ret = false; + + // for every child in the destination, see if it has a source node. If not, we're out of date + for(size_t i = 0; i < dstConfig->NumChildren(); i++) + ret |= (srcConfig->FindChild(dstConfig->GetChild(i)->name.c_str()) == NULL); + + // for every child in the source + for(size_t i = 0; i < srcConfig->NumChildren(); i++) + { + // see if it's present in the destination + const SDObject *srcChild = srcConfig->GetChild(i); + SDObject *dstChild = dstConfig->FindChild(srcChild->name.c_str()); + + if(dstChild) + { + // if present, merge the values + rdcstr prefixedChild = prefix + dstChild->name; + + if(dstChild->type.name == "category"_lit) + { + // recurse if this child is not a setting node + ret |= MergeConfigValues(prefixedChild + ".", dstChild, srcChild, updateDescs); + } + else + { + SDObject *dstVal = dstChild->FindChild("value"); + const SDObject *srcVal = srcChild->FindChild("value"); + SDObject *dstDesc = dstChild->FindChild("description"); + const SDObject *srcDesc = srcChild->FindChild("description"); + + bool customised = !srcVal->HasEqualValue(dstVal); + + // otherwise see if the value is customised, and if so log the change + if(customised) + { + rdcstr oldVal = valueString(dstVal); + rdcstr newVal = valueString(srcVal); + + RDCLOG("%s has been customised from %s to %s", (prefix + dstChild->name).c_str(), + oldVal.c_str(), newVal.c_str()); + + if(dstDesc->data.str.contains(debugOnlyString)) + { + RDCWARN("%s customisation will not apply - read only in this build", + (prefix + dstChild->name).c_str()); + } + + // always set the value. For a debug-only setting this will do nothing but we want to + // update our config value with the user's in case we're going to write out some new + // values/descriptions + dstVal->data.str = srcVal->data.str; + memcpy(&dstVal->data.basic, &srcVal->data.basic, sizeof(dstVal->data.basic)); + + dstVal->DeleteChildren(); + + for(size_t c = 0; c < srcVal->data.children.size(); c++) + dstVal->data.children.push_back(srcVal->data.children[c]->Duplicate()); + } + + // if the description has changed from the loaded, need to write the new one + if(dstDesc->data.str != srcDesc->data.str) + { + if(updateDescs) + dstDesc->data.str = srcDesc->data.str; + ret |= true; + } + } + } + else + { + // child wasn't in the destination config, out of date + ret |= true; + + // if we're copying nodes, do that now + dstConfig->AddChild(srcChild->Duplicate()); + } + } + + return ret; +} + +const bool &ConfigVarRegistration::value() +{ + // avoid warnings on stupid compilers + (void)tmp; + return obj->data.basic.b; +} + +const uint64_t &ConfigVarRegistration::value() +{ + (void)tmp; + return obj->data.basic.u; +} + +const uint32_t &ConfigVarRegistration::value() +{ + tmp = obj->data.basic.u & 0xFFFFFFFFU; + return tmp; +} + +const rdcstr &ConfigVarRegistration::value() +{ + (void)tmp; + return obj->data.str; +} + +template +rdcstr DefValString(const T &el) +{ + return ToStr(el); +} + +// this one needs a special implementation unfortunately to convert +const rdcarray &ConfigVarRegistration>::value() +{ + tmp.resize(obj->data.children.size()); + for(size_t i = 0; i < tmp.size(); i++) + tmp[i] = obj->data.children[i]->data.str; + + return tmp; +} + +rdcstr DefValString(const rdcarray &el) +{ + rdcstr ret = "["; + for(size_t i = 0; i < el.size(); i++) + { + if(i != 0) + ret += ", "; + ret += el[i]; + } + ret += "]"; + return ret; +} + +inline SDObject *makeSDObject(const char *name, const rdcarray &vals) +{ + SDObject *ret = new SDObject(name, "array"_lit); + ret->type.basetype = SDBasic::Array; + for(const rdcstr &s : vals) + ret->data.children.push_back(makeSDObject("$el", s)); + return ret; +} + +#define CONFIG_SUPPORT_TYPE(T) \ + ConfigVarRegistration::ConfigVarRegistration(rdcliteral name, const T &defaultValue, \ + bool debugOnly, rdcliteral description) \ + { \ + rdcstr settingName = name; \ + settingName = settingName.substr(settingName.find_last_of("_") + 1); \ + \ + rdcstr desc = name; \ + desc += "\n\n"; \ + for(char &c : desc) \ + if(c == '_') \ + c = '.'; \ + desc += description; \ + \ + desc += "\n\nDefault value: '" + DefValString(defaultValue) + "'"; \ + if(debugOnly) \ + { \ + desc += "\n"; \ + desc += debugOnlyString; \ + } \ + \ + SDObject *setting = new SDObject(settingName, "setting"_lit); \ + setting->AddAndOwnChild(makeSDObject("value", defaultValue)); \ + setting->AddAndOwnChild(makeSDObject("key", name)); \ + setting->AddAndOwnChild(makeSDObject("default", defaultValue)); \ + setting->AddAndOwnChild(makeSDObject("description", desc.c_str())); \ + \ + obj = setting->GetChild(0); \ + \ + RenderDoc::Inst().RegisterSetting(name, setting); \ + } + +CONFIG_SUPPORT_TYPE(bool) +CONFIG_SUPPORT_TYPE(uint64_t) +CONFIG_SUPPORT_TYPE(uint32_t) +CONFIG_SUPPORT_TYPE(rdcstr) +CONFIG_SUPPORT_TYPE(rdcarray) + +void RenderDoc::ProcessConfig() +{ + rdcstr confFile = FileIO::GetAppFolderFilename("renderdoc.conf"); + + SDObject *loadedConfig = NULL; + { + StreamReader reader(FileIO::fopen(confFile.c_str(), "rb")); + + loadedConfig = importXMLConfig(reader); + } + + // iterate through the current config, and update any values that are found in the loaded config. + // returns true if the loaded config is out of date (i.e. there's a value we have which isn't + // present at all, or the descriptions in the loaded config are old). + bool outofDate = ::MergeConfigValues(rdcstr(), m_Config, loadedConfig, false); + + // in the replay application, write it back out again if it's out of date. This + // refreshes the config without changing any customised values and means the user can always edit + // the files on disk + if(IsReplayApp() && outofDate) + { + bool success = false; + + // merge the current config into the loaded config. Values that overlap will have been updated + // with the user's values above, so all that's left is to add new values which aren't in the + // config or update descriptions + MergeConfigValues(rdcstr(), loadedConfig, m_Config, true); + + { + StreamWriter writer(FileIO::fopen((confFile + ".tmp").c_str(), "wb"), Ownership::Stream); + + exportXMLConfig(writer, loadedConfig); + + // only overwrite the config if there were no errors here + success = !writer.IsErrored(); + } + + // if we successfully wrote the file, move it over the original + if(success) + FileIO::Move((confFile + ".tmp").c_str(), confFile.c_str(), true); + } + + // delete the loaded config if we have it + delete loadedConfig; +} + +void RenderDoc::SaveConfigSettings() +{ + if(IsReplayApp()) + { + rdcstr confFile = FileIO::GetAppFolderFilename("renderdoc.conf"); + + bool success = false; + + { + StreamWriter writer(FileIO::fopen((confFile + ".tmp").c_str(), "wb"), Ownership::Stream); + + exportXMLConfig(writer, m_Config); + + // only overwrite the config if there were no errors here + success = !writer.IsErrored(); + } + + // if we successfully wrote the file, move it over the original + if(success) + FileIO::Move((confFile + ".tmp").c_str(), confFile.c_str(), true); + } +} + +const SDObject *RenderDoc::GetConfigSetting(const rdcstr &settingPath) +{ + return FindConfigSetting(settingPath); +} + +SDObject *RenderDoc::SetConfigSetting(const rdcstr &settingPath) +{ + return FindConfigSetting(settingPath); +} + +SDObject *RenderDoc::FindConfigSetting(const rdcstr &settingPath) +{ + if(settingPath.empty()) + return m_Config; + + SDObject *cur = m_Config; + + rdcstr path = settingPath; + int idx = path.find_first_of("_."); + while(idx >= 0) + { + rdcstr node = path.substr(0, idx); + path.erase(0, idx + 1); + + SDObject *child = cur->FindChild(node.c_str()); + if(!child) + return NULL; + + cur = child; + idx = path.find_first_of("_."); + } + + SDObject *obj = cur->FindChild(path.c_str()); + if(obj) + return obj->FindChild("value"); + + return NULL; +} + +void RenderDoc::RegisterSetting(const rdcstr &settingPath, SDObject *setting) +{ + SDObject *cur = m_Config; + + if(cur == NULL) + cur = m_Config = new SDObject("config"_lit, "config"_lit); + + rdcstr path = settingPath; + int idx = path.indexOf('_'); + while(idx >= 0) + { + rdcstr node = path.substr(0, idx); + path.erase(0, idx + 1); + + SDObject *child = cur->FindChild(node.c_str()); + if(!child) + { + child = new SDObject(node, "category"_lit); + auto it = + std::lower_bound(cur->data.children.begin(), cur->data.children.end(), child, + [](const SDObject *a, const SDObject *b) { return a->name < b->name; }); + cur->data.children.insert(it - cur->data.children.begin(), child); + } + + cur = child; + + idx = path.indexOf('_'); + } + + SDObject *obj = cur->FindChild(path.c_str()); + if(obj != NULL) + RDCFATAL("Duplicate setting %s", settingPath.c_str()); + + cur->AddAndOwnChild(setting); +} diff --git a/renderdoc/core/settings.h b/renderdoc/core/settings.h new file mode 100644 index 000000000..e39fc6a1a --- /dev/null +++ b/renderdoc/core/settings.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "api/replay/rdcarray.h" +#include "api/replay/rdcstr.h" +#include "api/replay/version.h" +#include "common/common.h" + +struct SDObject; + +template +struct ConfigVarRegistration; + +#define CONFIG_SUPPORT_TYPE(T) \ + template <> \ + struct ConfigVarRegistration \ + { \ + ConfigVarRegistration(rdcliteral name, const T &defaultValue, bool debugOnly, \ + rdcliteral description); \ + const T &value(); \ + \ + private: \ + SDObject *obj; \ + T tmp; \ + }; + +CONFIG_SUPPORT_TYPE(rdcstr); +CONFIG_SUPPORT_TYPE(bool); +CONFIG_SUPPORT_TYPE(uint64_t); +CONFIG_SUPPORT_TYPE(uint32_t); +CONFIG_SUPPORT_TYPE(rdcarray); + +#undef CONFIG_SUPPORT_TYPE + +#define RDOC_CONFIG(type, name, defaultValue, description) \ + static ConfigVarRegistration CONCAT(config, __LINE__)( \ + STRING_LITERAL(STRINGIZE(name)), defaultValue, false, STRING_LITERAL(description)); \ + static const type &name = CONCAT(config, __LINE__).value(); + +// debug configs get set to constants in official stable builds, they will remain configurable +// in nightly builds and of course in development builds +#if RENDERDOC_STABLE_BUILD + +#define RDOC_DEBUG_CONFIG(type, name, defaultValue, description) \ + static ConfigVarRegistration CONCAT(config, __LINE__)( \ + STRING_LITERAL(STRINGIZE(name)), defaultValue, true, STRING_LITERAL(description)); \ + static constexpr type name = defaultValue; + +#else + +#define RDOC_DEBUG_CONFIG(type, name, defaultValue, description) \ + ConfigVarRegistration CONCAT(config, __LINE__)( \ + STRING_LITERAL(STRINGIZE(name)), defaultValue, true, STRING_LITERAL(description)); \ + static const type &name = CONCAT(config, __LINE__).value(); + +#endif diff --git a/renderdoc/driver/d3d11/d3d11_device.cpp b/renderdoc/driver/d3d11/d3d11_device.cpp index 286a34d2e..da0cc8cfd 100644 --- a/renderdoc/driver/d3d11/d3d11_device.cpp +++ b/renderdoc/driver/d3d11/d3d11_device.cpp @@ -25,6 +25,7 @@ #include "d3d11_device.h" #include "core/core.h" +#include "core/settings.h" #include "driver/dxgi/dxgi_wrapped.h" #include "jpeg-compressor/jpge.h" #include "maths/formatpacking.h" @@ -38,6 +39,9 @@ #include "d3d11_resources.h" #include "d3d11_shader_cache.h" +RDOC_CONFIG(rdcarray, DXBC_Debug_SearchDirPaths, {}, + "Paths to search for separated shader debug PDBs."); + WRAPPED_POOL_INST(WrappedID3D11Device); WrappedID3D11Device *WrappedID3D11Device::m_pCurrentWrappedDevice = NULL; @@ -112,9 +116,6 @@ WrappedID3D11Device::WrappedID3D11Device(ID3D11Device *realDevice, D3D11InitPara D3D11MarkerRegion::device = this; - rdcstr shaderSearchPathString = RenderDoc::Inst().GetConfigSetting("shader.debug.searchPaths"); - split(shaderSearchPathString, m_ShaderSearchPaths, ';'); - ResourceIDGen::SetReplayResourceIDs(); } else @@ -789,6 +790,11 @@ rdcstr WrappedID3D11Device::GetChunkName(uint32_t idx) return ToStr((D3D11Chunk)idx); } +const rdcarray *WrappedID3D11Device::GetShaderDebugInfoSearchPaths() +{ + return &DXBC_Debug_SearchDirPaths; +} + void WrappedID3D11Device::AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSource src, rdcstr d) { diff --git a/renderdoc/driver/d3d11/d3d11_device.h b/renderdoc/driver/d3d11/d3d11_device.h index 9e2ab261d..387172edd 100644 --- a/renderdoc/driver/d3d11/d3d11_device.h +++ b/renderdoc/driver/d3d11/d3d11_device.h @@ -314,8 +314,6 @@ private: D3D11ShaderCache *m_ShaderCache = NULL; D3D11ResourceManager *m_ResourceManager = NULL; - rdcarray m_ShaderSearchPaths; - D3D11InitParams m_InitParams; uint64_t m_SectionVersion; ReplayOptions m_ReplayOptions; @@ -456,7 +454,7 @@ public: return m_LayoutDescs[layout]; } - rdcarray *GetShaderDebugInfoSearchPaths() { return &m_ShaderSearchPaths; } + const rdcarray *GetShaderDebugInfoSearchPaths(); template bool Serialise_CaptureScope(SerialiserType &ser); diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index 7d936b634..ae4d155e5 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -24,6 +24,7 @@ ******************************************************************************/ #include "d3d11_replay.h" +#include "core/settings.h" #include "driver/dx/official/d3dcompiler.h" #include "driver/ihv/amd/amd_counters.h" #include "driver/ihv/intel/intel_counters.h" @@ -42,6 +43,9 @@ #include "data/hlsl/hlsl_cbuffers.h" +RDOC_CONFIG(bool, D3D11_HardwareCounters, true, + "Enable support for IHV-specific hardware counters on D3D11."); + static const char *DXBCDisassemblyTarget = "DXBC"; D3D11Replay::D3D11Replay(WrappedID3D11Device *d) @@ -170,7 +174,7 @@ void D3D11Replay::CreateResources(IDXGIFactory *factory) m_pDevice->GetShaderCache()->SetCaching(false); - if(!m_Proxy) + if(!m_Proxy && D3D11_HardwareCounters) { AMDCounters *countersAMD = NULL; NVCounters *countersNV = NULL; diff --git a/renderdoc/driver/d3d11/d3d11_resources.h b/renderdoc/driver/d3d11/d3d11_resources.h index 8f4125b3e..c7d483aa9 100644 --- a/renderdoc/driver/d3d11/d3d11_resources.h +++ b/renderdoc/driver/d3d11/d3d11_resources.h @@ -956,7 +956,7 @@ public: ResourceId m_ID; rdcstr m_DebugInfoPath; - rdcarray *m_DebugInfoSearchPaths; + const rdcarray *m_DebugInfoSearchPaths; rdcarray m_Bytecode; diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 29072a91c..73612fd4c 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -24,6 +24,7 @@ #include "d3d12_replay.h" #include "core/plugins.h" +#include "core/settings.h" #include "driver/dx/official/d3dcompiler.h" #include "driver/dxgi/dxgi_common.h" #include "driver/ihv/amd/amd_counters.h" @@ -41,6 +42,12 @@ #include "data/hlsl/hlsl_cbuffers.h" +RDOC_CONFIG(bool, D3D12_ShaderDebugging, false, + "BETA: Enable experimental shader debugging support."); + +RDOC_CONFIG(bool, D3D12_HardwareCounters, true, + "Enable support for IHV-specific hardware counters on D3D12."); + static const char *LiveDriverDisassemblyTarget = "Live driver disassembly"; ID3DDevice *GetD3D12DeviceIfAlloc(IUnknown *dev); @@ -151,7 +158,7 @@ void D3D12Replay::CreateResources() m_PixelPick.Init(m_pDevice, m_DebugManager); m_Histogram.Init(m_pDevice, m_DebugManager); - if(!m_Proxy) + if(!m_Proxy && D3D12_HardwareCounters) { AMDCounters *counters = NULL; @@ -271,11 +278,7 @@ APIProperties D3D12Replay::GetAPIProperties() ret.shadersMutable = false; ret.rgpCapture = m_DriverInfo.vendor == GPUVendor::AMD && m_RGP != NULL && m_RGP->DriverSupportsInterop(); - - // Enable shader debugging if specified in the config - rdcstr setting = strlower(RenderDoc::Inst().GetConfigSetting("d3d12ShaderDebugging")); - if(!strcmp(setting.c_str(), "true") || setting == "1") - ret.shaderDebugging = true; + ret.shaderDebugging = D3D12_ShaderDebugging; return ret; } diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index b0b1ce894..aef349f90 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -24,6 +24,7 @@ ******************************************************************************/ #include "gl_replay.h" +#include "core/settings.h" #include "driver/ihv/amd/amd_counters.h" #include "driver/ihv/intel/intel_gl_counters.h" #include "maths/matrix.h" @@ -35,6 +36,9 @@ #define OPENGL 1 #include "data/glsl/glsl_ubos_cpp.h" +RDOC_CONFIG(bool, OpenGL_HardwareCounters, true, + "Enable support for IHV-specific hardware counters on OpenGL."); + static const char *SPIRVDisassemblyTarget = "SPIR-V (RenderDoc)"; GLReplay::GLReplay(WrappedOpenGL *d) @@ -227,7 +231,7 @@ void GLReplay::SetReplayData(GLWindowingData data) if(!HasDebugContext()) return; - if(!m_Proxy) + if(!m_Proxy && OpenGL_HardwareCounters) { AMDCounters *countersAMD = NULL; IntelGlCounters *countersIntel = NULL; diff --git a/renderdoc/driver/ihv/amd/amd_rgp.cpp b/renderdoc/driver/ihv/amd/amd_rgp.cpp index 5e0c7d682..29f5231a8 100644 --- a/renderdoc/driver/ihv/amd/amd_rgp.cpp +++ b/renderdoc/driver/ihv/amd/amd_rgp.cpp @@ -26,8 +26,11 @@ #include "common/common.h" #include "core/core.h" #include "core/plugins.h" +#include "core/settings.h" #include "official/RGP/DevDriverAPI.h" +RDOC_CONFIG(bool, AMD_RGP_Enable, false, "Enable integration with AMD's RGP tool."); + uint64_t MakeTagFromMarker(const char *marker) { if(!marker) @@ -68,9 +71,7 @@ AMDRGPControl::AMDRGPControl() m_RGPDispatchTable->minorVersion = DEV_DRIVER_API_MINOR_VERSION; m_RGPContext = NULL; - const bool enabled = RenderDoc::Inst().GetConfigSetting("ExternalTool_RGPIntegration") == "1"; - - if(!enabled) + if(!AMD_RGP_Enable) { RDCLOG("AMD RGP Interop is not enabled"); return; diff --git a/renderdoc/driver/shaders/dxbc/dxbc_disassemble.cpp b/renderdoc/driver/shaders/dxbc/dxbc_disassemble.cpp index 387069aee..4cfb71f8d 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_disassemble.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_disassemble.cpp @@ -26,12 +26,17 @@ #include #include "common/common.h" #include "core/core.h" +#include "core/settings.h" #include "serialise/serialiser.h" #include "strings/string_utils.h" #include "dxbc_bytecode.h" #include "dxbc_container.h" +RDOC_CONFIG(bool, DXBC_Disassembly_FriendlyNaming, true, + "Where possible (i.e. it is completely unambiguous) replace register names with " + "high-level variable names."); + namespace DXBCBytecode { // little utility function to both document and easily extract an arbitrary mask @@ -437,7 +442,7 @@ void Program::DisassembleHexDump() m_Declarations.reserve(numDecls); - const bool friendly = RenderDoc::Inst().GetConfigSetting("Disassembly_FriendlyNaming") != "0"; + const bool friendly = DXBC_Disassembly_FriendlyNaming; while(cur < end) { diff --git a/renderdoc/driver/vulkan/vk_bindless_feedback.cpp b/renderdoc/driver/vulkan/vk_bindless_feedback.cpp index 485e56c10..62d1a2d7c 100644 --- a/renderdoc/driver/vulkan/vk_bindless_feedback.cpp +++ b/renderdoc/driver/vulkan/vk_bindless_feedback.cpp @@ -23,6 +23,7 @@ ******************************************************************************/ #include +#include "core/settings.h" #include "driver/shaders/spirv/spirv_editor.h" #include "driver/shaders/spirv/spirv_op_helpers.h" #include "vk_core.h" @@ -30,6 +31,10 @@ #include "vk_replay.h" #include "vk_shader_cache.h" +RDOC_CONFIG( + bool, Vulkan_BindlessFeedback, true, + "Enable fetching from GPU which descriptors were dynamically used in descriptor arrays."); + struct feedbackData { uint64_t offset; @@ -524,6 +529,9 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) if(m_BindlessFeedback.Usage.find(eventId) != m_BindlessFeedback.Usage.end()) return; + if(!Vulkan_BindlessFeedback) + return; + // create it here so we won't re-run any code if the event is re-selected. We'll mark it as valid // if it actually has any data in it later. DynamicUsedBinds &result = m_BindlessFeedback.Usage[eventId]; diff --git a/renderdoc/driver/vulkan/vk_debug.cpp b/renderdoc/driver/vulkan/vk_debug.cpp index 885e6c1a8..a3b90793a 100644 --- a/renderdoc/driver/vulkan/vk_debug.cpp +++ b/renderdoc/driver/vulkan/vk_debug.cpp @@ -24,6 +24,7 @@ #include "vk_debug.h" #include +#include "core/settings.h" #include "data/glsl_shaders.h" #include "driver/ihv/amd/amd_counters.h" #include "driver/ihv/amd/official/GPUPerfAPI/Include/GPUPerfAPI-VK.h" @@ -38,6 +39,9 @@ #define VULKAN 1 #include "data/glsl/glsl_ubos_cpp.h" +RDOC_CONFIG(bool, Vulkan_HardwareCounters, true, + "Enable support for IHV-specific hardware counters on Vulkan."); + const VkDeviceSize STAGE_BUFFER_BYTE_SIZE = 16 * 1024 * 1024ULL; static void create(WrappedVulkan *driver, const char *objName, const int line, VkSampler *sampler, @@ -1712,7 +1716,7 @@ void VulkanReplay::CreateResources() GPA_vkContextOpenInfo context = {Unwrap(m_pDriver->GetInstance()), Unwrap(m_pDriver->GetPhysDev()), Unwrap(m_pDriver->GetDev())}; - if(!m_pDriver->GetReplay()->IsRemoteProxy()) + if(!m_pDriver->GetReplay()->IsRemoteProxy() && Vulkan_HardwareCounters) { AMDCounters *counters = NULL; diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index 319103602..261f5a75c 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -27,6 +27,7 @@ #include #include #include +#include "core/settings.h" #include "driver/ihv/amd/amd_rgp.h" #include "driver/shaders/spirv/spirv_compile.h" #include "maths/formatpacking.h" @@ -41,6 +42,9 @@ #define VULKAN 1 #include "data/glsl/glsl_ubos_cpp.h" +RDOC_CONFIG(bool, Vulkan_ShaderDebugging, false, + "BETA: Enable experimental shader debugging support."); + static const char *SPIRVDisassemblyTarget = "SPIR-V (RenderDoc)"; static const char *AMDShaderInfoTarget = "AMD_shader_info"; static const char *KHRExecutablePropertiesTarget = "KHR_pipeline_executable_properties"; @@ -186,11 +190,7 @@ APIProperties VulkanReplay::GetAPIProperties() ret.shadersMutable = false; ret.rgpCapture = m_DriverInfo.vendor == GPUVendor::AMD && m_RGP != NULL && m_RGP->DriverSupportsInterop(); - - // Enable shader debugging if specified in the config - rdcstr setting = strlower(RenderDoc::Inst().GetConfigSetting("vulkanShaderDebugging")); - if(!strcmp(setting.c_str(), "true") || setting == "1") - ret.shaderDebugging = true; + ret.shaderDebugging = Vulkan_ShaderDebugging; return ret; } diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index bb4c593de..5632f3150 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -189,6 +189,7 @@ + @@ -421,6 +422,7 @@ + diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index 0891c7abe..0b19f3403 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -504,6 +504,9 @@ Common + + Core + @@ -887,6 +890,9 @@ Common\Maths + + Core + diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index a5364dd3b..62aa60766 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -192,15 +192,19 @@ extern "C" RENDERDOC_API uint64_t RENDERDOC_CC RENDERDOC_GetCurrentProcessMemory return Process::GetMemoryUsage(); } -extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetConfigSetting(const char *name) +extern "C" RENDERDOC_API const SDObject *RENDERDOC_CC RENDERDOC_GetConfigSetting(const char *name) { - return RenderDoc::Inst().GetConfigSetting(name).c_str(); + return RenderDoc::Inst().GetConfigSetting(name); } -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SetConfigSetting(const char *name, - const char *value) +extern "C" RENDERDOC_API SDObject *RENDERDOC_CC RENDERDOC_SetConfigSetting(const char *name) { - RenderDoc::Inst().SetConfigSetting(name, value); + return RenderDoc::Inst().SetConfigSetting(name); +} + +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SaveConfigSettings() +{ + return RenderDoc::Inst().SaveConfigSettings(); } extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SetColors(FloatVector darkChecker, diff --git a/renderdoc/serialise/serialiser.cpp b/renderdoc/serialise/serialiser.cpp index 9217a6b0e..784ef8ef3 100644 --- a/renderdoc/serialise/serialiser.cpp +++ b/renderdoc/serialise/serialiser.cpp @@ -34,6 +34,8 @@ int64_t Chunk::m_LiveChunks = 0; int64_t Chunk::m_TotalMem = 0; +#endif + void DumpObject(FileIO::LogFileHandle *log, const rdcstr &indent, SDObject *obj) { if(obj->NumChildren() > 0) @@ -81,8 +83,6 @@ void DumpChunk(bool reading, FileIO::LogFileHandle *log, SDChunk *chunk) DumpObject(log, " ", chunk); } -#endif - ///////////////////////////////////////////////////////////// // Read Serialiser functions @@ -265,12 +265,10 @@ void Serialiser::EndChunk() m_StructureStack.pop_back(); } -#if ENABLED(RDOC_DEVEL) if(m_DebugDumpLog && !m_StructuredFile->chunks.empty()) { DumpChunk(true, m_DebugDumpLog, m_StructuredFile->chunks.back()); } -#endif } // only skip remaining bytes if we have a valid length - if we have a length of 0 we wrote this @@ -526,12 +524,10 @@ void Serialiser::EndChunk() m_StructureStack.pop_back(); } -#if ENABLED(RDOC_DEVEL) if(m_DebugDumpLog && !m_StructuredFile->chunks.empty()) { DumpChunk(false, m_DebugDumpLog, m_StructuredFile->chunks.back()); } -#endif } // align to the natural chunk alignment @@ -621,27 +617,6 @@ void Serialiser::WriteStructuredFile(const SDFile &file scratchWriter.m_StructuredFile = &scratchWriter.m_StructData; } -template <> -rdcstr DoStringise(const SDBasic &el) -{ - BEGIN_ENUM_STRINGISE(SDBasic); - { - STRINGISE_ENUM_CLASS(Chunk); - STRINGISE_ENUM_CLASS(Struct); - STRINGISE_ENUM_CLASS(Array); - STRINGISE_ENUM_CLASS(Null); - STRINGISE_ENUM_CLASS(Buffer); - STRINGISE_ENUM_CLASS(String); - STRINGISE_ENUM_CLASS(Enum); - STRINGISE_ENUM_CLASS(UnsignedInteger); - STRINGISE_ENUM_CLASS(SignedInteger); - STRINGISE_ENUM_CLASS(Float); - STRINGISE_ENUM_CLASS(Boolean); - STRINGISE_ENUM_CLASS(Character); - } - END_ENUM_STRINGISE(); -} - template <> rdcstr DoStringise(const SDTypeFlags &el) { diff --git a/renderdoc/serialise/serialiser.h b/renderdoc/serialise/serialiser.h index f711237a8..10bd606a4 100644 --- a/renderdoc/serialise/serialiser.h +++ b/renderdoc/serialise/serialiser.h @@ -121,11 +121,8 @@ public: uint32_t GetChunkMetadataRecording() { return m_ChunkFlags; } void SetChunkMetadataRecording(uint32_t flags); -// debug-only option to dump out (roughly) the data going through the serialiser as it happens -#if ENABLED(RDOC_DEVEL) + // debug-only option to dump out (roughly) the data going through the serialiser as it happens void EnableDumping(FileIO::LogFileHandle *debugLog) { m_DebugDumpLog = debugLog; } -#endif - SDChunkMetaData &ChunkMetadata() { return m_ChunkMetadata; } ////////////////////////////////////////// // Utility functions @@ -1340,9 +1337,7 @@ private: } ChunkLookup m_ChunkLookup = NULL; -#if ENABLED(RDOC_DEVEL) FileIO::LogFileHandle *m_DebugDumpLog = NULL; -#endif }; #ifndef SERIALISER_IMPL diff --git a/util/test/tests/D3D12/D3D12_RGP_Capture.py b/util/test/tests/D3D12/D3D12_RGP_Capture.py index 7270d2bfa..f2caa51c5 100644 --- a/util/test/tests/D3D12/D3D12_RGP_Capture.py +++ b/util/test/tests/D3D12/D3D12_RGP_Capture.py @@ -18,7 +18,9 @@ class D3D12_RGP_Capture(rdtest.TestCase): # Need to enable RGP mode before opening the capture def run(self): - rd.SetConfigSetting("ExternalTool_RGPIntegration", "1") + obj: rd.SDObject = rd.SetConfigSetting("AMD.RGP.Enable") + if obj is not None: + obj.data.basic.b = True super().run() def check_capture(self): diff --git a/util/test/tests/Vulkan/VK_RGP_Capture.py b/util/test/tests/Vulkan/VK_RGP_Capture.py index 4ed37044d..a7ab74541 100644 --- a/util/test/tests/Vulkan/VK_RGP_Capture.py +++ b/util/test/tests/Vulkan/VK_RGP_Capture.py @@ -8,7 +8,9 @@ class VK_RGP_Capture(rdtest.TestCase): # Need to enable RGP mode before opening the capture def run(self): - rd.SetConfigSetting("ExternalTool_RGPIntegration", "1") + obj: rd.SDObject = rd.SetConfigSetting("AMD.RGP.Enable") + if obj is not None: + obj.data.basic.b = True super().run() def check_capture(self):