summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Tony Wasserka2014-10-25 18:02:26 +0200
committerGravatar Tony Wasserka2014-12-09 16:37:34 +0100
commit2c71ec70527abd091d69f1fdd30aaf95d815214a (patch)
treed0e1f48b048f6d3ef801d0562bfcfe0fa11de6f1 /src
parentcitra-qt: Polish the pica tracing widget. (diff)
downloadyuzu-2c71ec70527abd091d69f1fdd30aaf95d815214a.tar.gz
yuzu-2c71ec70527abd091d69f1fdd30aaf95d815214a.tar.xz
yuzu-2c71ec70527abd091d69f1fdd30aaf95d815214a.zip
Pica/DebugUtils: Add breakpoint functionality.
Diffstat (limited to 'src')
-rw-r--r--src/citra_qt/bootmanager.cpp13
-rw-r--r--src/citra_qt/main.cpp4
-rw-r--r--src/video_core/command_processor.cpp13
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp43
-rw-r--r--src/video_core/debug_utils/debug_utils.h133
5 files changed, 204 insertions, 2 deletions
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 9a29f974b..b53206be6 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -14,6 +14,8 @@
14#include "core/core.h" 14#include "core/core.h"
15#include "core/settings.h" 15#include "core/settings.h"
16 16
17#include "video_core/debug_utils/debug_utils.h"
18
17#include "video_core/video_core.h" 19#include "video_core/video_core.h"
18 20
19#include "citra_qt/version.h" 21#include "citra_qt/version.h"
@@ -65,14 +67,21 @@ void EmuThread::Stop()
65 } 67 }
66 stop_run = true; 68 stop_run = true;
67 69
70 // Release emu threads from any breakpoints, so that this doesn't hang forever.
71 Pica::g_debug_context->ClearBreakpoints();
72
68 //core::g_state = core::SYS_DIE; 73 //core::g_state = core::SYS_DIE;
69 74
70 wait(500); 75 // TODO: Waiting here is just a bad workaround for retarded shutdown logic.
76 wait(1000);
71 if (isRunning()) 77 if (isRunning())
72 { 78 {
73 WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); 79 WARN_LOG(MASTER_LOG, "EmuThread still running, terminating...");
74 quit(); 80 quit();
75 wait(1000); 81
82 // TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam
83 // queued... This should be fixed.
84 wait(50000);
76 if (isRunning()) 85 if (isRunning())
77 { 86 {
78 WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here..."); 87 WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here...");
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 0701decef..869826e61 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -36,6 +36,8 @@ GMainWindow::GMainWindow()
36{ 36{
37 LogManager::Init(); 37 LogManager::Init();
38 38
39 Pica::g_debug_context = Pica::DebugContext::Construct();
40
39 Config config; 41 Config config;
40 42
41 if (!Settings::values.enable_log) 43 if (!Settings::values.enable_log)
@@ -133,6 +135,8 @@ GMainWindow::~GMainWindow()
133 // will get automatically deleted otherwise 135 // will get automatically deleted otherwise
134 if (render_window->parent() == nullptr) 136 if (render_window->parent() == nullptr)
135 delete render_window; 137 delete render_window;
138
139 Pica::g_debug_context.reset();
136} 140}
137 141
138void GMainWindow::BootGame(std::string filename) 142void GMainWindow::BootGame(std::string filename)
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 8a6ba2560..298b04c51 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -34,6 +34,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
34 u32 old_value = registers[id]; 34 u32 old_value = registers[id];
35 registers[id] = (old_value & ~mask) | (value & mask); 35 registers[id] = (old_value & ~mask) | (value & mask);
36 36
37 if (g_debug_context)
38 g_debug_context->OnEvent(DebugContext::Event::CommandLoaded, reinterpret_cast<void*>(&id));
39
37 DebugUtils::OnPicaRegWrite(id, registers[id]); 40 DebugUtils::OnPicaRegWrite(id, registers[id]);
38 41
39 switch(id) { 42 switch(id) {
@@ -43,6 +46,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
43 { 46 {
44 DebugUtils::DumpTevStageConfig(registers.GetTevStages()); 47 DebugUtils::DumpTevStageConfig(registers.GetTevStages());
45 48
49 if (g_debug_context)
50 g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr);
51
46 const auto& attribute_config = registers.vertex_attributes; 52 const auto& attribute_config = registers.vertex_attributes;
47 const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); 53 const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress());
48 54
@@ -132,6 +138,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
132 clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); 138 clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle);
133 } 139 }
134 geometry_dumper.Dump(); 140 geometry_dumper.Dump();
141
142 if (g_debug_context)
143 g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr);
144
135 break; 145 break;
136 } 146 }
137 147
@@ -229,6 +239,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
229 default: 239 default:
230 break; 240 break;
231 } 241 }
242
243 if (g_debug_context)
244 g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast<void*>(&id));
232} 245}
233 246
234static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { 247static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) {
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index 8a5f11424..11f87d988 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -3,6 +3,8 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <condition_variable>
7#include <list>
6#include <map> 8#include <map>
7#include <fstream> 9#include <fstream>
8#include <mutex> 10#include <mutex>
@@ -12,6 +14,7 @@
12#include <png.h> 14#include <png.h>
13#endif 15#endif
14 16
17#include "common/log.h"
15#include "common/file_util.h" 18#include "common/file_util.h"
16 19
17#include "video_core/pica.h" 20#include "video_core/pica.h"
@@ -20,6 +23,46 @@
20 23
21namespace Pica { 24namespace Pica {
22 25
26void DebugContext::OnEvent(Event event, void* data) {
27 if (!breakpoints[event].enabled)
28 return;
29
30 {
31 std::unique_lock<std::mutex> lock(breakpoint_mutex);
32
33 // TODO: Should stop the CPU thread here once we multithread emulation.
34
35 active_breakpoint = event;
36 at_breakpoint = true;
37
38 // Tell all observers that we hit a breakpoint
39 for (auto& breakpoint_observer : breakpoint_observers) {
40 breakpoint_observer->OnPicaBreakPointHit(event, data);
41 }
42
43 // Wait until another thread tells us to Resume()
44 resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; });
45 }
46}
47
48void DebugContext::Resume() {
49 {
50 std::unique_lock<std::mutex> lock(breakpoint_mutex);
51
52 // Tell all observers that we are about to resume
53 for (auto& breakpoint_observer : breakpoint_observers) {
54 breakpoint_observer->OnPicaResume();
55 }
56
57 // Resume the waiting thread (i.e. OnEvent())
58 at_breakpoint = false;
59 }
60
61 resume_from_breakpoint.notify_one();
62}
63
64std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
65
23namespace DebugUtils { 66namespace DebugUtils {
24 67
25void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { 68void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) {
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index b1558cfae..26b26e22f 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -5,13 +5,146 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <condition_variable>
9#include <list>
10#include <map>
8#include <memory> 11#include <memory>
12#include <mutex>
9#include <vector> 13#include <vector>
10 14
11#include "video_core/pica.h" 15#include "video_core/pica.h"
12 16
13namespace Pica { 17namespace Pica {
14 18
19class DebugContext {
20public:
21 enum class Event {
22 FirstEvent = 0,
23
24 CommandLoaded = FirstEvent,
25 CommandProcessed,
26 IncomingPrimitiveBatch,
27 FinishedPrimitiveBatch,
28
29 NumEvents
30 };
31
32 /**
33 * Inherit from this class to be notified of events registered to some debug context.
34 * Most importantly this is used for our debugger GUI.
35 *
36 * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods.
37 * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access
38 * @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread.
39 */
40 class BreakPointObserver {
41 public:
42 /// Constructs the object such that it observes events of the given DebugContext.
43 BreakPointObserver(std::shared_ptr<DebugContext> debug_context) : context_weak(debug_context) {
44 std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
45 debug_context->breakpoint_observers.push_back(this);
46 }
47
48 virtual ~BreakPointObserver() {
49 auto context = context_weak.lock();
50 if (context) {
51 std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
52 context->breakpoint_observers.remove(this);
53
54 // If we are the last observer to be destroyed, tell the debugger context that
55 // it is free to continue. In particular, this is required for a proper Citra
56 // shutdown, when the emulation thread is waiting at a breakpoint.
57 if (context->breakpoint_observers.empty())
58 context->Resume();
59 }
60 }
61
62 /**
63 * Action to perform when a breakpoint was reached.
64 * @param event Type of event which triggered the breakpoint
65 * @param data Optional data pointer (if unused, this is a nullptr)
66 * @note This function will perform nothing unless it is overridden in the child class.
67 */
68 virtual void OnPicaBreakPointHit(Event, void*) {
69 }
70
71 /**
72 * Action to perform when emulation is resumed from a breakpoint.
73 * @note This function will perform nothing unless it is overridden in the child class.
74 */
75 virtual void OnPicaResume() {
76 }
77
78 protected:
79 /**
80 * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
81 * context_weak.lock(), always compare the result against nullptr.
82 */
83 std::weak_ptr<DebugContext> context_weak;
84 };
85
86 /**
87 * Simple structure defining a breakpoint state
88 */
89 struct BreakPoint {
90 bool enabled = false;
91 };
92
93 /**
94 * Static constructor used to create a shared_ptr of a DebugContext.
95 */
96 static std::shared_ptr<DebugContext> Construct() {
97 return std::shared_ptr<DebugContext>(new DebugContext);
98 }
99
100 /**
101 * Used by the emulation core when a given event has happened. If a breakpoint has been set
102 * for this event, OnEvent calls the event handlers of the registered breakpoint observers.
103 * The current thread then is halted until Resume() is called from another thread (or until
104 * emulation is stopped).
105 * @param event Event which has happened
106 * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called.
107 */
108 void OnEvent(Event event, void* data);
109
110 /**
111 * Resume from the current breakpoint.
112 * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe.
113 */
114 void Resume();
115
116 /**
117 * Delete all set breakpoints and resume emulation.
118 */
119 void ClearBreakpoints() {
120 breakpoints.clear();
121 Resume();
122 }
123
124 // TODO: Evaluate if access to these members should be hidden behind a public interface.
125 std::map<Event, BreakPoint> breakpoints;
126 Event active_breakpoint;
127 bool at_breakpoint = false;
128
129private:
130 /**
131 * Private default constructor to make sure people always construct this through Construct()
132 * instead.
133 */
134 DebugContext() = default;
135
136 /// Mutex protecting current breakpoint state and the observer list.
137 std::mutex breakpoint_mutex;
138
139 /// Used by OnEvent to wait for resumption.
140 std::condition_variable resume_from_breakpoint;
141
142 /// List of registered observers
143 std::list<BreakPointObserver*> breakpoint_observers;
144};
145
146extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
147
15namespace DebugUtils { 148namespace DebugUtils {
16 149
17// Simple utility class for dumping geometry data to an OBJ file 150// Simple utility class for dumping geometry data to an OBJ file