From 27280f178bf2ea94f575849da036d11d23f30d94 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 21:57:00 +0100 Subject: [PATCH 01/14] Fix some headers to include their dependencies properly. --- src/common/common_funcs.h | 4 ++++ src/common/log.h | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 1139dc3b8..3f6df075a 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -4,6 +4,8 @@ #pragma once +#include "common_types.h" + #ifdef _WIN32 #define SLEEP(x) Sleep(x) #else @@ -73,6 +75,8 @@ inline u64 _rotr64(u64 x, unsigned int shift){ } #else // WIN32 +#include + // Function Cross-Compatibility #define strcasecmp _stricmp #define strncasecmp _strnicmp diff --git a/src/common/log.h b/src/common/log.h index 14ad98c08..ff0295cb0 100644 --- a/src/common/log.h +++ b/src/common/log.h @@ -4,6 +4,9 @@ #pragma once +#include "common/common_funcs.h" +#include "common/msg_handler.h" + #ifndef LOGGING #define LOGGING #endif From 8db65723d233486ac98ab9112a81f80b0313c7f7 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 21:55:32 +0100 Subject: [PATCH 02/14] Build fix for something which shouldn't have compiled successfully to begin with. --- src/video_core/pica.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 5fe15a218..10fa73355 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -109,7 +109,7 @@ struct Regs { u32 address; - u32 GetPhysicalAddress() { + u32 GetPhysicalAddress() const { return DecodeAddressRegister(address) - Memory::FCRAM_PADDR + Memory::HEAP_GSP_VADDR; } From bf6b23f4a0ea01af2c5e87b0fcabd1aea4a51fd6 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sat, 1 Nov 2014 15:36:59 +0100 Subject: [PATCH 03/14] citra-qt: Add a utility spinbox class called CSpinBox. This class has a few advantages over the regular QSpinBox: - QSpinBox stores its as signed 32 bit integers, which for instance is unsuitable for representing memory addresses. CSpinBox uses 64 bit integers instead. - QSpinBox does not provide an easy way to handle number input from bases different than 10. - QSpinBox is quite inflexible in general and almost any sort of customization requires reimplementing it anyway. --- src/citra_qt/CMakeLists.txt | 2 + src/citra_qt/util/spinbox.cpp | 303 ++++++++++++++++++++++++++++++++++ src/citra_qt/util/spinbox.hxx | 88 ++++++++++ 3 files changed, 393 insertions(+) create mode 100644 src/citra_qt/util/spinbox.cpp create mode 100644 src/citra_qt/util/spinbox.hxx diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 98a48a69a..7bf44197d 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS debugger/graphics_cmdlists.cpp debugger/ramview.cpp debugger/registers.cpp + util/spinbox.cpp bootmanager.cpp hotkeys.cpp main.cpp @@ -26,6 +27,7 @@ set(HEADERS debugger/graphics_cmdlists.hxx debugger/ramview.hxx debugger/registers.hxx + util/spinbox.hxx bootmanager.hxx hotkeys.hxx main.hxx diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp new file mode 100644 index 000000000..80dc67d7d --- /dev/null +++ b/src/citra_qt/util/spinbox.cpp @@ -0,0 +1,303 @@ +// Licensed under GPLv2+ +// Refer to the license.txt file included. + + +// Copyright 2014 Tony Wasserka +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the owner nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include "common/log.h" + +#include "spinbox.hxx" + +CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0) +{ + // TODO: Might be nice to not immediately call the slot. + // Think of an address that is being replaced by a different one, in which case a lot + // invalid intermediate addresses would be read from during editing. + connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished())); + + UpdateText(); +} + +void CSpinBox::SetValue(qint64 val) +{ + auto old_value = value; + value = std::max(std::min(val, max_value), min_value); + + if (old_value != value) { + UpdateText(); + emit ValueChanged(value); + } +} + +void CSpinBox::SetRange(qint64 min, qint64 max) +{ + min_value = min; + max_value = max; + + SetValue(value); + UpdateText(); +} + +void CSpinBox::stepBy(int steps) +{ + auto new_value = value; + // Scale number of steps by the currently selected digit + // TODO: Move this code elsewhere and enable it. + // TODO: Support for num_digits==0, too + // TODO: Support base!=16, too + // TODO: Make the cursor not jump back to the end of the line... + /*if (base == 16 && num_digits > 0) { + int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1; + digit = std::max(0, std::min(digit, num_digits - 1)); + steps <<= digit * 4; + }*/ + + // Increment "new_value" by "steps", and perform annoying overflow checks, too. + if (steps < 0 && new_value + steps > new_value) { + new_value = std::numeric_limits::min(); + } else if (steps > 0 && new_value + steps < new_value) { + new_value = std::numeric_limits::max(); + } else { + new_value += steps; + } + + SetValue(new_value); + UpdateText(); +} + +QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const +{ + StepEnabled ret = StepNone; + + if (value > min_value) + ret |= StepDownEnabled; + + if (value < max_value) + ret |= StepUpEnabled; + + return ret; +} + +void CSpinBox::SetBase(int base) +{ + this->base = base; + + UpdateText(); +} + +void CSpinBox::SetNumDigits(int num_digits) +{ + this->num_digits = num_digits; + + UpdateText(); +} + +void CSpinBox::SetPrefix(const QString& prefix) +{ + this->prefix = prefix; + + UpdateText(); +} + +void CSpinBox::SetSuffix(const QString& suffix) +{ + this->suffix = suffix; + + UpdateText(); +} + +static QString StringToInputMask(const QString& input) { + QString mask = input; + + // ... replace any special characters by their escaped counterparts ... + mask.replace("\\", "\\\\"); + mask.replace("A", "\\A"); + mask.replace("a", "\\a"); + mask.replace("N", "\\N"); + mask.replace("n", "\\n"); + mask.replace("X", "\\X"); + mask.replace("x", "\\x"); + mask.replace("9", "\\9"); + mask.replace("0", "\\0"); + mask.replace("D", "\\D"); + mask.replace("d", "\\d"); + mask.replace("#", "\\#"); + mask.replace("H", "\\H"); + mask.replace("h", "\\h"); + mask.replace("B", "\\B"); + mask.replace("b", "\\b"); + mask.replace(">", "\\>"); + mask.replace("<", "\\<"); + mask.replace("!", "\\!"); + + return mask; +} + +void CSpinBox::UpdateText() +{ + // If a fixed number of digits is used, we put the line edit in insertion mode by setting an + // input mask. + QString mask; + if (num_digits != 0) { + mask += StringToInputMask(prefix); + + // For base 10 and negative range, demand a single sign character + if (HasSign()) + mask += "X"; // identified as "-" or "+" in the validator + + // Uppercase digits greater than 9. + mask += ">"; + + // The greatest signed 64-bit number has 19 decimal digits. + // TODO: Could probably make this more generic with some logarithms. + // For reference, unsigned 64-bit can have up to 20 decimal digits. + int digits = (num_digits != 0) ? num_digits + : (base == 16) ? 16 + : (base == 10) ? 19 + : 0xFF; // fallback case... + + // Match num_digits digits + // Digits irrelevant to the chosen number base are filtered in the validator + mask += QString("H").repeated(std::max(num_digits, 1)); + + // Switch off case conversion + mask += "!"; + + mask += StringToInputMask(suffix); + } + lineEdit()->setInputMask(mask); + + // Set new text without changing the cursor position. This will cause the cursor to briefly + // appear at the end of the line and then to jump back to its original position. That's + // a bit ugly, but better than having setText() move the cursor permanently all the time. + int cursor_position = lineEdit()->cursorPosition(); + lineEdit()->setText(TextFromValue()); + lineEdit()->setCursorPosition(cursor_position); +} + +QString CSpinBox::TextFromValue() +{ + return prefix + + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") + + QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper() + + suffix; +} + +qint64 CSpinBox::ValueFromText() +{ + unsigned strpos = prefix.length(); + + QString num_string = text().mid(strpos, text().length() - strpos - suffix.length()); + return num_string.toLongLong(nullptr, base); +} + +bool CSpinBox::HasSign() const +{ + return base == 10 && min_value < 0; +} + +void CSpinBox::OnEditingFinished() +{ + // Only update for valid input + QString input = lineEdit()->text(); + int pos = 0; + if (QValidator::Acceptable == validate(input, pos)) + SetValue(ValueFromText()); +} + +QValidator::State CSpinBox::validate(QString& input, int& pos) const +{ + if (!prefix.isEmpty() && input.left(prefix.length()) != prefix) + return QValidator::Invalid; + + unsigned strpos = prefix.length(); + + // Empty "numbers" allowed as intermediate values + if (strpos >= input.length() - HasSign() - suffix.length()) + return QValidator::Intermediate; + + _dbg_assert_(GUI, base <= 10 || base == 16); + QString regexp; + + // Demand sign character for negative ranges + if (HasSign()) + regexp += "[+\\-]"; + + // Match digits corresponding to the chosen number base. + regexp += QString("[0-%1").arg(std::min(base, 9)); + if (base == 16) { + regexp += "a-fA-F"; + } + regexp += "]"; + + // Specify number of digits + if (num_digits > 0) { + regexp += QString("{%1}").arg(num_digits); + } else { + regexp += "+"; + } + + // Match string + QRegExp num_regexp(regexp); + int num_pos = strpos; + QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length()); + + if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0) + return QValidator::Invalid; + + sub_input = sub_input.left(num_regexp.matchedLength()); + bool ok; + qint64 val = sub_input.toLongLong(&ok, base); + + if (!ok) + return QValidator::Invalid; + + // Outside boundaries => don't accept + if (val < min_value || val > max_value) + return QValidator::Invalid; + + // Make sure we are actually at the end of this string... + strpos += num_regexp.matchedLength(); + + if (!suffix.isEmpty() && input.mid(strpos) != suffix) { + return QValidator::Invalid; + } else { + strpos += suffix.length(); + } + + if (strpos != input.length()) + return QValidator::Invalid; + + // At this point we can say for sure that the input is fine. Let's fix it up a bit though + input.replace(num_pos, sub_input.length(), sub_input.toUpper()); + + return QValidator::Acceptable; +} diff --git a/src/citra_qt/util/spinbox.hxx b/src/citra_qt/util/spinbox.hxx new file mode 100644 index 000000000..68f5b9894 --- /dev/null +++ b/src/citra_qt/util/spinbox.hxx @@ -0,0 +1,88 @@ +// Licensed under GPLv2+ +// Refer to the license.txt file included. + + +// Copyright 2014 Tony Wasserka +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the owner nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#pragma once + +#include +#include + +class QVariant; + +/** + * A custom spin box widget with enhanced functionality over Qt's QSpinBox + */ +class CSpinBox : public QAbstractSpinBox { + Q_OBJECT + +public: + CSpinBox(QWidget* parent = nullptr); + + void stepBy(int steps) override; + StepEnabled stepEnabled() const override; + + void SetValue(qint64 val); + + void SetRange(qint64 min, qint64 max); + + void SetBase(int base); + + void SetPrefix(const QString& prefix); + void SetSuffix(const QString& suffix); + + void SetNumDigits(int num_digits); + + QValidator::State validate(QString& input, int& pos) const override; + +signals: + void ValueChanged(qint64 val); + +private slots: + void OnEditingFinished(); + +private: + void UpdateText(); + + bool HasSign() const; + + QString TextFromValue(); + qint64 ValueFromText(); + + qint64 min_value, max_value; + + qint64 value; + + QString prefix, suffix; + + int base; + + int num_digits; +}; From 706f9c5574f74b018958477813495dd6e15bd00d Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 26 Oct 2014 11:40:12 +0100 Subject: [PATCH 04/14] citra-qt: Polish the pica tracing widget. Changed start/stop button to reflect current tracing status. Properly labeled column headers. --- src/citra_qt/debugger/graphics_cmdlists.cpp | 22 ++++++++++++++++++++- src/citra_qt/debugger/graphics_cmdlists.hxx | 5 +++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 71dd166cd..9e53a03d0 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -49,6 +49,24 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const return QVariant(); } +QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(role) { + case Qt::DisplayRole: + { + if (section == 0) { + return tr("Command Name"); + } else if (section == 1) { + return tr("Data"); + } + + break; + } + } + + return QVariant(); +} + void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) { beginResetModel(); @@ -70,7 +88,7 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pi list_widget->setFont(QFont("monospace")); list_widget->setRootIsDecorated(false); - QPushButton* toggle_tracing = new QPushButton(tr("Start Tracing")); + toggle_tracing = new QPushButton(tr("Start Tracing")); connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing())); connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)), @@ -88,8 +106,10 @@ void GPUCommandListWidget::OnToggleTracing() { if (!Pica::DebugUtils::IsPicaTracing()) { Pica::DebugUtils::StartPicaTracing(); + toggle_tracing->setText(tr("Stop Tracing")); } else { pica_trace = Pica::DebugUtils::FinishPicaTracing(); emit TracingFinished(*pica_trace); + toggle_tracing->setText(tr("Start Tracing")); } } diff --git a/src/citra_qt/debugger/graphics_cmdlists.hxx b/src/citra_qt/debugger/graphics_cmdlists.hxx index 1523e724f..31bd2546d 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.hxx +++ b/src/citra_qt/debugger/graphics_cmdlists.hxx @@ -10,6 +10,8 @@ #include "video_core/gpu_debugger.h" #include "video_core/debug_utils/debug_utils.h" +class QPushButton; + class GPUCommandListModel : public QAbstractListModel { Q_OBJECT @@ -20,6 +22,7 @@ public: int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; public slots: void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace); @@ -43,4 +46,6 @@ signals: private: std::unique_ptr pica_trace; + + QPushButton* toggle_tracing; }; From 2c71ec70527abd091d69f1fdd30aaf95d815214a Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sat, 25 Oct 2014 18:02:26 +0200 Subject: [PATCH 05/14] Pica/DebugUtils: Add breakpoint functionality. --- src/citra_qt/bootmanager.cpp | 13 +- src/citra_qt/main.cpp | 4 + src/video_core/command_processor.cpp | 13 ++ src/video_core/debug_utils/debug_utils.cpp | 43 +++++++ src/video_core/debug_utils/debug_utils.h | 133 +++++++++++++++++++++ 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 @@ #include "core/core.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" + #include "video_core/video_core.h" #include "citra_qt/version.h" @@ -65,14 +67,21 @@ void EmuThread::Stop() } stop_run = true; + // Release emu threads from any breakpoints, so that this doesn't hang forever. + Pica::g_debug_context->ClearBreakpoints(); + //core::g_state = core::SYS_DIE; - wait(500); + // TODO: Waiting here is just a bad workaround for retarded shutdown logic. + wait(1000); if (isRunning()) { WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); quit(); - wait(1000); + + // TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam + // queued... This should be fixed. + wait(50000); if (isRunning()) { 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() { LogManager::Init(); + Pica::g_debug_context = Pica::DebugContext::Construct(); + Config config; if (!Settings::values.enable_log) @@ -133,6 +135,8 @@ GMainWindow::~GMainWindow() // will get automatically deleted otherwise if (render_window->parent() == nullptr) delete render_window; + + Pica::g_debug_context.reset(); } void 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) { u32 old_value = registers[id]; registers[id] = (old_value & ~mask) | (value & mask); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::CommandLoaded, reinterpret_cast(&id)); + DebugUtils::OnPicaRegWrite(id, registers[id]); switch(id) { @@ -43,6 +46,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { { DebugUtils::DumpTevStageConfig(registers.GetTevStages()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); + const auto& attribute_config = registers.vertex_attributes; const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); @@ -132,6 +138,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); } geometry_dumper.Dump(); + + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); + break; } @@ -229,6 +239,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { default: break; } + + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast(&id)); } static 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 @@ // Refer to the license.txt file included. #include +#include +#include #include #include #include @@ -12,6 +14,7 @@ #include #endif +#include "common/log.h" #include "common/file_util.h" #include "video_core/pica.h" @@ -20,6 +23,46 @@ namespace Pica { +void DebugContext::OnEvent(Event event, void* data) { + if (!breakpoints[event].enabled) + return; + + { + std::unique_lock lock(breakpoint_mutex); + + // TODO: Should stop the CPU thread here once we multithread emulation. + + active_breakpoint = event; + at_breakpoint = true; + + // Tell all observers that we hit a breakpoint + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnPicaBreakPointHit(event, data); + } + + // Wait until another thread tells us to Resume() + resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; }); + } +} + +void DebugContext::Resume() { + { + std::unique_lock lock(breakpoint_mutex); + + // Tell all observers that we are about to resume + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnPicaResume(); + } + + // Resume the waiting thread (i.e. OnEvent()) + at_breakpoint = false; + } + + resume_from_breakpoint.notify_one(); +} + +std::shared_ptr g_debug_context; // TODO: Get rid of this global + namespace DebugUtils { void 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 @@ #pragma once #include +#include +#include +#include #include +#include #include #include "video_core/pica.h" namespace Pica { +class DebugContext { +public: + enum class Event { + FirstEvent = 0, + + CommandLoaded = FirstEvent, + CommandProcessed, + IncomingPrimitiveBatch, + FinishedPrimitiveBatch, + + NumEvents + }; + + /** + * Inherit from this class to be notified of events registered to some debug context. + * Most importantly this is used for our debugger GUI. + * + * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. + * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access + * @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread. + */ + class BreakPointObserver { + public: + /// Constructs the object such that it observes events of the given DebugContext. + BreakPointObserver(std::shared_ptr debug_context) : context_weak(debug_context) { + std::unique_lock lock(debug_context->breakpoint_mutex); + debug_context->breakpoint_observers.push_back(this); + } + + virtual ~BreakPointObserver() { + auto context = context_weak.lock(); + if (context) { + std::unique_lock lock(context->breakpoint_mutex); + context->breakpoint_observers.remove(this); + + // If we are the last observer to be destroyed, tell the debugger context that + // it is free to continue. In particular, this is required for a proper Citra + // shutdown, when the emulation thread is waiting at a breakpoint. + if (context->breakpoint_observers.empty()) + context->Resume(); + } + } + + /** + * Action to perform when a breakpoint was reached. + * @param event Type of event which triggered the breakpoint + * @param data Optional data pointer (if unused, this is a nullptr) + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnPicaBreakPointHit(Event, void*) { + } + + /** + * Action to perform when emulation is resumed from a breakpoint. + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnPicaResume() { + } + + protected: + /** + * Weak context pointer. This need not be valid, so when requesting a shared_ptr via + * context_weak.lock(), always compare the result against nullptr. + */ + std::weak_ptr context_weak; + }; + + /** + * Simple structure defining a breakpoint state + */ + struct BreakPoint { + bool enabled = false; + }; + + /** + * Static constructor used to create a shared_ptr of a DebugContext. + */ + static std::shared_ptr Construct() { + return std::shared_ptr(new DebugContext); + } + + /** + * Used by the emulation core when a given event has happened. If a breakpoint has been set + * for this event, OnEvent calls the event handlers of the registered breakpoint observers. + * The current thread then is halted until Resume() is called from another thread (or until + * emulation is stopped). + * @param event Event which has happened + * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. + */ + void OnEvent(Event event, void* data); + + /** + * Resume from the current breakpoint. + * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe. + */ + void Resume(); + + /** + * Delete all set breakpoints and resume emulation. + */ + void ClearBreakpoints() { + breakpoints.clear(); + Resume(); + } + + // TODO: Evaluate if access to these members should be hidden behind a public interface. + std::map breakpoints; + Event active_breakpoint; + bool at_breakpoint = false; + +private: + /** + * Private default constructor to make sure people always construct this through Construct() + * instead. + */ + DebugContext() = default; + + /// Mutex protecting current breakpoint state and the observer list. + std::mutex breakpoint_mutex; + + /// Used by OnEvent to wait for resumption. + std::condition_variable resume_from_breakpoint; + + /// List of registered observers + std::list breakpoint_observers; +}; + +extern std::shared_ptr g_debug_context; // TODO: Get rid of this global + namespace DebugUtils { // Simple utility class for dumping geometry data to an OBJ file From c63a495de6f4b4729cb3c24dc65ae019a39bbb0d Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sat, 25 Oct 2014 20:28:24 +0200 Subject: [PATCH 06/14] Add GUI widget for controlling pica breakpoints. --- src/citra_qt/CMakeLists.txt | 2 + .../debugger/graphics_breakpoints.cpp | 253 ++++++++++++++++++ .../debugger/graphics_breakpoints.hxx | 78 ++++++ src/citra_qt/main.cpp | 6 + 4 files changed, 339 insertions(+) create mode 100644 src/citra_qt/debugger/graphics_breakpoints.cpp create mode 100644 src/citra_qt/debugger/graphics_breakpoints.hxx diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 7bf44197d..999724102 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -8,6 +8,7 @@ set(SRCS debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics.cpp + debugger/graphics_breakpoints.cpp debugger/graphics_cmdlists.cpp debugger/ramview.cpp debugger/registers.cpp @@ -24,6 +25,7 @@ set(HEADERS debugger/callstack.hxx debugger/disassembler.hxx debugger/graphics.hxx + debugger/graphics_breakpoints.hxx debugger/graphics_cmdlists.hxx debugger/ramview.hxx debugger/registers.hxx diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp new file mode 100644 index 000000000..2f41d5f77 --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -0,0 +1,253 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include + +#include "graphics_breakpoints.hxx" + +BreakPointModel::BreakPointModel(std::shared_ptr debug_context, QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) +{ + +} + +int BreakPointModel::columnCount(const QModelIndex& parent) const +{ + return 2; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const +{ + return static_cast(Pica::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const +{ + const auto event = static_cast(index.row()); + + switch (role) { + case Qt::DisplayRole: + { + if (index.column() == 0) { + std::map map; + map.insert({Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded")}); + map.insert({Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed")}); + map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incomming primitive batch")}); + map.insert({Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}); + + _dbg_assert_(GPU, map.size() == static_cast(Pica::DebugContext::Event::NumEvents)); + + return map[event]; + } else if (index.column() == 1) { + return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled"); + } + + break; + } + + case Qt::BackgroundRole: + { + if (at_breakpoint && index.row() == static_cast(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: + { + auto context = context_weak.lock(); + return context && context->breakpoints[event].enabled; + } + + default: + break; + } + return QVariant(); +} + +QVariant BreakPointModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(role) { + case Qt::DisplayRole: + { + if (section == 0) { + return tr("Event"); + } else if (section == 1) { + return tr("Status"); + } + + break; + } + } + + return QVariant(); +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + const auto event = static_cast(index.row()); + + switch (role) { + case Role_IsEnabled: + { + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[event].enabled = value.toBool(); + QModelIndex changed_index = createIndex(index.row(), 1); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + + +void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event) +{ + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast(event), 0), + createIndex(static_cast(event), 1)); +} + +void BreakPointModel::OnResumed() +{ + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast(active_breakpoint), 0), + createIndex(static_cast(active_breakpoint), 1)); + active_breakpoint = context->active_breakpoint; +} + + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr debug_context, + QWidget* parent) + : QDockWidget(tr("Pica Breakpoints"), parent), + Pica::DebugContext::BreakPointObserver(debug_context) +{ + setObjectName("PicaBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setModel(breakpoint_model); + + toggle_breakpoint_button = new QPushButton(tr("Enable")); + toggle_breakpoint_button->setEnabled(false); + + qRegisterMetaType("Pica::DebugContext::Event"); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), + Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)), + Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&))); + + connect(breakpoint_list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(OnBreakpointSelectionChanged(QModelIndex))); + + connect(toggle_breakpoint_button, SIGNAL(clicked()), this, SLOT(OnToggleBreakpointEnabled())); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_layout->addWidget(toggle_breakpoint_button); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data) +{ + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnPicaResume() +{ + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() +{ + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() +{ + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnBreakpointSelectionChanged(const QModelIndex& index) +{ + if (!index.isValid()) { + toggle_breakpoint_button->setEnabled(false); + return; + } + + toggle_breakpoint_button->setEnabled(true); + UpdateToggleBreakpointButton(index); +} + +void GraphicsBreakPointsWidget::OnToggleBreakpointEnabled() +{ + QModelIndex index = breakpoint_list->selectionModel()->currentIndex(); + bool new_state = !(breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()); + + breakpoint_model->setData(index, new_state, + BreakPointModel::Role_IsEnabled); + UpdateToggleBreakpointButton(index); +} + +void GraphicsBreakPointsWidget::UpdateToggleBreakpointButton(const QModelIndex& index) +{ + if (true == breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()) { + toggle_breakpoint_button->setText(tr("Disable")); + } else { + toggle_breakpoint_button->setText(tr("Enable")); + } +} diff --git a/src/citra_qt/debugger/graphics_breakpoints.hxx b/src/citra_qt/debugger/graphics_breakpoints.hxx new file mode 100644 index 000000000..edc41283e --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints.hxx @@ -0,0 +1,78 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include + +#include +#include + +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event); + void OnResumed(); + +private: + bool at_breakpoint; + Pica::DebugContext::Event active_breakpoint; + std::weak_ptr context_weak; +}; + +class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + +public: + GraphicsBreakPointsWidget(std::shared_ptr debug_context, + QWidget* parent = nullptr); + + void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnPicaResume() override; + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data); + void OnResumeRequested(); + void OnResumed(); + void OnBreakpointSelectionChanged(const QModelIndex&); + void OnToggleBreakpointEnabled(); + +signals: + void Resumed(); + void BreakPointHit(Pica::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + void UpdateToggleBreakpointButton(const QModelIndex& index); + + QLabel* status_text; + QPushButton* resume_button; + QPushButton* toggle_breakpoint_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 869826e61..84afe59d8 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -20,6 +20,7 @@ #include "debugger/callstack.hxx" #include "debugger/ramview.hxx" #include "debugger/graphics.hxx" +#include "debugger/graphics_breakpoints.hxx" #include "debugger/graphics_cmdlists.hxx" #include "core/settings.h" @@ -69,12 +70,17 @@ GMainWindow::GMainWindow() addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); graphicsCommandsWidget->hide(); + auto graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); debug_menu->addAction(callstackWidget->toggleViewAction()); debug_menu->addAction(graphicsWidget->toggleViewAction()); debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); // Set default UI state // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half From fd194d95b0f1522b970a2b77f9ea490fb8c3ef08 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 24 Aug 2014 14:39:52 +0200 Subject: [PATCH 07/14] citra-qt: Add texture viewer to Pica command list. The texture viewer is enabled when selecting a write command to one of the texture config registers. --- src/citra_qt/debugger/graphics_cmdlists.cpp | 64 ++++++++++++++++++++- src/citra_qt/debugger/graphics_cmdlists.hxx | 8 +++ src/video_core/debug_utils/debug_utils.cpp | 57 +++++++++++------- src/video_core/debug_utils/debug_utils.h | 9 +++ 4 files changed, 116 insertions(+), 22 deletions(-) diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 9e53a03d0..dcd0ced33 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 // Refer to the license.txt file included. +#include #include #include #include @@ -9,6 +10,33 @@ #include "graphics_cmdlists.hxx" +#include "video_core/pica.h" +#include "video_core/math.h" + +#include "video_core/debug_utils/debug_utils.h" + +class TextureInfoWidget : public QWidget { +public: + TextureInfoWidget(u8* src, const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) { + QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); + for (int y = 0; y < info.height; ++y) { + for (int x = 0; x < info.width; ++x) { + Math::Vec4 color = Pica::DebugUtils::LookupTexture(src, x, y, info); + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + QLabel* image_widget = new QLabel; + QPixmap image_pixmap = QPixmap::fromImage(decoded_image); + image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); + image_widget->setPixmap(image_pixmap); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget(image_widget); + setLayout(layout); + } +}; + GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { @@ -44,6 +72,8 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const } return QVariant(content); + } else if (role == CommandIdRole) { + return QVariant::fromValue(cmd.cmd_id.Value()); } return QVariant(); @@ -76,27 +106,59 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& endResetModel(); } +void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) +{ + QWidget* new_info_widget; + +#define COMMAND_IN_RANGE(cmd_id, reg_name) (cmd_id >= PICA_REG_INDEX(reg_name) && cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::registers.reg_name)) / 4) + const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); + if (COMMAND_IN_RANGE(command_id, texture0)) { + u8* src = Memory::GetPointer(Pica::registers.texture0.GetPhysicalAddress()); + Pica::DebugUtils::TextureInfo info; + info.width = Pica::registers.texture0.width; + info.height = Pica::registers.texture0.height; + info.stride = 3 * Pica::registers.texture0.width; + info.format = Pica::registers.texture0_format; + new_info_widget = new TextureInfoWidget(src, info); + } else { + new_info_widget = new QWidget; + } +#undef COMMAND_IN_RANGE + + widget()->layout()->removeWidget(command_info_widget); + delete command_info_widget; + widget()->layout()->addWidget(new_info_widget); + command_info_widget = new_info_widget; +} GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) { + setObjectName("Pica Command List"); GPUCommandListModel* model = new GPUCommandListModel(this); QWidget* main_widget = new QWidget; - QTreeView* list_widget = new QTreeView; + list_widget = new QTreeView; list_widget->setModel(model); list_widget->setFont(QFont("monospace")); list_widget->setRootIsDecorated(false); + connect(list_widget->selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)), + this, SLOT(SetCommandInfo(const QModelIndex&))); + + toggle_tracing = new QPushButton(tr("Start Tracing")); connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing())); connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)), model, SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&))); + command_info_widget = new QWidget; + QVBoxLayout* main_layout = new QVBoxLayout; main_layout->addWidget(list_widget); main_layout->addWidget(toggle_tracing); + main_layout->addWidget(command_info_widget); main_widget->setLayout(main_layout); setWidget(main_widget); diff --git a/src/citra_qt/debugger/graphics_cmdlists.hxx b/src/citra_qt/debugger/graphics_cmdlists.hxx index 31bd2546d..37fe19053 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.hxx +++ b/src/citra_qt/debugger/graphics_cmdlists.hxx @@ -11,12 +11,17 @@ #include "video_core/debug_utils/debug_utils.h" class QPushButton; +class QTreeView; class GPUCommandListModel : public QAbstractListModel { Q_OBJECT public: + enum { + CommandIdRole = Qt::UserRole, + }; + GPUCommandListModel(QObject* parent); int columnCount(const QModelIndex& parent = QModelIndex()) const override; @@ -40,6 +45,7 @@ public: public slots: void OnToggleTracing(); + void SetCommandInfo(const QModelIndex&); signals: void TracingFinished(const Pica::DebugUtils::PicaTrace&); @@ -47,5 +53,7 @@ signals: private: std::unique_ptr pica_trace; + QTreeView* list_widget; + QWidget* command_info_widget; QPushButton* toggle_tracing; }; diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 11f87d988..59909c827 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 // Refer to the license.txt file included. +#include + #include #include #include @@ -17,6 +19,7 @@ #include "common/log.h" #include "common/file_util.h" +#include "video_core/math.h" #include "video_core/pica.h" #include "debug_utils.h" @@ -355,6 +358,30 @@ std::unique_ptr FinishPicaTracing() return std::move(ret); } +const Math::Vec4 LookupTexture(const u8* source, int x, int y, const TextureInfo& info) { + assert(info.format == Pica::Regs::TextureFormat::RGB8); + + // Cf. rasterizer code for an explanation of this algorithm. + int texel_index_within_tile = 0; + for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { + int sub_tile_width = 1 << block_size_index; + int sub_tile_height = 1 << block_size_index; + + int sub_tile_index = (x & sub_tile_width) << block_size_index; + sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); + texel_index_within_tile += sub_tile_index; + } + + const int block_width = 8; + const int block_height = 8; + + int coarse_x = (x / block_width) * block_width; + int coarse_y = (y / block_height) * block_height; + + const u8* source_ptr = source + coarse_x * block_height * 3 + coarse_y * info.stride + texel_index_within_tile * 3; + return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; +} + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { // NOTE: Permanently enabling this just trashes hard disks for no reason. // Hence, this is currently disabled. @@ -420,27 +447,15 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { buf = new u8[row_stride * texture_config.height]; for (unsigned y = 0; y < texture_config.height; ++y) { for (unsigned x = 0; x < texture_config.width; ++x) { - // Cf. rasterizer code for an explanation of this algorithm. - int texel_index_within_tile = 0; - for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { - int sub_tile_width = 1 << block_size_index; - int sub_tile_height = 1 << block_size_index; - - int sub_tile_index = (x & sub_tile_width) << block_size_index; - sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); - texel_index_within_tile += sub_tile_index; - } - - const int block_width = 8; - const int block_height = 8; - - int coarse_x = (x / block_width) * block_width; - int coarse_y = (y / block_height) * block_height; - - u8* source_ptr = (u8*)data + coarse_x * block_height * 3 + coarse_y * row_stride + texel_index_within_tile * 3; - buf[3 * x + y * row_stride ] = source_ptr[2]; - buf[3 * x + y * row_stride + 1] = source_ptr[1]; - buf[3 * x + y * row_stride + 2] = source_ptr[0]; + TextureInfo info; + info.width = texture_config.width; + info.height = texture_config.height; + info.stride = row_stride; + info.format = registers.texture0_format; + Math::Vec4 texture_color = LookupTexture(data, x, y, info); + buf[3 * x + y * row_stride ] = texture_color.r(); + buf[3 * x + y * row_stride + 1] = texture_color.g(); + buf[3 * x + y * row_stride + 2] = texture_color.b(); } } diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 26b26e22f..bad4c919a 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -12,6 +12,7 @@ #include #include +#include "video_core/math.h" #include "video_core/pica.h" namespace Pica { @@ -190,6 +191,14 @@ bool IsPicaTracing(); void OnPicaRegWrite(u32 id, u32 value); std::unique_ptr FinishPicaTracing(); +struct TextureInfo { + int width; + int height; + int stride; + Pica::Regs::TextureFormat format; +}; + +const Math::Vec4 LookupTexture(const u8* source, int x, int y, const TextureInfo& info); void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); void DumpTevStageConfig(const std::array& stages); From 2793619dcef9fb2f97db5f0258ca950e18fe7f13 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 24 Aug 2014 17:23:02 +0200 Subject: [PATCH 08/14] citra_qt: Add enhanced texture debugging widgets. Double-clicking a texture parameter command in the pica command lists will spawn these as a new tab in the pica command list dock area. --- src/citra_qt/debugger/graphics_cmdlists.cpp | 173 ++++++++++++++++++-- src/citra_qt/debugger/graphics_cmdlists.hxx | 24 +++ src/video_core/debug_utils/debug_utils.cpp | 12 ++ src/video_core/debug_utils/debug_utils.h | 4 + src/video_core/pica.h | 15 +- 5 files changed, 209 insertions(+), 19 deletions(-) diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index dcd0ced33..4f58e9a90 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -4,30 +4,39 @@ #include #include +#include #include #include #include - -#include "graphics_cmdlists.hxx" +#include +#include #include "video_core/pica.h" #include "video_core/math.h" #include "video_core/debug_utils/debug_utils.h" +#include "graphics_cmdlists.hxx" + +#include "util/spinbox.hxx" + +QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) { + QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); + for (int y = 0; y < info.height; ++y) { + for (int x = 0; x < info.width; ++x) { + Math::Vec4 color = Pica::DebugUtils::LookupTexture(src, x, y, info); + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + return decoded_image; +} + class TextureInfoWidget : public QWidget { public: TextureInfoWidget(u8* src, const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) { - QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); - for (int y = 0; y < info.height; ++y) { - for (int x = 0; x < info.width; ++x) { - Math::Vec4 color = Pica::DebugUtils::LookupTexture(src, x, y, info); - decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); - } - } - QLabel* image_widget = new QLabel; - QPixmap image_pixmap = QPixmap::fromImage(decoded_image); + QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info)); image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); image_widget->setPixmap(image_pixmap); @@ -37,6 +46,120 @@ public: } }; +TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent) + : QDockWidget(tr("Texture 0x%1").arg(info.address, 8, 16, QLatin1Char('0'))), + info(info) { + + QWidget* main_widget = new QWidget; + + QLabel* image_widget = new QLabel; + + connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&))); + + CSpinBox* phys_address_spinbox = new CSpinBox; + phys_address_spinbox->SetBase(16); + phys_address_spinbox->SetRange(0, 0xFFFFFFFF); + phys_address_spinbox->SetPrefix("0x"); + phys_address_spinbox->SetValue(info.address); + connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64))); + + QComboBox* format_choice = new QComboBox; + format_choice->addItem(tr("RGBA8")); + format_choice->addItem(tr("RGB8")); + format_choice->addItem(tr("RGBA5551")); + format_choice->addItem(tr("RGB565")); + format_choice->addItem(tr("RGBA4")); + format_choice->setCurrentIndex(static_cast(info.format)); + connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int))); + + QSpinBox* width_spinbox = new QSpinBox; + width_spinbox->setMaximum(65535); + width_spinbox->setValue(info.width); + connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int))); + + QSpinBox* height_spinbox = new QSpinBox; + height_spinbox->setMaximum(65535); + height_spinbox->setValue(info.height); + connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int))); + + QSpinBox* stride_spinbox = new QSpinBox; + stride_spinbox->setMaximum(65535 * 4); + stride_spinbox->setValue(info.stride); + connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int))); + + QVBoxLayout* main_layout = new QVBoxLayout; + main_layout->addWidget(image_widget); + + { + QHBoxLayout* sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source Address:"))); + sub_layout->addWidget(phys_address_spinbox); + main_layout->addLayout(sub_layout); + } + + { + QHBoxLayout* sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format"))); + sub_layout->addWidget(format_choice); + main_layout->addLayout(sub_layout); + } + + { + QHBoxLayout* sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(width_spinbox); + sub_layout->addStretch(); + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(height_spinbox); + sub_layout->addStretch(); + sub_layout->addWidget(new QLabel(tr("Stride:"))); + sub_layout->addWidget(stride_spinbox); + main_layout->addLayout(sub_layout); + } + + main_widget->setLayout(main_layout); + + emit UpdatePixmap(ReloadPixmap()); + + setWidget(main_widget); +} + +void TextureInfoDockWidget::OnAddressChanged(qint64 value) +{ + info.address = value; + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnFormatChanged(int value) +{ + info.format = static_cast(value); + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnWidthChanged(int value) +{ + info.width = value; + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnHeightChanged(int value) +{ + info.height = value; + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnStrideChanged(int value) +{ + info.stride = value; + emit UpdatePixmap(ReloadPixmap()); +} + +QPixmap TextureInfoDockWidget::ReloadPixmap() const +{ + u8* src = Memory::GetPointer(info.address); + return QPixmap::fromImage(LoadTexture(src, info)); +} + GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { @@ -106,30 +229,42 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& endResetModel(); } +#define COMMAND_IN_RANGE(cmd_id, reg_name) \ + (cmd_id >= PICA_REG_INDEX(reg_name) && \ + cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::registers.reg_name)) / 4) + +void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) +{ + + const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); + if (COMMAND_IN_RANGE(command_id, texture0)) { + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, + Pica::registers.texture0_format); + QMainWindow* main_window = (QMainWindow*)parent(); + main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window)); + } +} + void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { QWidget* new_info_widget; -#define COMMAND_IN_RANGE(cmd_id, reg_name) (cmd_id >= PICA_REG_INDEX(reg_name) && cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::registers.reg_name)) / 4) const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); if (COMMAND_IN_RANGE(command_id, texture0)) { u8* src = Memory::GetPointer(Pica::registers.texture0.GetPhysicalAddress()); - Pica::DebugUtils::TextureInfo info; - info.width = Pica::registers.texture0.width; - info.height = Pica::registers.texture0.height; - info.stride = 3 * Pica::registers.texture0.width; - info.format = Pica::registers.texture0_format; + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, + Pica::registers.texture0_format); new_info_widget = new TextureInfoWidget(src, info); } else { new_info_widget = new QWidget; } -#undef COMMAND_IN_RANGE widget()->layout()->removeWidget(command_info_widget); delete command_info_widget; widget()->layout()->addWidget(new_info_widget); command_info_widget = new_info_widget; } +#undef COMMAND_IN_RANGE GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) { @@ -145,6 +280,8 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pi connect(list_widget->selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)), this, SLOT(SetCommandInfo(const QModelIndex&))); + connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)), + this, SLOT(OnCommandDoubleClicked(const QModelIndex&))); toggle_tracing = new QPushButton(tr("Start Tracing")); diff --git a/src/citra_qt/debugger/graphics_cmdlists.hxx b/src/citra_qt/debugger/graphics_cmdlists.hxx index 37fe19053..a459bba64 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.hxx +++ b/src/citra_qt/debugger/graphics_cmdlists.hxx @@ -45,6 +45,8 @@ public: public slots: void OnToggleTracing(); + void OnCommandDoubleClicked(const QModelIndex&); + void SetCommandInfo(const QModelIndex&); signals: @@ -57,3 +59,25 @@ private: QWidget* command_info_widget; QPushButton* toggle_tracing; }; + +class TextureInfoDockWidget : public QDockWidget { + Q_OBJECT + +public: + TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr); + +signals: + void UpdatePixmap(const QPixmap& pixmap); + +private slots: + void OnAddressChanged(qint64 value); + void OnFormatChanged(int value); + void OnWidthChanged(int value); + void OnHeightChanged(int value); + void OnStrideChanged(int value); + +private: + QPixmap ReloadPixmap() const; + + Pica::DebugUtils::TextureInfo info; +}; diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 59909c827..31ce09faf 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -382,6 +382,18 @@ const Math::Vec4 LookupTexture(const u8* source, int x, int y, const Texture return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; } +TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, + const Regs::TextureFormat& format) +{ + TextureInfo info; + info.address = config.GetPhysicalAddress(); + info.width = config.width; + info.height = config.height; + info.format = format; + info.stride = Pica::Regs::BytesPerPixel(info.format) * info.width; + return info; +} + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { // NOTE: Permanently enabling this just trashes hard disks for no reason. // Hence, this is currently disabled. diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index bad4c919a..51f14f12f 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -192,10 +192,14 @@ void OnPicaRegWrite(u32 id, u32 value); std::unique_ptr FinishPicaTracing(); struct TextureInfo { + unsigned int address; int width; int height; int stride; Pica::Regs::TextureFormat format; + + static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, + const Pica::Regs::TextureFormat& format); }; const Math::Vec4 LookupTexture(const u8* source, int x, int y, const TextureInfo& info); diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 10fa73355..c1f35a011 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -130,7 +130,20 @@ struct Regs { // Seems like they are luminance formats and compressed textures. }; - BitField<0, 1, u32> texturing_enable; + static unsigned BytesPerPixel(TextureFormat format) { + if (format == TextureFormat::RGBA8) + return 4; + else if (format == TextureFormat::RGB8) + return 3; + else if (format == TextureFormat::RGBA5551 || + format == TextureFormat::RGB565 || + format == TextureFormat::RGBA4) + return 2; + else // placeholder + return 1; + } + + BitField< 0, 1, u32> texturing_enable; TextureConfig texture0; INSERT_PADDING_WORDS(0x8); BitField<0, 4, TextureFormat> texture0_format; From 55ce9aca7149159d6ec444047355e47d70c13c3f Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 26 Oct 2014 16:38:40 +0100 Subject: [PATCH 09/14] citra-qt: Add pica framebuffer widget. --- src/citra_qt/CMakeLists.txt | 2 + .../debugger/graphics_framebuffer.cpp | 282 ++++++++++++++++++ .../debugger/graphics_framebuffer.hxx | 92 ++++++ src/citra_qt/main.cpp | 6 + 4 files changed, 382 insertions(+) create mode 100644 src/citra_qt/debugger/graphics_framebuffer.cpp create mode 100644 src/citra_qt/debugger/graphics_framebuffer.hxx diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 999724102..cd4a04eac 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -10,6 +10,7 @@ set(SRCS debugger/graphics.cpp debugger/graphics_breakpoints.cpp debugger/graphics_cmdlists.cpp + debugger/graphics_framebuffer.cpp debugger/ramview.cpp debugger/registers.cpp util/spinbox.cpp @@ -27,6 +28,7 @@ set(HEADERS debugger/graphics.hxx debugger/graphics_breakpoints.hxx debugger/graphics_cmdlists.hxx + debugger/graphics_framebuffer.hxx debugger/ramview.hxx debugger/registers.hxx util/spinbox.hxx diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp new file mode 100644 index 000000000..b91db5433 --- /dev/null +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -0,0 +1,282 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include + +#include "video_core/pica.h" + +#include "graphics_framebuffer.hxx" + +#include "util/spinbox.hxx" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) +{ + qRegisterMetaType("Pica::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), + Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnPicaResume() +{ + emit Resumed(); +} + + +GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), + framebuffer_source(Source::PicaTarget) +{ + setObjectName("PicaFramebuffer"); + + framebuffer_source_list = new QComboBox; + framebuffer_source_list->addItem(tr("Active Render Target")); + framebuffer_source_list->addItem(tr("Custom")); + framebuffer_source_list->setCurrentIndex(static_cast(framebuffer_source)); + + framebuffer_address_control = new CSpinBox; + framebuffer_address_control->SetBase(16); + framebuffer_address_control->SetRange(0, 0xFFFFFFFF); + framebuffer_address_control->SetPrefix("0x"); + + framebuffer_width_control = new QSpinBox; + framebuffer_width_control->setMinimum(1); + framebuffer_width_control->setMaximum(std::numeric_limits::max()); // TODO: Find actual maximum + + framebuffer_height_control = new QSpinBox; + framebuffer_height_control->setMinimum(1); + framebuffer_height_control->setMaximum(std::numeric_limits::max()); // TODO: Find actual maximum + + framebuffer_format_control = new QComboBox; + framebuffer_format_control->addItem(tr("RGBA8")); + framebuffer_format_control->addItem(tr("RGB8")); + framebuffer_format_control->addItem(tr("RGBA5551")); + framebuffer_format_control->addItem(tr("RGB565")); + framebuffer_format_control->addItem(tr("RGBA4")); + + // TODO: This QLabel should shrink the image to the available space rather than just expanding... + framebuffer_picture_label = new QLabel; + + auto enlarge_button = new QPushButton(tr("Enlarge")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int))); + connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64))); + connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int))); + connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int))); + connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int))); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(framebuffer_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Virtual Address:"))); + sub_layout->addWidget(framebuffer_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(framebuffer_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(framebuffer_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(framebuffer_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(framebuffer_picture_label); + main_layout->addWidget(enlarge_button); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + emit Update(); + widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint +} + +void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsFramebufferWidget::OnResumed() +{ + widget()->setEnabled(false); +} + +void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value) +{ + framebuffer_source = static_cast(new_value); + emit Update(); +} + +void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) +{ + if (framebuffer_address != new_value) { + framebuffer_address = static_cast(new_value); + + framebuffer_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) +{ + if (framebuffer_width != new_value) { + framebuffer_width = new_value; + + framebuffer_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) +{ + if (framebuffer_height != new_value) { + framebuffer_height = new_value; + + framebuffer_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value) +{ + if (framebuffer_format != static_cast(new_value)) { + framebuffer_format = static_cast(new_value); + + framebuffer_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnUpdate() +{ + QPixmap pixmap; + + switch (framebuffer_source) { + case Source::PicaTarget: + { + // TODO: Store a reference to the registers in the debug context instead of accessing them directly... + + auto framebuffer = Pica::registers.framebuffer; + using Framebuffer = decltype(framebuffer); + + framebuffer_address = framebuffer.GetColorBufferAddress(); + framebuffer_width = framebuffer.GetWidth(); + framebuffer_height = framebuffer.GetHeight(); + framebuffer_format = static_cast(framebuffer.color_format); + + break; + } + + case Source::Custom: + { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown framebuffer source " << static_cast(framebuffer_source); + break; + } + + switch (framebuffer_format) { + case Format::RGBA8: + { + // TODO: Implement a good way to visualize the alpha component + + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); + for (int y = 0; y < framebuffer_height; ++y) { + for (int x = 0; x < framebuffer_width; ++x) { + u32 value = *(color_buffer + x + y * framebuffer_width); + + decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); + } + } + pixmap = QPixmap::fromImage(decoded_image); + break; + } + + case Format::RGB8: + { + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u8* color_buffer = Memory::GetPointer(framebuffer_address); + for (int y = 0; y < framebuffer_height; ++y) { + for (int x = 0; x < framebuffer_width; ++x) { + u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; + + decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); + } + } + pixmap = QPixmap::fromImage(decoded_image); + break; + } + + case Format::RGBA5551: + { + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); + for (int y = 0; y < framebuffer_height; ++y) { + for (int x = 0; x < framebuffer_width; ++x) { + u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); + u8 r = (value >> 11) & 0x1F; + u8 g = (value >> 6) & 0x1F; + u8 b = (value >> 1) & 0x1F; + u8 a = value & 1; + + decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/)); + } + } + pixmap = QPixmap::fromImage(decoded_image); + break; + } + + default: + qDebug() << "Unknown fb color format " << static_cast(framebuffer_format); + break; + } + + framebuffer_address_control->SetValue(framebuffer_address); + framebuffer_width_control->setValue(framebuffer_width); + framebuffer_height_control->setValue(framebuffer_height); + framebuffer_format_control->setCurrentIndex(static_cast(framebuffer_format)); + framebuffer_picture_label->setPixmap(pixmap); +} diff --git a/src/citra_qt/debugger/graphics_framebuffer.hxx b/src/citra_qt/debugger/graphics_framebuffer.hxx new file mode 100644 index 000000000..bb73b2f72 --- /dev/null +++ b/src/citra_qt/debugger/graphics_framebuffer.hxx @@ -0,0 +1,92 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "video_core/debug_utils/debug_utils.h" + +class QComboBox; +class QLabel; +class QSpinBox; + +class CSpinBox; + +// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots. +// This is because the Pica breakpoint callbacks will called on a non-GUI thread, while +// the widget usually wants to perform reactions in the GUI thread. +class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnPicaResume() override; + +private slots: + virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Pica::DebugContext::Event event, void* data); +}; + +class GraphicsFramebufferWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + + enum class Source { + PicaTarget = 0, + Custom = 1, + + // TODO: Add GPU framebuffer sources! + }; + + enum class Format { + RGBA8 = 0, + RGB8 = 1, + RGBA5551 = 2, + RGB565 = 3, + RGBA4 = 4, + }; + +public: + GraphicsFramebufferWidget(std::shared_ptr debug_context, QWidget* parent = nullptr); + +public slots: + void OnFramebufferSourceChanged(int new_value); + void OnFramebufferAddressChanged(qint64 new_value); + void OnFramebufferWidthChanged(int new_value); + void OnFramebufferHeightChanged(int new_value); + void OnFramebufferFormatChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnResumed() override; + +signals: + void Update(); + +private: + + QComboBox* framebuffer_source_list; + CSpinBox* framebuffer_address_control; + QSpinBox* framebuffer_width_control; + QSpinBox* framebuffer_height_control; + QComboBox* framebuffer_format_control; + + QLabel* framebuffer_picture_label; + + Source framebuffer_source; + unsigned framebuffer_address; + unsigned framebuffer_width; + unsigned framebuffer_height; + Format framebuffer_format; +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 84afe59d8..b4e3ad964 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -22,6 +22,7 @@ #include "debugger/graphics.hxx" #include "debugger/graphics_breakpoints.hxx" #include "debugger/graphics_cmdlists.hxx" +#include "debugger/graphics_framebuffer.hxx" #include "core/settings.h" #include "core/system.h" @@ -74,6 +75,10 @@ GMainWindow::GMainWindow() addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); + auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget); + graphicsFramebufferWidget->hide(); + QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); @@ -81,6 +86,7 @@ GMainWindow::GMainWindow() debug_menu->addAction(graphicsWidget->toggleViewAction()); debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); // Set default UI state // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half From 0cd27a511ecd170484b672263c09192b579e31ac Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 19:41:03 +0100 Subject: [PATCH 10/14] Some code cleanup. --- src/citra_qt/CMakeLists.txt | 1 + .../debugger/graphics_breakpoints.cpp | 3 +- .../debugger/graphics_breakpoints.hxx | 27 +--------- .../debugger/graphics_breakpoints_p.hxx | 38 +++++++++++++ src/citra_qt/debugger/graphics_cmdlists.cpp | 54 +++++++------------ .../debugger/graphics_framebuffer.cpp | 4 +- src/common/common_funcs.h | 2 + src/video_core/debug_utils/debug_utils.cpp | 4 +- 8 files changed, 66 insertions(+), 67 deletions(-) create mode 100644 src/citra_qt/debugger/graphics_breakpoints_p.hxx diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index cd4a04eac..90e5c6aa6 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -27,6 +27,7 @@ set(HEADERS debugger/disassembler.hxx debugger/graphics.hxx debugger/graphics_breakpoints.hxx + debugger/graphics_breakpoints_p.hxx debugger/graphics_cmdlists.hxx debugger/graphics_framebuffer.hxx debugger/ramview.hxx diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp index 2f41d5f77..db98a3b05 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.cpp +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -9,6 +9,7 @@ #include #include "graphics_breakpoints.hxx" +#include "graphics_breakpoints_p.hxx" BreakPointModel::BreakPointModel(std::shared_ptr debug_context, QObject* parent) : QAbstractListModel(parent), context_weak(debug_context), @@ -39,7 +40,7 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const std::map map; map.insert({Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded")}); map.insert({Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed")}); - map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incomming primitive batch")}); + map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")}); map.insert({Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}); _dbg_assert_(GPU, map.size() == static_cast(Pica::DebugContext::Event::NumEvents)); diff --git a/src/citra_qt/debugger/graphics_breakpoints.hxx b/src/citra_qt/debugger/graphics_breakpoints.hxx index edc41283e..2142c6fa1 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.hxx +++ b/src/citra_qt/debugger/graphics_breakpoints.hxx @@ -15,32 +15,7 @@ class QLabel; class QPushButton; class QTreeView; -class BreakPointModel : public QAbstractListModel { - Q_OBJECT - -public: - enum { - Role_IsEnabled = Qt::UserRole, - }; - - BreakPointModel(std::shared_ptr context, QObject* parent); - - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); - -public slots: - void OnBreakPointHit(Pica::DebugContext::Event event); - void OnResumed(); - -private: - bool at_breakpoint; - Pica::DebugContext::Event active_breakpoint; - std::weak_ptr context_weak; -}; +class BreakPointModel; class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver { Q_OBJECT diff --git a/src/citra_qt/debugger/graphics_breakpoints_p.hxx b/src/citra_qt/debugger/graphics_breakpoints_p.hxx new file mode 100644 index 000000000..bf5daf73d --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints_p.hxx @@ -0,0 +1,38 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event); + void OnResumed(); + +private: + std::weak_ptr context_weak; + bool at_breakpoint; + Pica::DebugContext::Event active_breakpoint; +}; diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 4f58e9a90..ed51f97f3 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -124,59 +124,49 @@ TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo setWidget(main_widget); } -void TextureInfoDockWidget::OnAddressChanged(qint64 value) -{ +void TextureInfoDockWidget::OnAddressChanged(qint64 value) { info.address = value; emit UpdatePixmap(ReloadPixmap()); } -void TextureInfoDockWidget::OnFormatChanged(int value) -{ +void TextureInfoDockWidget::OnFormatChanged(int value) { info.format = static_cast(value); emit UpdatePixmap(ReloadPixmap()); } -void TextureInfoDockWidget::OnWidthChanged(int value) -{ +void TextureInfoDockWidget::OnWidthChanged(int value) { info.width = value; emit UpdatePixmap(ReloadPixmap()); } -void TextureInfoDockWidget::OnHeightChanged(int value) -{ +void TextureInfoDockWidget::OnHeightChanged(int value) { info.height = value; emit UpdatePixmap(ReloadPixmap()); } -void TextureInfoDockWidget::OnStrideChanged(int value) -{ +void TextureInfoDockWidget::OnStrideChanged(int value) { info.stride = value; emit UpdatePixmap(ReloadPixmap()); } -QPixmap TextureInfoDockWidget::ReloadPixmap() const -{ +QPixmap TextureInfoDockWidget::ReloadPixmap() const { u8* src = Memory::GetPointer(info.address); return QPixmap::fromImage(LoadTexture(src, info)); } -GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) -{ +GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { } -int GPUCommandListModel::rowCount(const QModelIndex& parent) const -{ +int GPUCommandListModel::rowCount(const QModelIndex& parent) const { return pica_trace.writes.size(); } -int GPUCommandListModel::columnCount(const QModelIndex& parent) const -{ +int GPUCommandListModel::columnCount(const QModelIndex& parent) const { return 2; } -QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const -{ +QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); @@ -202,8 +192,7 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const return QVariant(); } -QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const -{ +QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const { switch(role) { case Qt::DisplayRole: { @@ -220,8 +209,7 @@ QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientatio return QVariant(); } -void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) -{ +void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) { beginResetModel(); pica_trace = trace; @@ -233,20 +221,19 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& (cmd_id >= PICA_REG_INDEX(reg_name) && \ cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::registers.reg_name)) / 4) -void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) -{ - +void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); if (COMMAND_IN_RANGE(command_id, texture0)) { auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, Pica::registers.texture0_format); - QMainWindow* main_window = (QMainWindow*)parent(); + + // TODO: Instead, emit a signal here to be caught by the main window widget. + auto main_window = static_cast(parent()); main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window)); } } -void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) -{ +void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { QWidget* new_info_widget; const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); @@ -266,8 +253,7 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) } #undef COMMAND_IN_RANGE -GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) -{ +GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) { setObjectName("Pica Command List"); GPUCommandListModel* model = new GPUCommandListModel(this); @@ -283,7 +269,6 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pi connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(OnCommandDoubleClicked(const QModelIndex&))); - toggle_tracing = new QPushButton(tr("Start Tracing")); connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing())); @@ -301,8 +286,7 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pi setWidget(main_widget); } -void GPUCommandListWidget::OnToggleTracing() -{ +void GPUCommandListWidget::OnToggleTracing() { if (!Pica::DebugUtils::IsPicaTracing()) { Pica::DebugUtils::StartPicaTracing(); toggle_tracing->setText(tr("Stop Tracing")); diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index b91db5433..ac47f298d 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -217,11 +217,11 @@ void GraphicsFramebufferWidget::OnUpdate() break; } + // TODO: Implement a good way to visualize alpha components! + // TODO: Unify this decoding code with the texture decoder switch (framebuffer_format) { case Format::RGBA8: { - // TODO: Implement a good way to visualize the alpha component - QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); for (int y = 0; y < framebuffer_height; ++y) { diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 3f6df075a..db041780a 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -39,6 +39,8 @@ template<> struct CompileTimeAssert {}; #include #endif +#include "common_types.h" + // go to debugger mode #ifdef GEKKO #define Crash() diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 31ce09faf..71b03f31c 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -2,8 +2,6 @@ // Licensed under GPLv2 // Refer to the license.txt file included. -#include - #include #include #include @@ -359,7 +357,7 @@ std::unique_ptr FinishPicaTracing() } const Math::Vec4 LookupTexture(const u8* source, int x, int y, const TextureInfo& info) { - assert(info.format == Pica::Regs::TextureFormat::RGB8); + _dbg_assert_(GPU, info.format == Pica::Regs::TextureFormat::RGB8); // Cf. rasterizer code for an explanation of this algorithm. int texel_index_within_tile = 0; From 79bb403089ff91c24b8356ad8d5bc5f7666a0d11 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 20:41:01 +0100 Subject: [PATCH 11/14] More coding style fixes. --- src/citra_qt/debugger/graphics_breakpoints.cpp | 2 +- src/citra_qt/debugger/graphics_framebuffer.hxx | 2 +- src/video_core/pica.h | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp index db98a3b05..5973aafc8 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.cpp +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -43,7 +43,7 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")}); map.insert({Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}); - _dbg_assert_(GPU, map.size() == static_cast(Pica::DebugContext::Event::NumEvents)); + _dbg_assert_(GUI, map.size() == static_cast(Pica::DebugContext::Event::NumEvents)); return map[event]; } else if (index.column() == 1) { diff --git a/src/citra_qt/debugger/graphics_framebuffer.hxx b/src/citra_qt/debugger/graphics_framebuffer.hxx index bb73b2f72..1151ee7a1 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.hxx +++ b/src/citra_qt/debugger/graphics_framebuffer.hxx @@ -15,7 +15,7 @@ class QSpinBox; class CSpinBox; // Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots. -// This is because the Pica breakpoint callbacks will called on a non-GUI thread, while +// This is because the Pica breakpoint callbacks are called from a non-GUI thread, while // the widget usually wants to perform reactions in the GUI thread. class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { Q_OBJECT diff --git a/src/video_core/pica.h b/src/video_core/pica.h index c1f35a011..b463a32ef 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -131,16 +131,22 @@ struct Regs { }; static unsigned BytesPerPixel(TextureFormat format) { - if (format == TextureFormat::RGBA8) + switch (format) { + case TextureFormat::RGBA8: return 4; - else if (format == TextureFormat::RGB8) + + case TextureFormat::RGB8: return 3; - else if (format == TextureFormat::RGBA5551 || - format == TextureFormat::RGB565 || - format == TextureFormat::RGBA4) + + case TextureFormat::RGBA5551: + case TextureFormat::RGB565: + case TextureFormat::RGBA4: return 2; - else // placeholder + + default: + // placeholder for yet unknown formats return 1; + } } BitField< 0, 1, u32> texturing_enable; From ac4d7462cb9de2038ba466f03a0241fde28ed73f Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 20:41:45 +0100 Subject: [PATCH 12/14] citra-qt: Rename "Stop Tracing" to "Finish Tracing". This better reflects that no commands are supposed to show up until you hit the button a second time. --- src/citra_qt/debugger/graphics_cmdlists.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index ed51f97f3..b2b8df101 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -289,7 +289,7 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pi void GPUCommandListWidget::OnToggleTracing() { if (!Pica::DebugUtils::IsPicaTracing()) { Pica::DebugUtils::StartPicaTracing(); - toggle_tracing->setText(tr("Stop Tracing")); + toggle_tracing->setText(tr("Finish Tracing")); } else { pica_trace = Pica::DebugUtils::FinishPicaTracing(); emit TracingFinished(*pica_trace); From 0305435edd9d6de3a2b245d60bcbf339d6dda2ff Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 20:42:53 +0100 Subject: [PATCH 13/14] Pica: Re-enable command names on MSVC. The affected code is no longer limited by compiler support on that platform. --- src/video_core/pica.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/video_core/pica.h b/src/video_core/pica.h index b463a32ef..e7ca38978 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -536,10 +536,6 @@ struct Regs { static std::string GetCommandName(int index) { std::map map; - // TODO: MSVC does not support using offsetof() on non-static data members even though this - // is technically allowed since C++11. Hence, this functionality is disabled until - // MSVC properly supports it. - #ifndef _MSC_VER Regs regs; #define ADD_FIELD(name) \ do { \ @@ -576,7 +572,6 @@ struct Regs { ADD_FIELD(vs_swizzle_patterns); #undef ADD_FIELD - #endif // _MSC_VER // Return empty string if no match is found return map[index]; From 8b8131baecca16b46c22318b3331b2165cc74cbc Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 4 Dec 2014 21:00:06 +0100 Subject: [PATCH 14/14] More cleanups. --- src/citra_qt/debugger/graphics_breakpoints.cpp | 11 +++++++++-- src/citra_qt/debugger/graphics_cmdlists.cpp | 8 ++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp index 5973aafc8..df5579e15 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.cpp +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -36,7 +36,9 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: { - if (index.column() == 0) { + switch (index.column()) { + case 0: + { std::map map; map.insert({Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded")}); map.insert({Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed")}); @@ -46,8 +48,13 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const _dbg_assert_(GUI, map.size() == static_cast(Pica::DebugContext::Event::NumEvents)); return map[event]; - } else if (index.column() == 1) { + } + + case 1: return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled"); + + default: + break; } break; diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index b2b8df101..7f97cf143 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -177,14 +177,14 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) { QString content; if (index.column() == 0) { - content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str()); + QString content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str()); content.append(" "); + return content; } else if (index.column() == 1) { - content.append(QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0'))); + QString content = QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0')); content.append(QString("%1 ").arg(val, 8, 16, QLatin1Char('0'))); + return content; } - - return QVariant(content); } else if (role == CommandIdRole) { return QVariant::fromValue(cmd.cmd_id.Value()); }