qt: Add a shader tools dialog with a viewer and editor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
261
src/yuzu/debugger/shader_tools.cpp
Normal file
261
src/yuzu/debugger/shader_tools.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
89
src/yuzu/debugger/shader_tools.h
Normal file
89
src/yuzu/debugger/shader_tools.h
Normal 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;
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user