Search bar for GPU Commands

This commit is contained in:
GASTER 2016-10-08 21:33:21 -05:00
parent cc7f1155a8
commit 8aa28825db
3 changed files with 263 additions and 209 deletions

View File

@ -13,6 +13,7 @@
#include <QSpinBox> #include <QSpinBox>
#include <QTreeView> #include <QTreeView>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QLineEdit>
#include "citra_qt/debugger/graphics_cmdlists.h" #include "citra_qt/debugger/graphics_cmdlists.h"
#include "citra_qt/util/spinbox.h" #include "citra_qt/util/spinbox.h"
#include "citra_qt/util/util.h" #include "citra_qt/util/util.h"
@ -20,6 +21,8 @@
#include "video_core/debug_utils/debug_utils.h" #include "video_core/debug_utils/debug_utils.h"
#include "video_core/pica.h" #include "video_core/pica.h"
#include "video_core/pica_state.h" #include "video_core/pica_state.h"
#include <iostream>
using namespace std;
QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) { QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) {
QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
@ -51,7 +54,7 @@ public:
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 static_cast<int>(pica_trace.writes.size()); return static_cast<int>((pica_trace.isSearchCorrected ? pica_trace.search_corrected_writes : pica_trace.writes).size());
} }
int GPUCommandListModel::columnCount(const QModelIndex& parent) const { int GPUCommandListModel::columnCount(const QModelIndex& parent) const {
@ -62,7 +65,7 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
const auto& write = pica_trace.writes[index.row()]; const auto& write = (pica_trace.isSearchCorrected ? pica_trace.search_corrected_writes[index.row()] : pica_trace.writes[index.row()]);
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
QString content; QString content;
@ -112,6 +115,16 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&
endResetModel(); endResetModel();
} }
void GPUCommandListModel::UpdatePicaTrace(const QString& str) {
pica_trace.CorrectSearchWrites(str.toStdString());
cout << "Updated writes";
beginResetModel();
endResetModel();
}
#define COMMAND_IN_RANGE(cmd_id, reg_name) \ #define COMMAND_IN_RANGE(cmd_id, reg_name) \
(cmd_id >= PICA_REG_INDEX(reg_name) && \ (cmd_id >= PICA_REG_INDEX(reg_name) && \
cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::g_state.regs.reg_name)) / 4) cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::g_state.regs.reg_name)) / 4)
@ -218,7 +231,14 @@ GPUCommandListWidget::GPUCommandListWidget(QWidget* parent)
sub_layout->addWidget(copy_all); sub_layout->addWidget(copy_all);
main_layout->addLayout(sub_layout); main_layout->addLayout(sub_layout);
} }
main_widget->setLayout(main_layout);
search_bar = new QLineEdit();
search_bar->setPlaceholderText("Search Bar");
main_layout->addWidget(search_bar);
connect(search_bar, SIGNAL(textChanged(const QString &)), model, SLOT(UpdatePicaTrace(const QString &)));
main_widget->setLayout(main_layout);
setWidget(main_widget); setWidget(main_widget);
} }

View File

@ -27,9 +27,10 @@ public:
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override; int role = Qt::DisplayRole) const override;
public slots: public slots:
void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace); void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace);
void UpdatePicaTrace(const QString &);
private: private:
Pica::DebugUtils::PicaTrace pica_trace; Pica::DebugUtils::PicaTrace pica_trace;
@ -41,13 +42,14 @@ class GPUCommandListWidget : public QDockWidget {
public: public:
GPUCommandListWidget(QWidget* parent = nullptr); GPUCommandListWidget(QWidget* parent = nullptr);
QLineEdit* search_bar;
public slots: public slots:
void OnToggleTracing(); void OnToggleTracing();
void OnCommandDoubleClicked(const QModelIndex&); void OnCommandDoubleClicked(const QModelIndex&);
void SetCommandInfo(const QModelIndex&); void SetCommandInfo(const QModelIndex&);
void CopyAllToClipboard(); void CopyAllToClipboard();
signals: signals:
void TracingFinished(const Pica::DebugUtils::PicaTrace&); void TracingFinished(const Pica::DebugUtils::PicaTrace&);

View File

@ -20,251 +20,283 @@
#include "video_core/pica.h" #include "video_core/pica.h"
namespace CiTrace { namespace CiTrace {
class Recorder; class Recorder;
} }
namespace Pica { namespace Pica {
namespace Shader { namespace Shader {
struct ShaderSetup; struct ShaderSetup;
} }
class DebugContext { class DebugContext {
public: public:
enum class Event { enum class Event {
FirstEvent = 0, FirstEvent = 0,
PicaCommandLoaded = FirstEvent, PicaCommandLoaded = FirstEvent,
PicaCommandProcessed, PicaCommandProcessed,
IncomingPrimitiveBatch, IncomingPrimitiveBatch,
FinishedPrimitiveBatch, FinishedPrimitiveBatch,
VertexShaderInvocation, VertexShaderInvocation,
IncomingDisplayTransfer, IncomingDisplayTransfer,
GSPCommandProcessed, GSPCommandProcessed,
BufferSwapped, BufferSwapped,
NumEvents NumEvents
}; };
/** /**
* Inherit from this class to be notified of events registered to some debug context. * Inherit from this class to be notified of events registered to some debug context.
* Most importantly this is used for our debugger GUI. * Most importantly this is used for our debugger GUI.
* *
* To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. * 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 * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
* access * access
* @todo Evaluate an alternative interface, in which there is only one managing observer and * @todo Evaluate an alternative interface, in which there is only one managing observer and
* multiple child observers running (by design) on the same thread. * multiple child observers running (by design) on the same thread.
*/ */
class BreakPointObserver { class BreakPointObserver {
public: public:
/// Constructs the object such that it observes events of the given DebugContext. /// Constructs the object such that it observes events of the given DebugContext.
BreakPointObserver(std::shared_ptr<DebugContext> debug_context) BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
: context_weak(debug_context) { : context_weak(debug_context) {
std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
debug_context->breakpoint_observers.push_back(this); debug_context->breakpoint_observers.push_back(this);
} }
virtual ~BreakPointObserver() { virtual ~BreakPointObserver() {
auto context = context_weak.lock(); auto context = context_weak.lock();
if (context) { if (context) {
std::unique_lock<std::mutex> lock(context->breakpoint_mutex); std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
context->breakpoint_observers.remove(this); context->breakpoint_observers.remove(this);
// If we are the last observer to be destroyed, tell the debugger context that // 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 // it is free to continue. In particular, this is required for a proper Citra
// shutdown, when the emulation thread is waiting at a breakpoint. // shutdown, when the emulation thread is waiting at a breakpoint.
if (context->breakpoint_observers.empty()) if (context->breakpoint_observers.empty())
context->Resume(); context->Resume();
} }
} }
/** /**
* Action to perform when a breakpoint was reached. * Action to perform when a breakpoint was reached.
* @param event Type of event which triggered the breakpoint * @param event Type of event which triggered the breakpoint
* @param data Optional data pointer (if unused, this is a nullptr) * @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. * @note This function will perform nothing unless it is overridden in the child class.
*/ */
virtual void OnPicaBreakPointHit(Event, void*) {} virtual void OnPicaBreakPointHit(Event, void*) {}
/** /**
* Action to perform when emulation is resumed from a breakpoint. * Action to perform when emulation is resumed from a breakpoint.
* @note This function will perform nothing unless it is overridden in the child class. * @note This function will perform nothing unless it is overridden in the child class.
*/ */
virtual void OnPicaResume() {} virtual void OnPicaResume() {}
protected: protected:
/** /**
* Weak context pointer. This need not be valid, so when requesting a shared_ptr via * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
* context_weak.lock(), always compare the result against nullptr. * context_weak.lock(), always compare the result against nullptr.
*/ */
std::weak_ptr<DebugContext> context_weak; std::weak_ptr<DebugContext> context_weak;
}; };
/** /**
* Simple structure defining a breakpoint state * Simple structure defining a breakpoint state
*/ */
struct BreakPoint { struct BreakPoint {
bool enabled = false; bool enabled = false;
}; };
/** /**
* Static constructor used to create a shared_ptr of a DebugContext. * Static constructor used to create a shared_ptr of a DebugContext.
*/ */
static std::shared_ptr<DebugContext> Construct() { static std::shared_ptr<DebugContext> Construct() {
return std::shared_ptr<DebugContext>(new DebugContext); return std::shared_ptr<DebugContext>(new DebugContext);
} }
/** /**
* Used by the emulation core when a given event has happened. If a breakpoint has been set * 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. * 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 * The current thread then is halted until Resume() is called from another thread (or until
* emulation is stopped). * emulation is stopped).
* @param event Event which has happened * @param event Event which has happened
* @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
* Resume() is called. * Resume() is called.
*/ */
void OnEvent(Event event, void* data) { void OnEvent(Event event, void* data) {
// This check is left in the header to allow the compiler to inline it. // This check is left in the header to allow the compiler to inline it.
if (!breakpoints[(int)event].enabled) if (!breakpoints[(int)event].enabled)
return; return;
// For the rest of event handling, call a separate function. // For the rest of event handling, call a separate function.
DoOnEvent(event, data); DoOnEvent(event, data);
} }
void DoOnEvent(Event event, void* data); void DoOnEvent(Event event, void* data);
/** /**
* Resume from the current breakpoint. * Resume from the current breakpoint.
* @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
* Calling from any other thread is safe. * Calling from any other thread is safe.
*/ */
void Resume(); void Resume();
/** /**
* Delete all set breakpoints and resume emulation. * Delete all set breakpoints and resume emulation.
*/ */
void ClearBreakpoints() { void ClearBreakpoints() {
for (auto& bp : breakpoints) { for (auto& bp : breakpoints) {
bp.enabled = false; bp.enabled = false;
} }
Resume(); Resume();
} }
// TODO: Evaluate if access to these members should be hidden behind a public interface. // TODO: Evaluate if access to these members should be hidden behind a public interface.
std::array<BreakPoint, (int)Event::NumEvents> breakpoints; std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
Event active_breakpoint; Event active_breakpoint;
bool at_breakpoint = false; bool at_breakpoint = false;
std::shared_ptr<CiTrace::Recorder> recorder = nullptr; std::shared_ptr<CiTrace::Recorder> recorder = nullptr;
private: private:
/** /**
* Private default constructor to make sure people always construct this through Construct() * Private default constructor to make sure people always construct this through Construct()
* instead. * instead.
*/ */
DebugContext() = default; DebugContext() = default;
/// Mutex protecting current breakpoint state and the observer list. /// Mutex protecting current breakpoint state and the observer list.
std::mutex breakpoint_mutex; std::mutex breakpoint_mutex;
/// Used by OnEvent to wait for resumption. /// Used by OnEvent to wait for resumption.
std::condition_variable resume_from_breakpoint; std::condition_variable resume_from_breakpoint;
/// List of registered observers /// List of registered observers
std::list<BreakPointObserver*> breakpoint_observers; std::list<BreakPointObserver*> breakpoint_observers;
}; };
extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
namespace DebugUtils { namespace DebugUtils {
#define PICA_DUMP_TEXTURES 0 #define PICA_DUMP_TEXTURES 0
#define PICA_LOG_TEV 0 #define PICA_LOG_TEV 0
void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, void DumpShader(const std::string& filename, const Regs::ShaderConfig& config,
const Shader::ShaderSetup& setup, const Shader::ShaderSetup& setup,
const Regs::VSOutputAttributes* output_attributes); const Regs::VSOutputAttributes* output_attributes);
// Utility class to log Pica commands. // Utility class to log Pica commands.
struct PicaTrace { struct PicaTrace {
struct Write { struct Write {
u16 cmd_id; u16 cmd_id;
u16 mask; u16 mask;
u32 value; u32 value;
}; };
std::vector<Write> writes; std::vector<Write> writes;
}; std::vector<Write> search_corrected_writes;
bool isSearchCorrected = false;
void CorrectSearchWrites(std::string search_string) {
void StartPicaTracing(); search_corrected_writes.clear();
bool IsPicaTracing();
void OnPicaRegWrite(PicaTrace::Write write);
std::unique_ptr<PicaTrace> FinishPicaTracing();
struct TextureInfo { if (search_string.size() == 0)
PAddr physical_address; isSearchCorrected = false;
int width; else isSearchCorrected = true;
int height;
int stride;
Pica::Regs::TextureFormat format;
static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, for (int i = 0; i < writes.size(); i++) {
const Pica::Regs::TextureFormat& format); bool isValid = true;
};
/** std::string cmdName = Pica::Regs::GetCommandName(writes[i].cmd_id);
* Lookup texel located at the given coordinates and return an RGBA vector of its color.
* @param source Source pointer to read data from
* @param s,t Texture coordinates to read from
* @param info TextureInfo object describing the texture setup
* @param disable_alpha This is used for debug widgets which use this method to display textures
* without providing a good way to visualize alpha by themselves. If true, this will return 255 for
* the alpha component, and either drop the information entirely or store it in an "unused" color
* channel.
* @todo Eventually we should get rid of the disable_alpha parameter.
*/
const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const TextureInfo& info,
bool disable_alpha = false);
void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); if (cmdName.size() < search_string.size())
continue;
std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage); for (int j = 0; j < search_string.size(); j++) {
std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage); if (cmdName.at(j) != search_string.at(j)) {
isValid = false;
break;
}
}
/// Dumps the Tev stage config to log at trace level if (!isValid)
void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages); continue;
/** search_corrected_writes.push_back(writes[i]);
* Used in the vertex loader to merge access records. TODO: Investigate if actually useful. }
*/
class MemoryAccessTracker {
/// Combine overlapping and close ranges
void SimplifyRanges() {
for (auto it = ranges.begin(); it != ranges.end(); ++it) {
// NOTE: We add 32 to the range end address to make sure "close" ranges are combined,
// too
auto it2 = std::next(it);
while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) {
it->second = std::max(it->second, it2->first + it2->second - it->first);
it2 = ranges.erase(it2);
}
}
}
public: }
/// Record a particular memory access in the list };
void AddAccess(u32 paddr, u32 size) {
// Create new range or extend existing one
ranges[paddr] = std::max(ranges[paddr], size);
// Simplify ranges... void StartPicaTracing();
SimplifyRanges(); bool IsPicaTracing();
} void OnPicaRegWrite(PicaTrace::Write write);
std::unique_ptr<PicaTrace> FinishPicaTracing();
/// Map of accessed ranges (mapping start address to range size) struct TextureInfo {
std::map<u32, u32> ranges; PAddr physical_address;
}; int width;
int height;
int stride;
Pica::Regs::TextureFormat format;
} // namespace static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config,
const Pica::Regs::TextureFormat& format);
};
} // namespace /**
* Lookup texel located at the given coordinates and return an RGBA vector of its color.
* @param source Source pointer to read data from
* @param s,t Texture coordinates to read from
* @param info TextureInfo object describing the texture setup
* @param disable_alpha This is used for debug widgets which use this method to display textures
* without providing a good way to visualize alpha by themselves. If true, this will return 255 for
* the alpha component, and either drop the information entirely or store it in an "unused" color
* channel.
* @todo Eventually we should get rid of the disable_alpha parameter.
*/
const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const TextureInfo& info,
bool disable_alpha = false);
void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data);
std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
/// Dumps the Tev stage config to log at trace level
void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages);
/**
* Used in the vertex loader to merge access records. TODO: Investigate if actually useful.
*/
class MemoryAccessTracker {
/// Combine overlapping and close ranges
void SimplifyRanges() {
for (auto it = ranges.begin(); it != ranges.end(); ++it) {
// NOTE: We add 32 to the range end address to make sure "close" ranges are combined,
// too
auto it2 = std::next(it);
while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) {
it->second = std::max(it->second, it2->first + it2->second - it->first);
it2 = ranges.erase(it2);
}
}
}
public:
/// Record a particular memory access in the list
void AddAccess(u32 paddr, u32 size) {
// Create new range or extend existing one
ranges[paddr] = std::max(ranges[paddr], size);
// Simplify ranges...
SimplifyRanges();
}
/// Map of accessed ranges (mapping start address to range size)
std::map<u32, u32> ranges;
};
} // namespace
} // namespace