ShaderManagementConsoleTableView.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
  9. #include <AtomToolsFramework/Util/Util.h>
  10. #include <AzCore/Name/Name.h>
  11. #include <AzQtComponents/Components/StyledSpinBox.h>
  12. #include <Window/ShaderManagementConsoleTableView.h>
  13. #include <QComboBox>
  14. #include <QHeaderView>
  15. #include <QMenu>
  16. #include <QKeyEvent>
  17. #include <QPushButton>
  18. #include <QFont>
  19. #include <functional>
  20. namespace ShaderManagementConsole
  21. {
  22. template<typename BaseWidget>
  23. struct FocusOutConfigurable : public BaseWidget
  24. {
  25. template<typename... Objects>
  26. FocusOutConfigurable(Objects&&... args)
  27. : BaseWidget(std::forward<Objects>(args)...)
  28. {
  29. }
  30. void hidePopup() override
  31. {
  32. BaseWidget::hidePopup();
  33. if (m_onExit)
  34. {
  35. m_onExit();
  36. }
  37. }
  38. std::function<void()> m_onExit;
  39. };
  40. ShaderManagementConsoleTableView::ShaderManagementConsoleTableView(
  41. const AZ::Crc32& toolId, const AZ::Uuid& documentId, QWidget* parent)
  42. : QTableWidget(parent)
  43. , m_toolId(toolId)
  44. , m_documentId(documentId)
  45. , m_emptyOptionIcon(":/Icons/emptyoption.svg")
  46. {
  47. setEditTriggers(QAbstractItemView::NoEditTriggers);
  48. setSelectionBehavior(QAbstractItemView::SelectItems);
  49. setSelectionMode(QAbstractItemView::SingleSelection);
  50. verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
  51. horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
  52. setAlternatingRowColors(true);
  53. setContextMenuPolicy(Qt::CustomContextMenu);
  54. connect(this, &QTableWidget::customContextMenuRequested, this, &ShaderManagementConsoleTableView::ShowContextMenu);
  55. RebuildTable();
  56. AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusConnect(m_toolId);
  57. }
  58. void ShaderManagementConsoleTableView::mousePressEvent(QMouseEvent* e)
  59. {
  60. QTableWidget::mousePressEvent(e);
  61. if (e->button() == Qt::RightButton)
  62. {
  63. ShowContextMenu(e->pos());
  64. }
  65. }
  66. void ShaderManagementConsoleTableView::ShowContextMenu(const QPoint& pos)
  67. {
  68. QMenu contextMenu(tr("Context menu"), this);
  69. contextMenu.addAction(
  70. tr("Add Variant"),
  71. [this]()
  72. {
  73. AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
  74. m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
  75. ShaderManagementConsoleDocumentRequestBus::Event(
  76. m_documentId, &ShaderManagementConsoleDocumentRequestBus::Events::AddOneVariantRow);
  77. AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
  78. m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
  79. });
  80. QMenu* scriptsMenu = contextMenu.addMenu(QObject::tr("Python Scripts"));
  81. const AZStd::vector<AZStd::string> arguments{ m_documentId.ToString<AZStd::string>(false, true) };
  82. AtomToolsFramework::AddRegisteredScriptToMenu(scriptsMenu, "/O3DE/ShaderManagementConsole/DocumentTableView/ContextMenuScripts", arguments);
  83. contextMenu.exec(mapToGlobal(pos));
  84. }
  85. ShaderManagementConsoleTableView::~ShaderManagementConsoleTableView()
  86. {
  87. AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect();
  88. }
  89. void ShaderManagementConsoleTableView::OnDocumentOpened(const AZ::Uuid& documentId)
  90. {
  91. if (m_documentId == documentId)
  92. {
  93. RebuildTable();
  94. }
  95. }
  96. void ShaderManagementConsoleTableView::OnDocumentModified(const AZ::Uuid& documentId)
  97. {
  98. if (m_documentId == documentId)
  99. {
  100. RebuildTable();
  101. }
  102. }
  103. int ShaderManagementConsoleTableView::UiColumnToOption(int uiColumnIndex) const
  104. {
  105. return uiColumnIndex - 1; // because column #0 is the "X" (deleters)
  106. }
  107. int ShaderManagementConsoleTableView::GetColumnsCount(CountQuery query) const
  108. {
  109. return query == CountQuery::ForUi ? columnCount() : UiColumnToOption(columnCount());
  110. }
  111. void ShaderManagementConsoleTableView::RebuildTable()
  112. {
  113. QSignalBlocker blocker(this);
  114. // Delete any active edit widget from the current selection
  115. if (currentColumn() != 0)
  116. {
  117. removeCellWidget(currentRow(), currentColumn());
  118. }
  119. // Disconnect data change signal while populating the table
  120. disconnect();
  121. // Get the shader variant list source data whose options will be used to populate the table
  122. m_shaderVariantListSourceData = {};
  123. ShaderManagementConsoleDocumentRequestBus::EventResult(
  124. m_shaderVariantListSourceData, m_documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantListSourceData);
  125. // The number of variants corresponds to the number of rows in the table
  126. m_shaderVariantCount = m_shaderVariantListSourceData.m_shaderVariants.size();
  127. // The number of options corresponds to the number of columns in the table. This data is being pulled from the asset instead of the
  128. // shader variant list source data. The asset may contain more options that are listed in the source data. This will result in
  129. // several columns with no values.
  130. m_shaderOptionCount = {};
  131. ShaderManagementConsoleDocumentRequestBus::EventResult(
  132. m_shaderOptionCount, m_documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionDescriptorCount);
  133. // Only clear the table if the number of columns or rows have changed
  134. if (rowCount() != m_shaderVariantCount || GetColumnsCount(CountQuery::Options) != m_shaderOptionCount)
  135. {
  136. clear();
  137. setRowCount(static_cast<int>(m_shaderVariantCount));
  138. setColumnCount(static_cast<int>(m_shaderOptionCount) + 1); // 1 for "delete row" widgets
  139. }
  140. // Get a list of all of the shader option descriptors from the shader asset that will be used for the columns in the table
  141. m_shaderOptionDescriptors = {};
  142. m_shaderOptionDescriptors.reserve(GetColumnsCount(CountQuery::Options));
  143. for (int column = 0; column < GetColumnsCount(CountQuery::Options); ++column)
  144. {
  145. AZ::RPI::ShaderOptionDescriptor shaderOptionDescriptor;
  146. ShaderManagementConsoleDocumentRequestBus::EventResult(
  147. shaderOptionDescriptor,
  148. m_documentId,
  149. &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionDescriptor,
  150. column);
  151. m_shaderOptionDescriptors.push_back(shaderOptionDescriptor);
  152. }
  153. switch (m_columnSortMode)
  154. {
  155. case Alpha:
  156. {
  157. // Sort descriptors by name and ascending order
  158. AZStd::sort(
  159. m_shaderOptionDescriptors.begin(),
  160. m_shaderOptionDescriptors.end(),
  161. [](const auto& a, const auto& b)
  162. {
  163. return a.GetName().GetStringView() < b.GetName().GetStringView();
  164. });
  165. }
  166. break;
  167. case Rank:
  168. {
  169. // Sort descriptors by ascending declaration order
  170. AZStd::sort(
  171. m_shaderOptionDescriptors.begin(),
  172. m_shaderOptionDescriptors.end(),
  173. [](const auto& a, const auto& b)
  174. {
  175. return a.GetOrder() < b.GetOrder();
  176. });
  177. }
  178. break;
  179. case Cost:
  180. {
  181. // Sort by cost estimate score in descending order
  182. AZStd::sort(
  183. m_shaderOptionDescriptors.begin(),
  184. m_shaderOptionDescriptors.end(),
  185. [](const auto& a, const auto& b)
  186. {
  187. return a.GetCostEstimate() > b.GetCostEstimate();
  188. });
  189. }
  190. break;
  191. }
  192. // Fill in the header of each column with the descriptor name
  193. for (int column = 1; column < GetColumnsCount(CountQuery::ForUi); ++column)
  194. {
  195. const auto& shaderOptionDescriptor = m_shaderOptionDescriptors[UiColumnToOption(column)];
  196. auto* tableItem = new QTableWidgetItem(shaderOptionDescriptor.GetName().GetCStr());
  197. tableItem->setToolTip(tr("cost %1").arg(shaderOptionDescriptor.GetCostEstimate()));
  198. // color material options in yellow
  199. if (m_shaderVariantListSourceData.m_materialOptionsHint.find(shaderOptionDescriptor.GetName()) !=
  200. m_shaderVariantListSourceData.m_materialOptionsHint.end())
  201. {
  202. tableItem->setForeground(QColorConstants::Yellow);
  203. }
  204. setHorizontalHeaderItem(column, tableItem);
  205. }
  206. setHorizontalHeaderItem(0, new QTableWidgetItem(""));
  207. // Fill all the rows with values from each variant
  208. for (int row = 0; row < rowCount(); ++row)
  209. {
  210. const auto& shaderVariant = m_shaderVariantListSourceData.m_shaderVariants[row];
  211. setVerticalHeaderItem(row, new QTableWidgetItem(QString::number(shaderVariant.m_stableId)));
  212. for (int column = 1; column < GetColumnsCount(CountQuery::ForUi); ++column)
  213. {
  214. const auto& shaderOptionDescriptor = m_shaderOptionDescriptors[UiColumnToOption(column)];
  215. const auto optionIt = shaderVariant.m_options.find(shaderOptionDescriptor.GetName());
  216. const AZ::Name valueName = optionIt != shaderVariant.m_options.end() ? AZ::Name(optionIt->second) : AZ::Name();
  217. auto* newItem = new QTableWidgetItem(valueName.GetCStr());
  218. if (valueName.IsEmpty())
  219. {
  220. newItem->setIcon(m_emptyOptionIcon);
  221. newItem->setToolTip(tr("runtime variable"));
  222. }
  223. setItem(row, column, newItem);
  224. }
  225. auto* deleterButton = new QPushButton;
  226. deleterButton->setText(reinterpret_cast<const char*>(u8"\u274C")); // cross sign
  227. deleterButton->setToolTip(tr("delete row"));
  228. connect(deleterButton, &QPushButton::clicked, this, [this, row](){
  229. auto& vec = m_shaderVariantListSourceData.m_shaderVariants;
  230. vec.erase(vec.begin() + row);
  231. TransferViewModelToModel(CallOnModified);
  232. });
  233. setCellWidget(row, 0, deleterButton);
  234. }
  235. horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
  236. // Connect to the data changed signal to listen for and apply table edits back to the document
  237. connect(this, &QTableWidget::currentCellChanged, this, &ShaderManagementConsoleTableView::OnCellSelected);
  238. connect(this, &QTableWidget::cellChanged, this, &ShaderManagementConsoleTableView::OnCellChanged);
  239. }
  240. void ShaderManagementConsoleTableView::OnCellSelected(int row, int column, int previousRow, int previousColumn)
  241. {
  242. if (column == 0)
  243. {
  244. return;
  245. }
  246. removeCellWidget(row, column);
  247. removeCellWidget(previousRow, previousColumn);
  248. if (row < 0 || row >= m_shaderVariantListSourceData.m_shaderVariants.size())
  249. {
  250. return;
  251. }
  252. if (column < 0 || UiColumnToOption(column) >= m_shaderOptionDescriptors.size())
  253. {
  254. return;
  255. }
  256. const auto& shaderOptionDescriptor = m_shaderOptionDescriptors[UiColumnToOption(column)];
  257. const auto& shaderVariant = m_shaderVariantListSourceData.m_shaderVariants[row];
  258. const auto optionIt = shaderVariant.m_options.find(shaderOptionDescriptor.GetName());
  259. const AZ::Name valueName = optionIt != shaderVariant.m_options.end() ? AZ::Name(optionIt->second) : AZ::Name();
  260. const AZ::RPI::ShaderOptionValue value = shaderOptionDescriptor.FindValue(valueName);
  261. const AZ::RPI::ShaderOptionValue valueMin = shaderOptionDescriptor.GetMinValue();
  262. const AZ::RPI::ShaderOptionValue valueMax = shaderOptionDescriptor.GetMaxValue();
  263. switch (shaderOptionDescriptor.GetType())
  264. {
  265. case AZ::RPI::ShaderOptionType::Boolean:
  266. case AZ::RPI::ShaderOptionType::Enumeration:
  267. {
  268. auto* comboBox = new FocusOutConfigurable<QComboBox>(this);
  269. static auto italicFont = QFont(comboBox->fontInfo().family(), comboBox->fontInfo().pointSize(), comboBox->fontInfo().weight(), true);
  270. comboBox->addItem("<dynamic>");
  271. comboBox->setItemData(0, italicFont, Qt::FontRole);
  272. comboBox->setItemIcon(0, m_emptyOptionIcon);
  273. for (uint32_t valueIndex = valueMin.GetIndex(); valueIndex <= valueMax.GetIndex(); ++valueIndex)
  274. {
  275. comboBox->addItem(shaderOptionDescriptor.GetValueName(AZ::RPI::ShaderOptionValue{ valueIndex }).GetCStr());
  276. }
  277. comboBox->setCurrentText(valueName.GetCStr());
  278. setCellWidget(row, column, comboBox);
  279. connect(comboBox, &QComboBox::currentTextChanged, this, [this, row, column](const QString& text) {
  280. item(row, column)->setText(text == "<dynamic>" ? "" : text);
  281. });
  282. comboBox->m_onExit = [this, row, column]() { removeCellWidget(row, column); };
  283. break;
  284. }
  285. case AZ::RPI::ShaderOptionType::IntegerRange:
  286. {
  287. auto* spinBox = new AzQtComponents::StyledSpinBox(this);
  288. spinBox->setRange(valueMin.GetIndex(), valueMax.GetIndex());
  289. spinBox->setValue(value.GetIndex());
  290. setCellWidget(row, column, spinBox);
  291. connect(spinBox, &AzQtComponents::StyledSpinBox::textChanged, this, [this, row, column](const QString& text) {
  292. item(row, column)->setText(text);
  293. });
  294. break;
  295. }
  296. }
  297. }
  298. void ShaderManagementConsoleTableView::keyPressEvent(QKeyEvent* e)
  299. {
  300. if (e->key() == Qt::Key_Escape)
  301. {
  302. setCurrentCell(-1, -1);
  303. clearFocus();
  304. }
  305. else if (e->key() == Qt::Key_Menu)
  306. {
  307. ShowContextMenu(mapFromGlobal(QCursor::pos()));
  308. }
  309. }
  310. void ShaderManagementConsoleTableView::OnCellChanged(int row, int column)
  311. {
  312. if (row < 0 || row >= m_shaderVariantListSourceData.m_shaderVariants.size())
  313. {
  314. return;
  315. }
  316. if (column < 0 || column > m_shaderOptionDescriptors.size())
  317. {
  318. return;
  319. }
  320. // Update the shader variant list from the table data
  321. auto& shaderVariant = m_shaderVariantListSourceData.m_shaderVariants[row];
  322. const auto optionItem = horizontalHeaderItem(column);
  323. if (optionItem && !optionItem->text().isEmpty())
  324. {
  325. if (auto variantItem = item(row, column))
  326. {
  327. QSignalBlocker blocker(this);
  328. // Set or clear the option based on the item text
  329. if (variantItem->text().isEmpty())
  330. {
  331. shaderVariant.m_options.erase(AZ::Name{optionItem->text().toUtf8().constData()});
  332. variantItem->setIcon(m_emptyOptionIcon);
  333. variantItem->setToolTip(tr("runtime variable"));
  334. }
  335. else
  336. {
  337. shaderVariant.m_options[AZ::Name{optionItem->text().toUtf8().constData()}] = variantItem->text().toUtf8().constData();
  338. variantItem->setIcon({});
  339. variantItem->setToolTip("");
  340. }
  341. }
  342. }
  343. TransferViewModelToModel(KeepAsIs/*because we know the change is already reflected*/);
  344. }
  345. void ShaderManagementConsoleTableView::TransferViewModelToModel(RebuildMode mode)
  346. {
  347. // Temporarily disconnect the document notification bus to prevent recursive notification handling as changes are applied
  348. AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect();
  349. // Send the begin edit notification to signify the beginning of an undoable change
  350. AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
  351. m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
  352. // Set the shader variant list source data built from the table onto the document
  353. ShaderManagementConsoleDocumentRequestBus::Event(
  354. m_documentId,
  355. &ShaderManagementConsoleDocumentRequestBus::Events::SetShaderVariantListSourceData,
  356. m_shaderVariantListSourceData);
  357. // Signify the end of the undoable change
  358. AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
  359. m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
  360. // Reconnect to the notification bus now that all changes have been applied
  361. AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusConnect(m_toolId);
  362. if (mode == CallOnModified)
  363. {
  364. // manual call to the modified handler because when the bus is disconnected, events to this goes to naught.
  365. OnDocumentModified(m_documentId);
  366. }
  367. }
  368. void ShaderManagementConsoleTableView::SetColumnSortMode(ColumnSortMode m)
  369. {
  370. m_columnSortMode = m;
  371. RebuildTable();
  372. }
  373. ShaderManagementConsoleContainer::ShaderManagementConsoleContainer(QWidget* container, const AZ::Crc32& toolId, const AZ::Uuid& documentId, QWidget* parent)
  374. :
  375. QVBoxLayout(container)
  376. , m_tableView(toolId, documentId, parent)
  377. {
  378. m_sortLabel.setText(tr("Option sort mode:"));
  379. m_sortComboBox.addItem(tr("Alphabetical"));
  380. m_sortComboBox.addItem(tr("Rank (shader declaration order)"));
  381. m_sortComboBox.addItem(tr("Cost impact (likely-performance weight, by static-analysis)"));
  382. m_sortComboBox.setCurrentIndex(2);
  383. m_defragVariants.setIcon(QIcon(":/Icons/defrag.svg"));
  384. m_defragVariants.setToolTip(tr("Merge duplicated variants, and recompact stable IDs"));
  385. connect(&m_defragVariants,
  386. &QPushButton::clicked,
  387. this,
  388. [documentId]() {
  389. AtomToolsFramework::AtomToolsDocumentRequestBus::Event(documentId,
  390. &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
  391. ShaderManagementConsoleDocumentRequestBus::Event(documentId,
  392. &ShaderManagementConsoleDocumentRequestBus::Events::DefragmentVariantList);
  393. AtomToolsFramework::AtomToolsDocumentRequestBus::Event(documentId,
  394. &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
  395. });
  396. m_subLayout.addWidget(&m_sortLabel);
  397. m_subLayout.addWidget(&m_sortComboBox);
  398. m_subLayout.addWidget(&m_defragVariants);
  399. m_subLayout.addStretch();
  400. addLayout(&m_subLayout);
  401. addWidget(&m_tableView);
  402. connect(&m_sortComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this](int index) {
  403. m_tableView.SetColumnSortMode(ColumnSortMode(index));
  404. });
  405. }
  406. } // namespace ShaderManagementConsole
  407. #include <Window/moc_ShaderManagementConsoleTableView.cpp>