summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Subv2018-03-22 15:19:35 -0500
committerGravatar Subv2018-03-24 11:31:49 -0500
commit77fd0d47e70968bcbc87a3b5607cd29e6211f656 (patch)
tree54e91cede780bbd5bec2612547a61bdd799e36af /src
parentGPU: Added a method to unswizzle a texture without decoding it. (diff)
downloadyuzu-77fd0d47e70968bcbc87a3b5607cd29e6211f656.tar.gz
yuzu-77fd0d47e70968bcbc87a3b5607cd29e6211f656.tar.xz
yuzu-77fd0d47e70968bcbc87a3b5607cd29e6211f656.zip
Frontend: Ported the GPU breakpoints and surface viewer widgets from citra.
Diffstat (limited to 'src')
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp66
-rw-r--r--src/video_core/debug_utils/debug_utils.h165
-rw-r--r--src/video_core/gpu.cpp4
-rw-r--r--src/video_core/gpu.h5
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp27
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.h33
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp212
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.h46
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints_p.h36
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp445
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h97
-rw-r--r--src/yuzu/main.cpp9
-rw-r--r--src/yuzu/main.h5
15 files changed, 1155 insertions, 4 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 8c0e6663b..3dab81769 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,6 +1,8 @@
1add_library(video_core STATIC 1add_library(video_core STATIC
2 command_processor.cpp 2 command_processor.cpp
3 command_processor.h 3 command_processor.h
4 debug_utils/debug_utils.cpp
5 debug_utils/debug_utils.h
4 engines/fermi_2d.cpp 6 engines/fermi_2d.cpp
5 engines/fermi_2d.h 7 engines/fermi_2d.h
6 engines/maxwell_3d.cpp 8 engines/maxwell_3d.cpp
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
new file mode 100644
index 000000000..73fd4d7a3
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -0,0 +1,66 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <condition_variable>
7#include <cstdint>
8#include <cstring>
9#include <fstream>
10#include <map>
11#include <mutex>
12#include <string>
13
14#include "common/assert.h"
15#include "common/bit_field.h"
16#include "common/color.h"
17#include "common/common_types.h"
18#include "common/file_util.h"
19#include "common/logging/log.h"
20#include "common/math_util.h"
21#include "common/vector_math.h"
22#include "video_core/debug_utils/debug_utils.h"
23
24namespace Tegra {
25
26std::shared_ptr<DebugContext> g_debug_context;
27
28void DebugContext::DoOnEvent(Event event, void* data) {
29 {
30 std::unique_lock<std::mutex> lock(breakpoint_mutex);
31
32 // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will
33 // show on debug widgets
34
35 // TODO: Should stop the CPU thread here once we multithread emulation.
36
37 active_breakpoint = event;
38 at_breakpoint = true;
39
40 // Tell all observers that we hit a breakpoint
41 for (auto& breakpoint_observer : breakpoint_observers) {
42 breakpoint_observer->OnMaxwellBreakPointHit(event, data);
43 }
44
45 // Wait until another thread tells us to Resume()
46 resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; });
47 }
48}
49
50void DebugContext::Resume() {
51 {
52 std::lock_guard<std::mutex> lock(breakpoint_mutex);
53
54 // Tell all observers that we are about to resume
55 for (auto& breakpoint_observer : breakpoint_observers) {
56 breakpoint_observer->OnMaxwellResume();
57 }
58
59 // Resume the waiting thread (i.e. OnEvent())
60 at_breakpoint = false;
61 }
62
63 resume_from_breakpoint.notify_one();
64}
65
66} // namespace Tegra
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
new file mode 100644
index 000000000..98461d6d9
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -0,0 +1,165 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <algorithm>
8#include <array>
9#include <condition_variable>
10#include <iterator>
11#include <list>
12#include <map>
13#include <memory>
14#include <mutex>
15#include <string>
16#include <utility>
17#include <vector>
18#include "common/common_types.h"
19#include "common/vector_math.h"
20
21namespace Tegra {
22
23class DebugContext {
24public:
25 enum class Event {
26 FirstEvent = 0,
27
28 MaxwellCommandLoaded = FirstEvent,
29 MaxwellCommandProcessed,
30 IncomingPrimitiveBatch,
31 FinishedPrimitiveBatch,
32
33 NumEvents
34 };
35
36 /**
37 * Inherit from this class to be notified of events registered to some debug context.
38 * Most importantly this is used for our debugger GUI.
39 *
40 * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods.
41 * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
42 * access
43 * @todo Evaluate an alternative interface, in which there is only one managing observer and
44 * multiple child observers running (by design) on the same thread.
45 */
46 class BreakPointObserver {
47 public:
48 /// Constructs the object such that it observes events of the given DebugContext.
49 BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
50 : context_weak(debug_context) {
51 std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
52 debug_context->breakpoint_observers.push_back(this);
53 }
54
55 virtual ~BreakPointObserver() {
56 auto context = context_weak.lock();
57 if (context) {
58 std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
59 context->breakpoint_observers.remove(this);
60
61 // If we are the last observer to be destroyed, tell the debugger context that
62 // it is free to continue. In particular, this is required for a proper yuzu
63 // shutdown, when the emulation thread is waiting at a breakpoint.
64 if (context->breakpoint_observers.empty())
65 context->Resume();
66 }
67 }
68
69 /**
70 * Action to perform when a breakpoint was reached.
71 * @param event Type of event which triggered the breakpoint
72 * @param data Optional data pointer (if unused, this is a nullptr)
73 * @note This function will perform nothing unless it is overridden in the child class.
74 */
75 virtual void OnMaxwellBreakPointHit(Event event, void* data) {}
76
77 /**
78 * Action to perform when emulation is resumed from a breakpoint.
79 * @note This function will perform nothing unless it is overridden in the child class.
80 */
81 virtual void OnMaxwellResume() {}
82
83 protected:
84 /**
85 * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
86 * context_weak.lock(), always compare the result against nullptr.
87 */
88 std::weak_ptr<DebugContext> context_weak;
89 };
90
91 /**
92 * Simple structure defining a breakpoint state
93 */
94 struct BreakPoint {
95 bool enabled = false;
96 };
97
98 /**
99 * Static constructor used to create a shared_ptr of a DebugContext.
100 */
101 static std::shared_ptr<DebugContext> Construct() {
102 return std::shared_ptr<DebugContext>(new DebugContext);
103 }
104
105 /**
106 * Used by the emulation core when a given event has happened. If a breakpoint has been set
107 * for this event, OnEvent calls the event handlers of the registered breakpoint observers.
108 * The current thread then is halted until Resume() is called from another thread (or until
109 * emulation is stopped).
110 * @param event Event which has happened
111 * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
112 * Resume() is called.
113 */
114 void OnEvent(Event event, void* data) {
115 // This check is left in the header to allow the compiler to inline it.
116 if (!breakpoints[(int)event].enabled)
117 return;
118 // For the rest of event handling, call a separate function.
119 DoOnEvent(event, data);
120 }
121
122 void DoOnEvent(Event event, void* data);
123
124 /**
125 * Resume from the current breakpoint.
126 * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
127 * Calling from any other thread is safe.
128 */
129 void Resume();
130
131 /**
132 * Delete all set breakpoints and resume emulation.
133 */
134 void ClearBreakpoints() {
135 for (auto& bp : breakpoints) {
136 bp.enabled = false;
137 }
138 Resume();
139 }
140
141 // TODO: Evaluate if access to these members should be hidden behind a public interface.
142 std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
143 Event active_breakpoint;
144 bool at_breakpoint = false;
145
146private:
147 /**
148 * Private default constructor to make sure people always construct this through Construct()
149 * instead.
150 */
151 DebugContext() = default;
152
153 /// Mutex protecting current breakpoint state and the observer list.
154 std::mutex breakpoint_mutex;
155
156 /// Used by OnEvent to wait for resumption.
157 std::condition_variable resume_from_breakpoint;
158
159 /// List of registered observers
160 std::list<BreakPointObserver*> breakpoint_observers;
161};
162
163extern std::shared_ptr<DebugContext> g_debug_context;
164
165} // namespace Tegra
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index c384d236e..9463cd5d6 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -18,4 +18,8 @@ GPU::GPU() {
18 18
19GPU::~GPU() = default; 19GPU::~GPU() = default;
20 20
21const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const {
22 return *maxwell_3d;
23}
24
21} // namespace Tegra 25} // namespace Tegra
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 206b3e05e..778b63218 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -13,6 +13,8 @@
13 13
14namespace Tegra { 14namespace Tegra {
15 15
16class DebugContext;
17
16/** 18/**
17 * Struct describing framebuffer configuration 19 * Struct describing framebuffer configuration
18 */ 20 */
@@ -66,6 +68,9 @@ public:
66 /// Processes a command list stored at the specified address in GPU memory. 68 /// Processes a command list stored at the specified address in GPU memory.
67 void ProcessCommandList(GPUVAddr address, u32 size); 69 void ProcessCommandList(GPUVAddr address, u32 size);
68 70
71 /// Returns a reference to the Maxwell3D GPU engine.
72 const Engines::Maxwell3D& Get3DEngine() const;
73
69 std::unique_ptr<MemoryManager> memory_manager; 74 std::unique_ptr<MemoryManager> memory_manager;
70 75
71 Engines::Maxwell3D& Maxwell3D() { 76 Engines::Maxwell3D& Maxwell3D() {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 0c4056c49..5af3154d7 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -23,6 +23,13 @@ add_executable(yuzu
23 configuration/configure_input.h 23 configuration/configure_input.h
24 configuration/configure_system.cpp 24 configuration/configure_system.cpp
25 configuration/configure_system.h 25 configuration/configure_system.h
26 debugger/graphics/graphics_breakpoint_observer.cpp
27 debugger/graphics/graphics_breakpoint_observer.h
28 debugger/graphics/graphics_breakpoints.cpp
29 debugger/graphics/graphics_breakpoints.h
30 debugger/graphics/graphics_breakpoints_p.h
31 debugger/graphics/graphics_surface.cpp
32 debugger/graphics/graphics_surface.h
26 debugger/profiler.cpp 33 debugger/profiler.cpp
27 debugger/profiler.h 34 debugger/profiler.h
28 debugger/registers.cpp 35 debugger/registers.cpp
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
new file mode 100644
index 000000000..d6d61a739
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
@@ -0,0 +1,27 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QMetaType>
6#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
7
8BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context,
9 const QString& title, QWidget* parent)
10 : QDockWidget(title, parent), BreakPointObserver(debug_context) {
11 qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
12
13 connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
14
15 // NOTE: This signal is emitted from a non-GUI thread, but connect() takes
16 // care of delaying its handling to the GUI thread.
17 connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
18 SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
19}
20
21void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) {
22 emit BreakPointHit(event, data);
23}
24
25void BreakPointObserverDock::OnMaxwellResume() {
26 emit Resumed();
27}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
new file mode 100644
index 000000000..9d05493cf
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
@@ -0,0 +1,33 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QDockWidget>
8#include "video_core/debug_utils/debug_utils.h"
9
10/**
11 * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots.
12 * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while
13 * the widget usually wants to perform reactions in the GUI thread.
14 */
15class BreakPointObserverDock : public QDockWidget,
16 protected Tegra::DebugContext::BreakPointObserver {
17 Q_OBJECT
18
19public:
20 BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title,
21 QWidget* parent = nullptr);
22
23 void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
24 void OnMaxwellResume() override;
25
26private slots:
27 virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0;
28 virtual void OnResumed() = 0;
29
30signals:
31 void Resumed();
32 void BreakPointHit(Tegra::DebugContext::Event event, void* data);
33};
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
new file mode 100644
index 000000000..f98cc8152
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
@@ -0,0 +1,212 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QLabel>
6#include <QMetaType>
7#include <QPushButton>
8#include <QTreeView>
9#include <QVBoxLayout>
10#include "common/assert.h"
11#include "yuzu/debugger/graphics/graphics_breakpoints.h"
12#include "yuzu/debugger/graphics/graphics_breakpoints_p.h"
13
14BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context,
15 QObject* parent)
16 : QAbstractListModel(parent), context_weak(debug_context),
17 at_breakpoint(debug_context->at_breakpoint),
18 active_breakpoint(debug_context->active_breakpoint) {}
19
20int BreakPointModel::columnCount(const QModelIndex& parent) const {
21 return 1;
22}
23
24int BreakPointModel::rowCount(const QModelIndex& parent) const {
25 return static_cast<int>(Tegra::DebugContext::Event::NumEvents);
26}
27
28QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
29 const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
30
31 switch (role) {
32 case Qt::DisplayRole: {
33 if (index.column() == 0) {
34 static const std::map<Tegra::DebugContext::Event, QString> map = {
35 {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
36 {Tegra::DebugContext::Event::MaxwellCommandProcessed,
37 tr("Maxwell command processed")},
38 {Tegra::DebugContext::Event::IncomingPrimitiveBatch,
39 tr("Incoming primitive batch")},
40 {Tegra::DebugContext::Event::FinishedPrimitiveBatch,
41 tr("Finished primitive batch")},
42 };
43
44 DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents));
45 return (map.find(event) != map.end()) ? map.at(event) : QString();
46 }
47
48 break;
49 }
50
51 case Qt::CheckStateRole: {
52 if (index.column() == 0)
53 return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
54 break;
55 }
56
57 case Qt::BackgroundRole: {
58 if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
59 return QBrush(QColor(0xE0, 0xE0, 0x10));
60 }
61 break;
62 }
63
64 case Role_IsEnabled: {
65 auto context = context_weak.lock();
66 return context && context->breakpoints[(int)event].enabled;
67 }
68
69 default:
70 break;
71 }
72 return QVariant();
73}
74
75Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
76 if (!index.isValid())
77 return 0;
78
79 Qt::ItemFlags flags = Qt::ItemIsEnabled;
80 if (index.column() == 0)
81 flags |= Qt::ItemIsUserCheckable;
82 return flags;
83}
84
85bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
86 const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
87
88 switch (role) {
89 case Qt::CheckStateRole: {
90 if (index.column() != 0)
91 return false;
92
93 auto context = context_weak.lock();
94 if (!context)
95 return false;
96
97 context->breakpoints[(int)event].enabled = value == Qt::Checked;
98 QModelIndex changed_index = createIndex(index.row(), 0);
99 emit dataChanged(changed_index, changed_index);
100 return true;
101 }
102 }
103
104 return false;
105}
106
107void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) {
108 auto context = context_weak.lock();
109 if (!context)
110 return;
111
112 active_breakpoint = context->active_breakpoint;
113 at_breakpoint = context->at_breakpoint;
114 emit dataChanged(createIndex(static_cast<int>(event), 0),
115 createIndex(static_cast<int>(event), 0));
116}
117
118void BreakPointModel::OnResumed() {
119 auto context = context_weak.lock();
120 if (!context)
121 return;
122
123 at_breakpoint = context->at_breakpoint;
124 emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
125 createIndex(static_cast<int>(active_breakpoint), 0));
126 active_breakpoint = context->active_breakpoint;
127}
128
129GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
130 std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
131 : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
132 debug_context) {
133 setObjectName("TegraBreakPointsWidget");
134
135 status_text = new QLabel(tr("Emulation running"));
136 resume_button = new QPushButton(tr("Resume"));
137 resume_button->setEnabled(false);
138
139 breakpoint_model = new BreakPointModel(debug_context, this);
140 breakpoint_list = new QTreeView;
141 breakpoint_list->setRootIsDecorated(false);
142 breakpoint_list->setHeaderHidden(true);
143 breakpoint_list->setModel(breakpoint_model);
144
145 qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
146
147 connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
148 SLOT(OnItemDoubleClicked(const QModelIndex&)));
149
150 connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
151
152 connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
153 SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
154 connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
155
156 connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model,
157 SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection);
158 connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
159
160 connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
161 breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
162
163 QWidget* main_widget = new QWidget;
164 auto main_layout = new QVBoxLayout;
165 {
166 auto sub_layout = new QHBoxLayout;
167 sub_layout->addWidget(status_text);
168 sub_layout->addWidget(resume_button);
169 main_layout->addLayout(sub_layout);
170 }
171 main_layout->addWidget(breakpoint_list);
172 main_widget->setLayout(main_layout);
173
174 setWidget(main_widget);
175}
176
177void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) {
178 // Process in GUI thread
179 emit BreakPointHit(event, data);
180}
181
182void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
183 status_text->setText(tr("Emulation halted at breakpoint"));
184 resume_button->setEnabled(true);
185}
186
187void GraphicsBreakPointsWidget::OnMaxwellResume() {
188 // Process in GUI thread
189 emit Resumed();
190}
191
192void GraphicsBreakPointsWidget::OnResumed() {
193 status_text->setText(tr("Emulation running"));
194 resume_button->setEnabled(false);
195}
196
197void GraphicsBreakPointsWidget::OnResumeRequested() {
198 if (auto context = context_weak.lock())
199 context->Resume();
200}
201
202void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
203 if (!index.isValid())
204 return;
205
206 QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0);
207 QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole);
208 QVariant new_state = Qt::Unchecked;
209 if (enabled == Qt::Unchecked)
210 new_state = Qt::Checked;
211 breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole);
212}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h
new file mode 100644
index 000000000..ae0ede2e8
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h
@@ -0,0 +1,46 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QDockWidget>
9#include "video_core/debug_utils/debug_utils.h"
10
11class QLabel;
12class QPushButton;
13class QTreeView;
14
15class BreakPointModel;
16
17class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver {
18 Q_OBJECT
19
20 using Event = Tegra::DebugContext::Event;
21
22public:
23 explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
24 QWidget* parent = nullptr);
25
26 void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
27 void OnMaxwellResume() override;
28
29public slots:
30 void OnBreakPointHit(Tegra::DebugContext::Event event, void* data);
31 void OnItemDoubleClicked(const QModelIndex&);
32 void OnResumeRequested();
33 void OnResumed();
34
35signals:
36 void Resumed();
37 void BreakPointHit(Tegra::DebugContext::Event event, void* data);
38 void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
39
40private:
41 QLabel* status_text;
42 QPushButton* resume_button;
43
44 BreakPointModel* breakpoint_model;
45 QTreeView* breakpoint_list;
46};
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
new file mode 100644
index 000000000..35a6876ae
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
@@ -0,0 +1,36 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QAbstractListModel>
9#include "video_core/debug_utils/debug_utils.h"
10
11class BreakPointModel : public QAbstractListModel {
12 Q_OBJECT
13
14public:
15 enum {
16 Role_IsEnabled = Qt::UserRole,
17 };
18
19 BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent);
20
21 int columnCount(const QModelIndex& parent = QModelIndex()) const override;
22 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
23 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
24 Qt::ItemFlags flags(const QModelIndex& index) const override;
25
26 bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
27
28public slots:
29 void OnBreakPointHit(Tegra::DebugContext::Event event);
30 void OnResumed();
31
32private:
33 std::weak_ptr<Tegra::DebugContext> context_weak;
34 bool at_breakpoint;
35 Tegra::DebugContext::Event active_breakpoint;
36};
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
new file mode 100644
index 000000000..54b816054
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -0,0 +1,445 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QBoxLayout>
6#include <QComboBox>
7#include <QDebug>
8#include <QFileDialog>
9#include <QLabel>
10#include <QMouseEvent>
11#include <QPushButton>
12#include <QScrollArea>
13#include <QSpinBox>
14#include "core/core.h"
15#include "video_core/engines/maxwell_3d.h"
16#include "video_core/textures/decoders.h"
17#include "video_core/textures/texture.h"
18#include "video_core/utils.h"
19#include "yuzu/debugger/graphics/graphics_surface.h"
20#include "yuzu/util/spinbox.h"
21
22SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
23 : QLabel(parent), surface_widget(surface_widget_) {}
24SurfacePicture::~SurfacePicture() {}
25
26void SurfacePicture::mousePressEvent(QMouseEvent* event) {
27 // Only do something while the left mouse button is held down
28 if (!(event->buttons() & Qt::LeftButton))
29 return;
30
31 if (pixmap() == nullptr)
32 return;
33
34 if (surface_widget)
35 surface_widget->Pick(event->x() * pixmap()->width() / width(),
36 event->y() * pixmap()->height() / height());
37}
38
39void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
40 // We also want to handle the event if the user moves the mouse while holding down the LMB
41 mousePressEvent(event);
42}
43
44GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
45 QWidget* parent)
46 : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
47 surface_source(Source::RenderTarget0) {
48 setObjectName("MaxwellSurface");
49
50 surface_source_list = new QComboBox;
51 surface_source_list->addItem(tr("Render Target 0"));
52 surface_source_list->addItem(tr("Render Target 1"));
53 surface_source_list->addItem(tr("Render Target 2"));
54 surface_source_list->addItem(tr("Render Target 3"));
55 surface_source_list->addItem(tr("Render Target 4"));
56 surface_source_list->addItem(tr("Render Target 5"));
57 surface_source_list->addItem(tr("Render Target 6"));
58 surface_source_list->addItem(tr("Render Target 7"));
59 surface_source_list->addItem(tr("Z Buffer"));
60 surface_source_list->addItem(tr("Custom"));
61 surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
62
63 surface_address_control = new CSpinBox;
64 surface_address_control->SetBase(16);
65 surface_address_control->SetRange(0, 0xFFFFFFFF);
66 surface_address_control->SetPrefix("0x");
67
68 unsigned max_dimension = 16384; // TODO: Find actual maximum
69
70 surface_width_control = new QSpinBox;
71 surface_width_control->setRange(0, max_dimension);
72
73 surface_height_control = new QSpinBox;
74 surface_height_control->setRange(0, max_dimension);
75
76 surface_picker_x_control = new QSpinBox;
77 surface_picker_x_control->setRange(0, max_dimension - 1);
78
79 surface_picker_y_control = new QSpinBox;
80 surface_picker_y_control->setRange(0, max_dimension - 1);
81
82 surface_format_control = new QComboBox;
83
84 // Color formats sorted by Maxwell texture format index
85 surface_format_control->addItem(tr("None"));
86 surface_format_control->addItem(tr("Unknown"));
87 surface_format_control->addItem(tr("Unknown"));
88 surface_format_control->addItem(tr("Unknown"));
89 surface_format_control->addItem(tr("Unknown"));
90 surface_format_control->addItem(tr("Unknown"));
91 surface_format_control->addItem(tr("Unknown"));
92 surface_format_control->addItem(tr("Unknown"));
93 surface_format_control->addItem(tr("A8R8G8B8"));
94 surface_format_control->addItem(tr("Unknown"));
95 surface_format_control->addItem(tr("Unknown"));
96 surface_format_control->addItem(tr("Unknown"));
97 surface_format_control->addItem(tr("Unknown"));
98 surface_format_control->addItem(tr("Unknown"));
99 surface_format_control->addItem(tr("Unknown"));
100 surface_format_control->addItem(tr("Unknown"));
101 surface_format_control->addItem(tr("Unknown"));
102 surface_format_control->addItem(tr("Unknown"));
103 surface_format_control->addItem(tr("Unknown"));
104 surface_format_control->addItem(tr("Unknown"));
105 surface_format_control->addItem(tr("Unknown"));
106 surface_format_control->addItem(tr("Unknown"));
107 surface_format_control->addItem(tr("Unknown"));
108 surface_format_control->addItem(tr("Unknown"));
109 surface_format_control->addItem(tr("Unknown"));
110 surface_format_control->addItem(tr("Unknown"));
111 surface_format_control->addItem(tr("Unknown"));
112 surface_format_control->addItem(tr("Unknown"));
113 surface_format_control->addItem(tr("Unknown"));
114 surface_format_control->addItem(tr("Unknown"));
115 surface_format_control->addItem(tr("Unknown"));
116 surface_format_control->addItem(tr("Unknown"));
117 surface_format_control->addItem(tr("Unknown"));
118 surface_format_control->addItem(tr("Unknown"));
119 surface_format_control->addItem(tr("Unknown"));
120 surface_format_control->addItem(tr("Unknown"));
121 surface_format_control->addItem(tr("DXT1"));
122 surface_format_control->addItem(tr("DXT23"));
123 surface_format_control->addItem(tr("DXT45"));
124 surface_format_control->addItem(tr("DXN1"));
125 surface_format_control->addItem(tr("DXN2"));
126
127 surface_info_label = new QLabel();
128 surface_info_label->setWordWrap(true);
129
130 surface_picture_label = new SurfacePicture(0, this);
131 surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
132 surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
133 surface_picture_label->setScaledContents(false);
134
135 auto scroll_area = new QScrollArea();
136 scroll_area->setBackgroundRole(QPalette::Dark);
137 scroll_area->setWidgetResizable(false);
138 scroll_area->setWidget(surface_picture_label);
139
140 save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
141
142 // Connections
143 connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
144 connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
145 SLOT(OnSurfaceSourceChanged(int)));
146 connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
147 SLOT(OnSurfaceAddressChanged(qint64)));
148 connect(surface_width_control, SIGNAL(valueChanged(int)), this,
149 SLOT(OnSurfaceWidthChanged(int)));
150 connect(surface_height_control, SIGNAL(valueChanged(int)), this,
151 SLOT(OnSurfaceHeightChanged(int)));
152 connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
153 SLOT(OnSurfaceFormatChanged(int)));
154 connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
155 SLOT(OnSurfacePickerXChanged(int)));
156 connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
157 SLOT(OnSurfacePickerYChanged(int)));
158 connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
159
160 auto main_widget = new QWidget;
161 auto main_layout = new QVBoxLayout;
162 {
163 auto sub_layout = new QHBoxLayout;
164 sub_layout->addWidget(new QLabel(tr("Source:")));
165 sub_layout->addWidget(surface_source_list);
166 main_layout->addLayout(sub_layout);
167 }
168 {
169 auto sub_layout = new QHBoxLayout;
170 sub_layout->addWidget(new QLabel(tr("GPU Address:")));
171 sub_layout->addWidget(surface_address_control);
172 main_layout->addLayout(sub_layout);
173 }
174 {
175 auto sub_layout = new QHBoxLayout;
176 sub_layout->addWidget(new QLabel(tr("Width:")));
177 sub_layout->addWidget(surface_width_control);
178 main_layout->addLayout(sub_layout);
179 }
180 {
181 auto sub_layout = new QHBoxLayout;
182 sub_layout->addWidget(new QLabel(tr("Height:")));
183 sub_layout->addWidget(surface_height_control);
184 main_layout->addLayout(sub_layout);
185 }
186 {
187 auto sub_layout = new QHBoxLayout;
188 sub_layout->addWidget(new QLabel(tr("Format:")));
189 sub_layout->addWidget(surface_format_control);
190 main_layout->addLayout(sub_layout);
191 }
192 main_layout->addWidget(scroll_area);
193
194 auto info_layout = new QHBoxLayout;
195 {
196 auto xy_layout = new QVBoxLayout;
197 {
198 {
199 auto sub_layout = new QHBoxLayout;
200 sub_layout->addWidget(new QLabel(tr("X:")));
201 sub_layout->addWidget(surface_picker_x_control);
202 xy_layout->addLayout(sub_layout);
203 }
204 {
205 auto sub_layout = new QHBoxLayout;
206 sub_layout->addWidget(new QLabel(tr("Y:")));
207 sub_layout->addWidget(surface_picker_y_control);
208 xy_layout->addLayout(sub_layout);
209 }
210 }
211 info_layout->addLayout(xy_layout);
212 surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
213 info_layout->addWidget(surface_info_label);
214 }
215 main_layout->addLayout(info_layout);
216
217 main_layout->addWidget(save_surface);
218 main_widget->setLayout(main_layout);
219 setWidget(main_widget);
220
221 // Load current data - TODO: Make sure this works when emulation is not running
222 if (debug_context && debug_context->at_breakpoint) {
223 emit Update();
224 widget()->setEnabled(debug_context->at_breakpoint);
225 } else {
226 widget()->setEnabled(false);
227 }
228}
229
230void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
231 emit Update();
232 widget()->setEnabled(true);
233}
234
235void GraphicsSurfaceWidget::OnResumed() {
236 widget()->setEnabled(false);
237}
238
239void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
240 surface_source = static_cast<Source>(new_value);
241 emit Update();
242}
243
244void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
245 if (surface_address != new_value) {
246 surface_address = static_cast<unsigned>(new_value);
247
248 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
249 emit Update();
250 }
251}
252
253void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
254 if (surface_width != static_cast<unsigned>(new_value)) {
255 surface_width = static_cast<unsigned>(new_value);
256
257 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
258 emit Update();
259 }
260}
261
262void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
263 if (surface_height != static_cast<unsigned>(new_value)) {
264 surface_height = static_cast<unsigned>(new_value);
265
266 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
267 emit Update();
268 }
269}
270
271void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
272 if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
273 surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
274
275 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
276 emit Update();
277 }
278}
279
280void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
281 if (surface_picker_x != new_value) {
282 surface_picker_x = new_value;
283 Pick(surface_picker_x, surface_picker_y);
284 }
285}
286
287void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
288 if (surface_picker_y != new_value) {
289 surface_picker_y = new_value;
290 Pick(surface_picker_x, surface_picker_y);
291 }
292}
293
294void GraphicsSurfaceWidget::Pick(int x, int y) {
295 surface_picker_x_control->setValue(x);
296 surface_picker_y_control->setValue(y);
297
298 if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
299 y >= static_cast<int>(surface_height)) {
300 surface_info_label->setText(tr("Pixel out of bounds"));
301 surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
302 return;
303 }
304
305 u8* buffer = Memory::GetPhysicalPointer(surface_address);
306 if (buffer == nullptr) {
307 surface_info_label->setText(tr("(unable to access pixel data)"));
308 surface_info_label->setAlignment(Qt::AlignCenter);
309 return;
310 }
311
312 surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
313 surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
314}
315
316void GraphicsSurfaceWidget::OnUpdate() {
317 auto& gpu = Core::System::GetInstance().GPU();
318
319 QPixmap pixmap;
320
321 Tegra::GPUVAddr surface_address = 0;
322
323 switch (surface_source) {
324 case Source::RenderTarget0:
325 case Source::RenderTarget1:
326 case Source::RenderTarget2:
327 case Source::RenderTarget3:
328 case Source::RenderTarget4:
329 case Source::RenderTarget5:
330 case Source::RenderTarget6:
331 case Source::RenderTarget7: {
332 // TODO: Store a reference to the registers in the debug context instead of accessing them
333 // directly...
334
335 auto& registers = gpu.Get3DEngine().regs;
336
337 surface_address = 0;
338 surface_width = 0;
339 surface_height = 0;
340 surface_format = Tegra::Texture::TextureFormat::DXT1;
341
342 break;
343 }
344
345 case Source::Custom: {
346 // Keep user-specified values
347 break;
348 }
349
350 default:
351 qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
352 break;
353 }
354
355 surface_address_control->SetValue(surface_address);
356 surface_width_control->setValue(surface_width);
357 surface_height_control->setValue(surface_height);
358 surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
359
360 if (surface_address == 0) {
361 surface_picture_label->hide();
362 surface_info_label->setText(tr("(invalid surface address)"));
363 surface_info_label->setAlignment(Qt::AlignCenter);
364 surface_picker_x_control->setEnabled(false);
365 surface_picker_y_control->setEnabled(false);
366 save_surface->setEnabled(false);
367 return;
368 }
369
370 // TODO: Implement a good way to visualize alpha components!
371
372 QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
373 VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
374
375 auto unswizzled_data =
376 Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height);
377
378 auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
379 surface_width, surface_height);
380
381 ASSERT(texture_data.size() ==
382 surface_width * surface_height *
383 Tegra::Texture::BytesPerPixel(Tegra::Texture::TextureFormat::A8R8G8B8));
384 surface_picture_label->show();
385
386 for (unsigned int y = 0; y < surface_height; ++y) {
387 for (unsigned int x = 0; x < surface_width; ++x) {
388 Math::Vec4<u8> color;
389 color[0] = texture_data[x + y * surface_width + 0];
390 color[1] = texture_data[x + y * surface_width + 1];
391 color[2] = texture_data[x + y * surface_width + 2];
392 color[3] = texture_data[x + y * surface_width + 3];
393 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
394 }
395 }
396
397 pixmap = QPixmap::fromImage(decoded_image);
398 surface_picture_label->setPixmap(pixmap);
399 surface_picture_label->resize(pixmap.size());
400
401 // Update the info with pixel data
402 surface_picker_x_control->setEnabled(true);
403 surface_picker_y_control->setEnabled(true);
404 Pick(surface_picker_x, surface_picker_y);
405
406 // Enable saving the converted pixmap to file
407 save_surface->setEnabled(true);
408}
409
410void GraphicsSurfaceWidget::SaveSurface() {
411 QString png_filter = tr("Portable Network Graphic (*.png)");
412 QString bin_filter = tr("Binary data (*.bin)");
413
414 QString selectedFilter;
415 QString filename = QFileDialog::getSaveFileName(
416 this, tr("Save Surface"),
417 QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
418 QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
419
420 if (filename.isEmpty()) {
421 // If the user canceled the dialog, don't save anything.
422 return;
423 }
424
425 if (selectedFilter == png_filter) {
426 const QPixmap* pixmap = surface_picture_label->pixmap();
427 ASSERT_MSG(pixmap != nullptr, "No pixmap set");
428
429 QFile file(filename);
430 file.open(QIODevice::WriteOnly);
431 if (pixmap)
432 pixmap->save(&file, "PNG");
433 } else if (selectedFilter == bin_filter) {
434 const u8* buffer = Memory::GetPhysicalPointer(surface_address);
435 ASSERT_MSG(buffer != nullptr, "Memory not accessible");
436
437 QFile file(filename);
438 file.open(QIODevice::WriteOnly);
439 int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
440 QByteArray data(reinterpret_cast<const char*>(buffer), size);
441 file.write(data);
442 } else {
443 UNREACHABLE_MSG("Unhandled filter selected");
444 }
445}
diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h
new file mode 100644
index 000000000..6a344bdfc
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.h
@@ -0,0 +1,97 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QLabel>
8#include <QPushButton>
9#include "video_core/memory_manager.h"
10#include "video_core/textures/texture.h"
11#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
12
13class QComboBox;
14class QSpinBox;
15class CSpinBox;
16
17class GraphicsSurfaceWidget;
18
19class SurfacePicture : public QLabel {
20 Q_OBJECT
21
22public:
23 explicit SurfacePicture(QWidget* parent = nullptr,
24 GraphicsSurfaceWidget* surface_widget = nullptr);
25 ~SurfacePicture();
26
27protected slots:
28 virtual void mouseMoveEvent(QMouseEvent* event);
29 virtual void mousePressEvent(QMouseEvent* event);
30
31private:
32 GraphicsSurfaceWidget* surface_widget;
33};
34
35class GraphicsSurfaceWidget : public BreakPointObserverDock {
36 Q_OBJECT
37
38 using Event = Tegra::DebugContext::Event;
39
40 enum class Source {
41 RenderTarget0 = 0,
42 RenderTarget1 = 1,
43 RenderTarget2 = 2,
44 RenderTarget3 = 3,
45 RenderTarget4 = 4,
46 RenderTarget5 = 5,
47 RenderTarget6 = 6,
48 RenderTarget7 = 7,
49 ZBuffer = 8,
50 Custom = 9,
51 };
52
53public:
54 explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
55 QWidget* parent = nullptr);
56 void Pick(int x, int y);
57
58public slots:
59 void OnSurfaceSourceChanged(int new_value);
60 void OnSurfaceAddressChanged(qint64 new_value);
61 void OnSurfaceWidthChanged(int new_value);
62 void OnSurfaceHeightChanged(int new_value);
63 void OnSurfaceFormatChanged(int new_value);
64 void OnSurfacePickerXChanged(int new_value);
65 void OnSurfacePickerYChanged(int new_value);
66 void OnUpdate();
67
68private slots:
69 void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
70 void OnResumed() override;
71
72 void SaveSurface();
73
74signals:
75 void Update();
76
77private:
78 QComboBox* surface_source_list;
79 CSpinBox* surface_address_control;
80 QSpinBox* surface_width_control;
81 QSpinBox* surface_height_control;
82 QComboBox* surface_format_control;
83
84 SurfacePicture* surface_picture_label;
85 QSpinBox* surface_picker_x_control;
86 QSpinBox* surface_picker_y_control;
87 QLabel* surface_info_label;
88 QPushButton* save_surface;
89
90 Source surface_source;
91 Tegra::GPUVAddr surface_address;
92 unsigned surface_width;
93 unsigned surface_height;
94 Tegra::Texture::TextureFormat surface_format;
95 int surface_picker_x = 0;
96 int surface_picker_y = 0;
97};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index eb22a8ccf..7b065ee7b 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -29,6 +29,7 @@
29#include "yuzu/bootmanager.h" 29#include "yuzu/bootmanager.h"
30#include "yuzu/configuration/config.h" 30#include "yuzu/configuration/config.h"
31#include "yuzu/configuration/configure_dialog.h" 31#include "yuzu/configuration/configure_dialog.h"
32#include "yuzu/debugger/graphics/graphics_breakpoints.h"
32#include "yuzu/debugger/profiler.h" 33#include "yuzu/debugger/profiler.h"
33#include "yuzu/debugger/registers.h" 34#include "yuzu/debugger/registers.h"
34#include "yuzu/debugger/wait_tree.h" 35#include "yuzu/debugger/wait_tree.h"
@@ -68,6 +69,9 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
68void GMainWindow::ShowCallouts() {} 69void GMainWindow::ShowCallouts() {}
69 70
70GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { 71GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
72
73 Tegra::g_debug_context = Tegra::DebugContext::Construct();
74
71 setAcceptDrops(true); 75 setAcceptDrops(true);
72 ui.setupUi(this); 76 ui.setupUi(this);
73 statusBar()->hide(); 77 statusBar()->hide();
@@ -160,6 +164,11 @@ void GMainWindow::InitializeDebugWidgets() {
160 connect(this, &GMainWindow::EmulationStopping, registersWidget, 164 connect(this, &GMainWindow::EmulationStopping, registersWidget,
161 &RegistersWidget::OnEmulationStopping); 165 &RegistersWidget::OnEmulationStopping);
162 166
167 graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Tegra::g_debug_context, this);
168 addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
169 graphicsBreakpointsWidget->hide();
170 debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
171
163 waitTreeWidget = new WaitTreeWidget(this); 172 waitTreeWidget = new WaitTreeWidget(this);
164 addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); 173 addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
165 waitTreeWidget->hide(); 174 waitTreeWidget->hide();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 4a0d912bb..86528f5b0 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -15,11 +15,7 @@ class Config;
15class EmuThread; 15class EmuThread;
16class GameList; 16class GameList;
17class GImageInfo; 17class GImageInfo;
18class GPUCommandStreamWidget;
19class GPUCommandListWidget;
20class GraphicsBreakPointsWidget; 18class GraphicsBreakPointsWidget;
21class GraphicsTracingWidget;
22class GraphicsVertexShaderWidget;
23class GRenderWindow; 19class GRenderWindow;
24class MicroProfileDialog; 20class MicroProfileDialog;
25class ProfilerWidget; 21class ProfilerWidget;
@@ -158,6 +154,7 @@ private:
158 ProfilerWidget* profilerWidget; 154 ProfilerWidget* profilerWidget;
159 MicroProfileDialog* microProfileDialog; 155 MicroProfileDialog* microProfileDialog;
160 RegistersWidget* registersWidget; 156 RegistersWidget* registersWidget;
157 GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
161 WaitTreeWidget* waitTreeWidget; 158 WaitTreeWidget* waitTreeWidget;
162 159
163 QAction* actions_recent_files[max_recent_files_item]; 160 QAction* actions_recent_files[max_recent_files_item];