diff options
Diffstat (limited to 'src/citra_qt/debugger')
-rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.cpp | 65 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.h | 10 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.cpp | 41 | ||||
-rw-r--r-- | src/citra_qt/debugger/profiler.cpp | 138 | ||||
-rw-r--r-- | src/citra_qt/debugger/profiler.h | 50 | ||||
-rw-r--r-- | src/citra_qt/debugger/profiler.ui | 33 |
6 files changed, 310 insertions, 27 deletions
diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index 5bd6c0235..d621d7204 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -27,6 +27,7 @@ GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::Debug framebuffer_source_list = new QComboBox; framebuffer_source_list->addItem(tr("Active Render Target")); + framebuffer_source_list->addItem(tr("Active Depth Buffer")); framebuffer_source_list->addItem(tr("Custom")); framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source)); @@ -49,6 +50,9 @@ GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::Debug framebuffer_format_control->addItem(tr("RGB5A1")); framebuffer_format_control->addItem(tr("RGB565")); framebuffer_format_control->addItem(tr("RGBA4")); + framebuffer_format_control->addItem(tr("D16")); + framebuffer_format_control->addItem(tr("D24")); + framebuffer_format_control->addItem(tr("D24S8")); // TODO: This QLabel should shrink the image to the available space rather than just expanding... framebuffer_picture_label = new QLabel; @@ -172,8 +176,7 @@ void GraphicsFramebufferWidget::OnUpdate() { // 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); + const auto& framebuffer = Pica::registers.framebuffer; framebuffer_address = framebuffer.GetColorBufferPhysicalAddress(); framebuffer_width = framebuffer.GetWidth(); @@ -184,6 +187,18 @@ void GraphicsFramebufferWidget::OnUpdate() break; } + case Source::DepthBuffer: + { + const auto& framebuffer = Pica::registers.framebuffer; + + framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress(); + framebuffer_width = framebuffer.GetWidth(); + framebuffer_height = framebuffer.GetHeight(); + framebuffer_format = Format::D16; + + break; + } + case Source::Custom: { // Keep user-specified values @@ -197,15 +212,16 @@ void GraphicsFramebufferWidget::OnUpdate() // TODO: Implement a good way to visualize alpha components! // TODO: Unify this decoding code with the texture decoder - u32 bytes_per_pixel = GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(framebuffer_format)); + u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format); QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); - u8* color_buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); + u8* buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); + for (unsigned int y = 0; y < framebuffer_height; ++y) { for (unsigned int x = 0; x < framebuffer_width; ++x) { const u32 coarse_y = y & ~7; u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel; - const u8* pixel = color_buffer + offset; + const u8* pixel = buffer + offset; Math::Vec4<u8> color = { 0, 0, 0, 0 }; switch (framebuffer_format) { @@ -224,6 +240,29 @@ void GraphicsFramebufferWidget::OnUpdate() case Format::RGBA4: color = Color::DecodeRGBA4(pixel); break; + case Format::D16: + { + u32 data = Color::DecodeD16(pixel); + color.r() = data & 0xFF; + color.g() = (data >> 8) & 0xFF; + break; + } + case Format::D24: + { + u32 data = Color::DecodeD24(pixel); + color.r() = data & 0xFF; + color.g() = (data >> 8) & 0xFF; + color.b() = (data >> 16) & 0xFF; + break; + } + case Format::D24S8: + { + Math::Vec2<u32> data = Color::DecodeD24S8(pixel); + color.r() = data.x & 0xFF; + color.g() = (data.x >> 8) & 0xFF; + color.b() = (data.x >> 16) & 0xFF; + break; + } default: qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); break; @@ -240,3 +279,19 @@ void GraphicsFramebufferWidget::OnUpdate() framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format)); framebuffer_picture_label->setPixmap(pixmap); } + +u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) { + switch (format) { + case Format::RGBA8: + case Format::D24S8: + return 4; + case Format::RGB8: + case Format::D24: + return 3; + case Format::RGB5A1: + case Format::RGB565: + case Format::RGBA4: + case Format::D16: + return 2; + } +} diff --git a/src/citra_qt/debugger/graphics_framebuffer.h b/src/citra_qt/debugger/graphics_framebuffer.h index 15ebd1f7d..4cb396ffe 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.h +++ b/src/citra_qt/debugger/graphics_framebuffer.h @@ -20,8 +20,9 @@ class GraphicsFramebufferWidget : public BreakPointObserverDock { using Event = Pica::DebugContext::Event; enum class Source { - PicaTarget = 0, - Custom = 1, + PicaTarget = 0, + DepthBuffer = 1, + Custom = 2, // TODO: Add GPU framebuffer sources! }; @@ -32,8 +33,13 @@ class GraphicsFramebufferWidget : public BreakPointObserverDock { RGB5A1 = 2, RGB565 = 3, RGBA4 = 4, + D16 = 5, + D24 = 6, + D24S8 = 7 }; + static u32 BytesPerPixel(Format format); + public: GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index 06eaf0bf0..3b072d015 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -12,6 +12,7 @@ #include "graphics_vertex_shader.h" +using nihstro::OpCode; using nihstro::Instruction; using nihstro::SourceRegister; using nihstro::SwizzlePattern; @@ -78,7 +79,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con const SwizzlePattern& swizzle = info.swizzle_info[instr.common.operand_desc_id].pattern; // longest known instruction name: "setemit " - output << std::setw(8) << std::left << instr.opcode.GetInfo().name; + output << std::setw(8) << std::left << instr.opcode.Value().GetInfo().name; // e.g. "-c92.xyzw" static auto print_input = [](std::stringstream& output, const SourceRegister& input, @@ -109,16 +110,16 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con print_input_indexed(output, input, negate, swizzle_mask, address_register_name); }; - switch (instr.opcode.GetInfo().type) { - case Instruction::OpCodeType::Trivial: + switch (instr.opcode.Value().GetInfo().type) { + case OpCode::Type::Trivial: // Nothing to do here break; - case Instruction::OpCodeType::Arithmetic: + case OpCode::Type::Arithmetic: { // Use custom code for special instructions - switch (instr.opcode.EffectiveOpCode()) { - case Instruction::OpCode::CMP: + switch (instr.opcode.Value().EffectiveOpCode()) { + case OpCode::Id::CMP: { // NOTE: CMP always writes both cc components, so we do not consider the dest mask here. output << std::setw(4) << std::right << "cc."; @@ -142,13 +143,13 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con default: { - bool src_is_inverted = 0 != (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::SrcInversed); + bool src_is_inverted = 0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed); - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Dest) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Dest) { // e.g. "r12.xy__" - output << std::setw(4) << std::right << instr.common.dest.GetName() + "."; + output << std::setw(4) << std::right << instr.common.dest.Value().GetName() + "."; output << swizzle.DestMaskToString(); - } else if (instr.opcode.GetInfo().subtype == Instruction::OpCodeInfo::MOVA) { + } else if (instr.opcode.Value().GetInfo().subtype == OpCode::Info::MOVA) { output << std::setw(4) << std::right << "a0."; output << swizzle.DestMaskToString(); } else { @@ -156,7 +157,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con } output << " "; - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Src1) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src1) { SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); print_input_indexed(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), instr.common.AddressRegisterName()); } else { @@ -164,7 +165,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con } // TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1 - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Src2) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src2) { SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(false)); } @@ -175,17 +176,17 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con break; } - case Instruction::OpCodeType::Conditional: + case OpCode::Type::Conditional: { - switch (instr.opcode.EffectiveOpCode()) { - case Instruction::OpCode::LOOP: + switch (instr.opcode.Value().EffectiveOpCode()) { + case OpCode::Id::LOOP: output << "(unknown instruction format)"; break; default: output << "if "; - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasCondition) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasCondition) { const char* ops[] = { " || ", " && ", "", "" }; @@ -198,22 +199,22 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con output << ((!instr.flow_control.refy) ? "!" : " ") << "cc.y"; output << " "; - } else if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasUniformIndex) { + } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasUniformIndex) { output << "b" << instr.flow_control.bool_uniform_id << " "; } u32 target_addr = instr.flow_control.dest_offset; u32 target_addr_else = instr.flow_control.dest_offset; - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasAlternative) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasAlternative) { output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; - } else if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasExplicitDest) { + } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasExplicitDest) { output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; } else { // TODO: Handle other cases } - if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasFinishPoint) { + if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasFinishPoint) { output << "(return on " << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions << ")"; } diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp new file mode 100644 index 000000000..ae0568b6a --- /dev/null +++ b/src/citra_qt/debugger/profiler.cpp @@ -0,0 +1,138 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "profiler.h" + +#include "common/profiler_reporting.h" + +using namespace Common::Profiling; + +static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) +{ + static auto duration_to_float = [](Duration dur) -> float { + using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; + return std::chrono::duration_cast<FloatMs>(dur).count(); + }; + + switch (col) { + case 1: return duration_to_float(duration.avg); + case 2: return duration_to_float(duration.min); + case 3: return duration_to_float(duration.max); + default: return QVariant(); + } +} + +static const TimingCategoryInfo* GetCategoryInfo(int id) +{ + const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); + if (id >= categories.size()) { + return nullptr; + } else { + return &categories[id]; + } +} + +ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) +{ + updateProfilingInfo(); + const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); + results.time_per_category.resize(categories.size()); +} + +QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case 0: return tr("Category"); + case 1: return tr("Avg"); + case 2: return tr("Min"); + case 3: return tr("Max"); + } + } + + return QVariant(); +} + +QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const +{ + return createIndex(row, column); +} + +QModelIndex ProfilerModel::parent(const QModelIndex& child) const +{ + return QModelIndex(); +} + +int ProfilerModel::columnCount(const QModelIndex& parent) const +{ + return 4; +} + +int ProfilerModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } else { + return results.time_per_category.size() + 2; + } +} + +QVariant ProfilerModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::DisplayRole) { + if (index.row() == 0) { + if (index.column() == 0) { + return tr("Frame"); + } else { + return GetDataForColumn(index.column(), results.frame_time); + } + } else if (index.row() == 1) { + if (index.column() == 0) { + return tr("Frame (with swapping)"); + } else { + return GetDataForColumn(index.column(), results.interframe_time); + } + } else { + if (index.column() == 0) { + const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2); + return info != nullptr ? QString(info->name) : QVariant(); + } else { + if (index.row() - 2 < results.time_per_category.size()) { + return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]); + } else { + return QVariant(); + } + } + } + } + + return QVariant(); +} + +void ProfilerModel::updateProfilingInfo() +{ + results = GetTimingResultsAggregator()->GetAggregatedResults(); + emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); +} + +ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) +{ + ui.setupUi(this); + + model = new ProfilerModel(this); + ui.treeView->setModel(model); + + connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); + connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); +} + +void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) +{ + if (enable) { + update_timer.start(100); + model->updateProfilingInfo(); + } else { + update_timer.stop(); + } +} diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h new file mode 100644 index 000000000..a6d87aa0f --- /dev/null +++ b/src/citra_qt/debugger/profiler.h @@ -0,0 +1,50 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QAbstractItemModel> +#include <QDockWidget> +#include <QTimer> +#include "ui_profiler.h" + +#include "common/profiler_reporting.h" + +class ProfilerModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + ProfilerModel(QObject* parent); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + 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; + +public slots: + void updateProfilingInfo(); + +private: + Common::Profiling::AggregatedFrameResult results; +}; + +class ProfilerWidget : public QDockWidget +{ + Q_OBJECT + +public: + ProfilerWidget(QWidget* parent = 0); + +private slots: + void setProfilingInfoUpdateEnabled(bool enable); + +private: + Ui::Profiler ui; + ProfilerModel* model; + + QTimer update_timer; +}; diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui new file mode 100644 index 000000000..d3c9a9a1f --- /dev/null +++ b/src/citra_qt/debugger/profiler.ui @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Profiler</class> + <widget class="QDockWidget" name="Profiler"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Profiler</string> + </property> + <widget class="QWidget" name="dockWidgetContents"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeView" name="treeView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> |