Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Viewer Improvements #14051

Merged
merged 7 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions rpcs3/rpcs3qt/debugger_frame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)
"\nKeys Ctrl+C: Copy instruction contents."
"\nKeys Ctrl+F: Find thread."
"\nKeys Alt+S: Capture SPU images of selected SPU or generalized form when used from PPU."
"\nKeys Alt+S: Launch a memory viewer pointed to the current RSX semaphores location when used from RSX."
"\nKeys Alt+R: Load last saved SPU state capture."
"\nKey D: SPU MFC commands logger, MFC debug setting must be enabled."
"\nKey D: Also PPU calling history logger, interpreter and non-zero call history size must be used."
Expand Down Expand Up @@ -642,6 +643,17 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)

if (modifiers & Qt::AltModifier)
{
if (cpu->id_type() == 0x55)
{
if (u32 addr = static_cast<rsx::thread*>(cpu)->label_addr)
{
// Memory viewer pointing to RSX semaphores
idm::make<memory_viewer_handle>(this, m_disasm, addr, make_check_cpu(nullptr));
}

return;
}

if (cpu->id_type() == 1)
{
new elf_memory_dumping_dialog(pc, m_gui_settings, this);
Expand Down
291 changes: 279 additions & 12 deletions rpcs3/rpcs3qt/memory_viewer_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
#include <QComboBox>
#include <QCheckBox>
#include <QWheelEvent>
#include <QHoverEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QThread>
#include <QKeyEvent>

#include "util/logs.hpp"
#include "util/asm.hpp"
Expand Down Expand Up @@ -142,10 +145,19 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr<CPUDis
hbox_tools_mem_buttons->addWidget(b_fnext);
tools_mem_buttons->setLayout(hbox_tools_mem_buttons);

QGroupBox* tools_mem_refresh = new QGroupBox(tr("Refresh"));
QHBoxLayout* hbox_tools_mem_refresh = new QHBoxLayout(this);
QPushButton* button_auto_refresh = new QPushButton(QStringLiteral(" "), this);
button_auto_refresh->setFixedWidth(20);
button_auto_refresh->setAutoDefault(false);
hbox_tools_mem_refresh->addWidget(button_auto_refresh);
tools_mem_refresh->setLayout(hbox_tools_mem_refresh);

// Merge Tools: Memory Viewer
hbox_tools_mem->addWidget(tools_mem_addr);
hbox_tools_mem->addWidget(tools_mem_words);
hbox_tools_mem->addWidget(tools_mem_buttons);
hbox_tools_mem->addWidget(tools_mem_refresh);
tools_mem->setLayout(hbox_tools_mem);

// Tools: Raw Image Preview Options
Expand Down Expand Up @@ -175,6 +187,8 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr<CPUDis
cbox_img_mode->addItem("ARGB", QVariant::fromValue(color_format::ARGB));
cbox_img_mode->addItem("RGBA", QVariant::fromValue(color_format::RGBA));
cbox_img_mode->addItem("ABGR", QVariant::fromValue(color_format::ABGR));
cbox_img_mode->addItem("G8", QVariant::fromValue(color_format::G8));
cbox_img_mode->addItem("G32MAX", QVariant::fromValue(color_format::G32MAX));
cbox_img_mode->setCurrentIndex(1); //ARGB
hbox_tools_img_mode->addWidget(cbox_img_mode);
tools_img_mode->setLayout(hbox_tools_img_mode);
Expand Down Expand Up @@ -426,6 +440,34 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr<CPUDis
ShowImage(this, m_addr, format, sizex, sizey, false);
});

QTimer* auto_refresh_timer = new QTimer(this);

connect(auto_refresh_timer, &QTimer::timeout, this, [this]()
{
ShowMemory();
});

connect(button_auto_refresh, &QAbstractButton::clicked, this, [=, this]()
{
const bool is_checked = button_auto_refresh->text() != " ";
if (auto_refresh_timer->isActive() != is_checked)
{
return;
}

if (is_checked)
{
button_auto_refresh->setText(QStringLiteral(" "));
auto_refresh_timer->stop();
}
else
{
button_auto_refresh->setText(reinterpret_cast<const char*>(u8"█"));
ShowMemory();
auto_refresh_timer->start(16);
}
});

if (!m_rsx)
{
connect(button_search, &QAbstractButton::clicked, this, [this]()
Expand Down Expand Up @@ -821,9 +863,14 @@ void memory_viewer_panel::ShowMemory()
m_mem_hex->setVisible(m_rowcount != 0);
m_mem_ascii->setVisible(m_rowcount != 0);

m_mem_addr->setText(t_mem_addr_str);
m_mem_hex->setText(t_mem_hex_str);
m_mem_ascii->setText(t_mem_ascii_str);
if (t_mem_addr_str != m_mem_addr->text())
m_mem_addr->setText(t_mem_addr_str);

if (t_mem_hex_str != m_mem_hex->text())
m_mem_hex->setText(t_mem_hex_str);

if (t_mem_ascii_str != m_mem_ascii->text())
m_mem_ascii->setText(t_mem_ascii_str);

auto mask_height = [&](int height)
{
Expand All @@ -846,19 +893,83 @@ void memory_viewer_panel::SetPC(const uint pc)
m_addr = pc;
}

void memory_viewer_panel::keyPressEvent(QKeyEvent* event)
{
if (!isActiveWindow())
{
QDialog::keyPressEvent(event);
return;
}

if (event->modifiers() == Qt::ControlModifier)
{
switch (const auto key = event->key())
{
case Qt::Key_PageUp:
case Qt::Key_PageDown:
{
scroll(key == Qt::Key_PageDown ? m_rowcount : 0u - m_rowcount);
break;
}
case Qt::Key_F5:
{
if (event->isAutoRepeat())
{
break;
}

// Single refresh
ShowMemory();
break;
}
case Qt::Key_F:
{
m_addr_line->setFocus();
break;
}
default: break;
}
}

QDialog::keyPressEvent(event);
}

void memory_viewer_panel::ShowImage(QWidget* parent, u32 addr, color_format format, u32 width, u32 height, bool flipv) const
{
u32 texel_bytes = 4;

switch (format)
{
case color_format::RGB:
{
texel_bytes = 3;
break;
}
case color_format::G8:
{
texel_bytes = 1;
break;
}
default: break;
}

// If exceeds 32-bits it is invalid as well, UINT32_MAX always fails checks
const u32 memsize = static_cast<u32>(std::min<u64>(4ull * width * height, u32{umax}));
const u32 memsize = utils::mul_saturate<u32>(utils::mul_saturate<u32>(texel_bytes, width), height);
if (memsize == 0)
{
return;
}

const auto originalBuffer = static_cast<u8*>(this->to_ptr(addr, memsize));
const auto convertedBuffer = new (std::nothrow) u8[memsize];

if (!originalBuffer || !convertedBuffer)
if (!originalBuffer)
{
return;
}

const auto convertedBuffer = new (std::nothrow) u8[memsize / texel_bytes * u64{4}];

if (!convertedBuffer)
{
// OOM or invalid memory address, give up
return;
Expand Down Expand Up @@ -932,6 +1043,47 @@ void memory_viewer_panel::ShowImage(QWidget* parent, u32 addr, color_format form
}
break;
}
case color_format::G8:
{
const u32 pitch = width * 1;
const u32 pitch_new = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
const u32 offset_new = y * pitch_new;
for (u32 x = 0; x < pitch; x++)
{
const u8 color = originalBuffer[offset + x];
convertedBuffer[offset_new + x * 4 + 0] = color;
convertedBuffer[offset_new + x * 4 + 1] = color;
convertedBuffer[offset_new + x * 4 + 2] = color;
convertedBuffer[offset_new + x * 4 + 3] = 255;
}
}
break;
}
case color_format::G32MAX:
{
// Special: whitens as 4-byte groups tend to have a higher value, in order to perceive memory contents
// May be used to search for instructions or floats for example

const u32 pitch = width * 4;

for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
for (u32 x = 0; x < pitch; x += 4)
{
const u8 color = std::max({originalBuffer[offset + x + 0], originalBuffer[offset + x + 1], originalBuffer[offset + x + 2], originalBuffer[offset + x + 3]});
convertedBuffer[offset + x + 0] = color;
convertedBuffer[offset + x + 1] = color;
convertedBuffer[offset + x + 2] = color;
convertedBuffer[offset + x + 3] = 255;
}
}

break;
}
}

// Flip vertically
Expand All @@ -951,20 +1103,135 @@ void memory_viewer_panel::ShowImage(QWidget* parent, u32 addr, color_format form
}
}

const QImage image(convertedBuffer, width, height, QImage::Format_ARGB32, [](void* buffer){ delete[] static_cast<u8*>(buffer); }, convertedBuffer);
if (image.isNull()) return;
std::unique_ptr<QImage> image = std::make_unique<QImage>(convertedBuffer, width, height, QImage::Format_ARGB32, [](void* buffer){ delete[] static_cast<u8*>(buffer); }, convertedBuffer);
if (image->isNull()) return;

QLabel* canvas = new QLabel();
canvas->setFixedSize(width, height);
canvas->setPixmap(QPixmap::fromImage(image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
canvas->setAttribute(Qt::WA_Hover);
canvas->setPixmap(QPixmap::fromImage(*image));

QLabel* image_title = new QLabel();

QHBoxLayout* layout = new QHBoxLayout();
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(image_title);
layout->addWidget(canvas);

QDialog* f_image_viewer = new QDialog(parent);
struct image_viewer : public QDialog
{
QLabel* const m_canvas;
QLabel* const m_image_title;
const std::unique_ptr<QImage> m_image;
const u32 m_addr;
const int m_addr_scale = 1;
const u32 m_pitch;
const u32 m_width;
const u32 m_height;
int m_canvas_scale = 1;

image_viewer(QWidget* parent, QLabel* canvas, QLabel* image_title, std::unique_ptr<QImage> image, u32 addr, u32 addr_scale, u32 pitch, u32 width, u32 height) noexcept
: QDialog(parent)
, m_canvas(canvas)
, m_image_title(image_title)
, m_image(std::move(image))
, m_addr(addr)
, m_addr_scale(addr_scale)
, m_pitch(pitch)
, m_width(width)
, m_height(height)
{
}

bool eventFilter(QObject* object, QEvent* event) override
{
if (object == m_canvas && (event->type() == QEvent::HoverMove || event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverLeave))
{
const QPoint xy = static_cast<QHoverEvent*>(event)->pos() / m_canvas_scale;
set_window_name_by_coordinates(xy.x(), xy.y());
return false;
}

if (object == m_canvas && event->type() == QEvent::MouseButtonDblClick && static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton)
{
QLineEdit* addr_line = static_cast<memory_viewer_panel*>(parent())->m_addr_line;

const QPoint xy = static_cast<QMouseEvent*>(event)->pos() / m_canvas_scale;
addr_line->setText(qstr(fmt::format("%08x", get_pointed_addr(xy.x(), xy.y()))));
Q_EMIT addr_line->returnPressed();
close();
return false;
}

return QDialog::eventFilter(object, event);
}

u32 get_pointed_addr(u32 x, u32 y) const
{
return m_addr + m_addr_scale * (y * m_pitch + x) / m_canvas_scale;
}

void set_window_name_by_coordinates(int x, int y)
{
if (x < 0 || y < 0)
{
m_image_title->setText(qstr(fmt::format("[-, -]: NA")));
return;
}

m_image_title->setText(qstr(fmt::format("[x:%d, y:%d]: 0x%x", x, y, get_pointed_addr(x, y))));
}

void keyPressEvent(QKeyEvent* event) override
{
if (!isActiveWindow())
{
QDialog::keyPressEvent(event);
return;
}

if (event->modifiers() == Qt::ControlModifier)
{
switch (const auto key = event->key())
{
case Qt::Key_Equal: // Also plus
case Qt::Key_Plus:
case Qt::Key_Minus:
{
m_canvas_scale = std::clamp(m_canvas_scale + (key == Qt::Key_Minus ? -1 : 1), 1, 5);

const QSize fixed_size(m_width * m_canvas_scale, m_height * m_canvas_scale);

// Fast transformation makes it not blurry, does not use bilinear filtering
m_canvas->setPixmap(QPixmap::fromImage(m_image->scaled(fixed_size.width(), fixed_size.height(), Qt::KeepAspectRatio, Qt::FastTransformation)));

m_canvas->setFixedSize(fixed_size);

QTimer::singleShot(0, this, [this]()
{
// sizeHint() evaluates properly after events have been processed
setFixedSize(sizeHint());
});

break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing default

}
}

QDialog::keyPressEvent(event);
}
};

image_viewer* f_image_viewer = new image_viewer(parent, canvas, image_title, std::move(image), addr, texel_bytes, width, width, height);
canvas->installEventFilter(f_image_viewer);
f_image_viewer->setWindowTitle(qstr(fmt::format("Raw Image @ 0x%x", addr)));
f_image_viewer->setFixedSize(QSize(width, height));
f_image_viewer->setLayout(layout);
f_image_viewer->setAttribute(Qt::WA_DeleteOnClose);
f_image_viewer->show();

QTimer::singleShot(0, f_image_viewer, [f_image_viewer]()
{
// sizeHint() evaluates properly after events have been processed
f_image_viewer->setFixedSize(f_image_viewer->sizeHint());
});
}