qt: Add a shader tools dialog with a viewer and editor

This commit is contained in:
ReinUsesLisp
2018-10-16 03:38:50 -03:00
parent b3254df4ec
commit dca38b7b8d
7 changed files with 372 additions and 0 deletions

View File

@@ -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

View File

@@ -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");

View File

@@ -0,0 +1,261 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <QAction>
#include <QFontDatabase>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QMessageBox>
#include <QScrollBar>
#include <QStandardItemModel>
#include <QTextEdit>
#include <QTreeView>
#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<Tegra::DebugContext> 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<QStandardItem*> 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<const u8*>(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<std::size_t>(*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<int>(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");
}
}

View File

@@ -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 <map>
#include <memory>
#include <optional>
#include <vector>
#include <QWidget>
#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<Tegra::DebugContext> 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<Tegra::DebugContext> context;
std::vector<VideoCore::ShaderInfo> shaders;
std::vector<bool> modified_shaders;
std::optional<int> active_shader;
// UX cached values
std::map<ShaderAddress, int> scroll_map;
std::optional<ShaderAddress> last_shader;
QAction* toggle_view_action = nullptr;
QHBoxLayout* layout = nullptr;
QStandardItemModel* item_model = nullptr;
QTreeView* tree_view = nullptr;
QTextEdit* code_editor = nullptr;
};

View File

@@ -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();

View File

@@ -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];

View File

@@ -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;