diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 34f36f06ba..20b9034c3d 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -48,6 +48,8 @@ add_executable(yuzu debugger/console.h debugger/profiler.cpp debugger/profiler.h + debugger/shader_tools.cpp + debugger/shader_tools.h debugger/wait_tree.cpp debugger/wait_tree.h discord.h diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index e24ed5f2b2..5348084867 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -467,6 +467,9 @@ void Config::ReadValues() { qt_config->value("microProfileDialogGeometry").toByteArray(); UISettings::values.microprofile_visible = qt_config->value("microProfileDialogVisible", false).toBool(); + UISettings::values.shader_tools_geometry = + qt_config->value("shaderToolsDialogGeometry").toByteArray(); + UISettings::values.shader_tools_visible = qt_config->value("shaderToolsDialogVisible").toBool(); qt_config->endGroup(); qt_config->beginGroup("Paths"); @@ -667,6 +670,8 @@ void Config::SaveValues() { qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state); qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry); qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible); + qt_config->setValue("shaderToolsDialogGeometry", UISettings::values.shader_tools_geometry); + qt_config->setValue("shaderToolsDialogVisible", UISettings::values.shader_tools_visible); qt_config->endGroup(); qt_config->beginGroup("Paths"); diff --git a/src/yuzu/debugger/shader_tools.cpp b/src/yuzu/debugger/shader_tools.cpp new file mode 100644 index 0000000000..ffadde30cf --- /dev/null +++ b/src/yuzu/debugger/shader_tools.cpp @@ -0,0 +1,261 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/core.h" +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "yuzu/debugger/shader_tools.h" + +ShaderToolsDialog::ShaderToolsDialog(std::shared_ptr debug_context, + QWidget* parent) + : context(debug_context), + QWidget(parent, Qt::Dialog), Tegra::DebugContext::BreakPointObserver(debug_context) { + + setObjectName("ShaderTools"); + setWindowTitle(tr("Shader Tools")); + resize(1024, 600); + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint); + + layout = new QHBoxLayout; + item_model = new QStandardItemModel; + tree_view = new QTreeView; + code_editor = new QTextEdit; + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setContextMenuPolicy(Qt::CustomContextMenu); + tree_view->setModel(item_model); + connect(tree_view, &QAbstractItemView::activated, this, &ShaderToolsDialog::onSelectionChanged); + + item_model->insertColumns(0, COLUMN_COUNT); + item_model->setHeaderData(COLUMN_STAGE, Qt::Horizontal, tr("Stage")); + item_model->setHeaderData(COLUMN_ADDRESS, Qt::Horizontal, tr("Address")); + + const QFont fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + code_editor->setFont(fixed_font); + + connect(this, &ShaderToolsDialog::BreakPointHit, this, &ShaderToolsDialog::OnBreakPointHit, + Qt::BlockingQueuedConnection); + connect(this, &ShaderToolsDialog::Resumed, this, &ShaderToolsDialog::OnResumed); + + layout->addWidget(code_editor); + layout->addWidget(tree_view); + layout->setStretchFactor(code_editor, 1); + setLayout(layout); + + DisableEditor(); +} + +void ShaderToolsDialog::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit BreakPointHit(event, data); +} + +void ShaderToolsDialog::OnMaxwellResume() { + emit Resumed(); +} + +void ShaderToolsDialog::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + auto& gpu = Core::System::GetInstance().GPU(); + shaders = gpu.Maxwell3D().GetShaderList(); + modified_shaders.resize(shaders.size()); + if (shaders.empty()) { + return; + } + + RemoveShaderList(); + for (const auto& shader : shaders) { + QList items; + items.append(new QStandardItem(GetShaderStageName(shader.stage))); + items.append(new QStandardItem(fmt::format("0x{:08x}", shader.addr).c_str())); + + item_model->appendRow(items); + } + + RestoreSelectedShader(); +} + +void ShaderToolsDialog::OnResumed() { + SaveActiveCode(); + SaveScrollPosition(); + SaveSelectedShader(); + active_shader = {}; + + auto& gpu = Core::System::GetInstance().GPU(); + for (std::size_t i = 0; i < shaders.size(); ++i) { + if (!modified_shaders[i]) { + continue; + } + const auto& shader = shaders[i]; + const auto* code = shader.code.c_str(); + gpu.Maxwell3D().InjectShader(shader.addr, shader.code.size(), + reinterpret_cast(code)); + } + + RemoveShaderList(); + DisableEditor(); +} + +QAction* ShaderToolsDialog::toggleViewAction() { + if (toggle_view_action != nullptr) { + return toggle_view_action; + } + toggle_view_action = new QAction(windowTitle(), this); + toggle_view_action->setCheckable(true); + toggle_view_action->setChecked(isVisible()); + connect(toggle_view_action, &QAction::toggled, this, &ShaderToolsDialog::setVisible); + return toggle_view_action; +} + +void ShaderToolsDialog::showEvent(QShowEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::showEvent(ev); +} + +void ShaderToolsDialog::hideEvent(QHideEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::hideEvent(ev); +} + +void ShaderToolsDialog::onSelectionChanged() { + SaveActiveCode(); + SaveScrollPosition(); + + SelectShader(tree_view->currentIndex().row()); + + RestoreScrollPosition(); +} + +void ShaderToolsDialog::SelectShader(int index) { + active_shader = std::make_optional(index); + + if (static_cast(*active_shader) >= shaders.size()) { + QMessageBox message_box; + message_box.setWindowTitle(tr("Shader Tools")); + message_box.setText(tr("Tried to select an out of bounds shader")); + message_box.setIcon(QMessageBox::Critical); + message_box.exec(); + return; + } + + auto& shader = shaders[*active_shader]; + code_editor->setText(shader.code.c_str()); + + EnableEditor(); + RestoreScrollPosition(); +} + +void ShaderToolsDialog::DisableEditor() { + code_editor->setText(""); + code_editor->setDisabled(true); +} + +void ShaderToolsDialog::EnableEditor() { + code_editor->setDisabled(false); +} + +void ShaderToolsDialog::RemoveShaderList() { + item_model->removeRows(0, item_model->rowCount()); +} + +const char* ShaderToolsDialog::GetActiveCode() const { + return code_editor->toPlainText().toUtf8().constData(); +} + +void ShaderToolsDialog::SaveActiveCode() { + if (!active_shader) { + return; + } + auto& shader = shaders[*active_shader]; + const std::string new_code = GetActiveCode(); + if (shader.code != new_code) { + shader.code = GetActiveCode(); + modified_shaders[*active_shader] = true; + } +} + +void ShaderToolsDialog::SaveScrollPosition() { + if (!active_shader) { + return; + } + auto& shader = shaders[*active_shader]; + const int scroll_pos = code_editor->verticalScrollBar()->value(); + scroll_map.insert_or_assign(shader.addr, scroll_pos); +} + +void ShaderToolsDialog::RestoreScrollPosition() { + if (!active_shader) { + return; + } + auto& shader = shaders[*active_shader]; + if (const auto it = scroll_map.find(shader.addr); it != scroll_map.end()) { + const int scroll_pos = it->second; + code_editor->verticalScrollBar()->setValue(scroll_pos); + } +} + +void ShaderToolsDialog::SaveSelectedShader() { + if (!active_shader) { + return; + } + auto& shader = shaders[*active_shader]; + last_shader = std::make_optional(shader.addr); +} + +void ShaderToolsDialog::RestoreSelectedShader() { + if (!last_shader) { + return; + } + const auto it = + std::find_if(shaders.begin(), shaders.end(), [&](const VideoCore::ShaderInfo& shader) { + return shader.addr == *last_shader; + }); + if (it == shaders.end()) + return; + + const auto index = static_cast(std::distance(shaders.begin(), it)); + tree_view->setCurrentIndex(item_model->index(index, 0)); + SelectShader(index); +} + +QString ShaderToolsDialog::GetShaderStageName(VideoCore::ShaderStage stage) { + switch (stage) { + case VideoCore::ShaderStage::Vertex: + return tr("Vertex"); + case VideoCore::ShaderStage::TesselationControl: + return tr("Hull"); + case VideoCore::ShaderStage::TesselationEval: + return tr("Domain"); + case VideoCore::ShaderStage::Geometry: + return tr("Geometry"); + case VideoCore::ShaderStage::Fragment: + return tr("Pixel"); + case VideoCore::ShaderStage::Compute: + return tr("Compute"); + default: + return tr("Invalid"); + } +} \ No newline at end of file diff --git a/src/yuzu/debugger/shader_tools.h b/src/yuzu/debugger/shader_tools.h new file mode 100644 index 0000000000..fad0bf8726 --- /dev/null +++ b/src/yuzu/debugger/shader_tools.h @@ -0,0 +1,89 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include + +#include "common/common_types.h" +#include "video_core/shader_info.h" + +class QHBoxLayout; +class QStandardItemModel; +class QTreeView; +class QTextEdit; + +class ShaderToolsDialog : public QWidget, Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + enum { + COLUMN_STAGE, + COLUMN_ADDRESS, + COLUMN_COUNT, + }; + + explicit ShaderToolsDialog(std::shared_ptr debug_context, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + + /// Returns a QAction that can be used to toggle visibility of this dialog. + QAction* toggleViewAction(); + +signals: + void BreakPointHit(Tegra::DebugContext::Event event, void* data); + void Resumed(); + +protected: + void showEvent(QShowEvent* ev) override; + void hideEvent(QHideEvent* ev) override; + +private: + static QString GetShaderStageName(VideoCore::ShaderStage stage); + + void onSelectionChanged(); + + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); + void OnResumed(); + + void SelectShader(int index); + + void DisableEditor(); + void EnableEditor(); + void RemoveShaderList(); + + const char* GetActiveCode() const; + void SaveActiveCode(); + + void SaveScrollPosition(); + void RestoreScrollPosition(); + + void SaveSelectedShader(); + void RestoreSelectedShader(); + + using ShaderAddress = u64; + + std::shared_ptr context; + + std::vector shaders; + std::vector modified_shaders; + std::optional active_shader; + + // UX cached values + std::map scroll_map; + std::optional last_shader; + + QAction* toggle_view_action = nullptr; + QHBoxLayout* layout = nullptr; + QStandardItemModel* item_model = nullptr; + QTreeView* tree_view = nullptr; + QTextEdit* code_editor = nullptr; +}; \ No newline at end of file diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4b969119c0..658f9aa93f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -79,6 +79,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/debugger/graphics/graphics_breakpoints.h" #include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" +#include "yuzu/debugger/shader_tools.h" #include "yuzu/debugger/wait_tree.h" #include "yuzu/discord.h" #include "yuzu/game_list.h" @@ -254,6 +255,10 @@ void GMainWindow::InitializeDebugWidgets() { debug_menu->addAction(microProfileDialog->toggleViewAction()); #endif + shaderToolsDialog = new ShaderToolsDialog(debug_context, this); + shaderToolsDialog->hide(); + debug_menu->addAction(shaderToolsDialog->toggleViewAction()); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); @@ -392,6 +397,9 @@ void GMainWindow::RestoreUIState() { microProfileDialog->setVisible(UISettings::values.microprofile_visible); #endif + shaderToolsDialog->restoreGeometry(UISettings::values.shader_tools_geometry); + shaderToolsDialog->setVisible(UISettings::values.shader_tools_visible); + game_list->LoadInterfaceLayout(); ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); @@ -1645,6 +1653,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) { UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif + UISettings::values.shader_tools_geometry = shaderToolsDialog->saveGeometry(); + UISettings::values.shader_tools_visible = shaderToolsDialog->isVisible(); UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 929250e8cd..2c7bf4747e 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -26,6 +26,7 @@ class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; +class ShaderToolsDialog; class WaitTreeWidget; enum class GameListOpenTarget; @@ -212,6 +213,7 @@ private: MicroProfileDialog* microProfileDialog; GraphicsBreakPointsWidget* graphicsBreakpointsWidget; GraphicsSurfaceWidget* graphicsSurfaceWidget; + ShaderToolsDialog* shaderToolsDialog; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index e80aebc0a2..7a118f0256 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -30,6 +30,9 @@ struct Values { QByteArray microprofile_geometry; bool microprofile_visible; + QByteArray shader_tools_geometry; + bool shader_tools_visible; + bool single_window_mode; bool fullscreen; bool display_titlebar;