diff --git a/src/nba/include/nba/core.hpp b/src/nba/include/nba/core.hpp index bf2895ef4..88f688606 100644 --- a/src/nba/include/nba/core.hpp +++ b/src/nba/include/nba/core.hpp @@ -37,6 +37,7 @@ struct CoreBase { virtual auto GetROM() -> ROM& = 0; virtual auto GetPRAM() -> u8* = 0; virtual auto GetVRAM() -> u8* = 0; + virtual auto GetOAM() -> u8* = 0; // @todo: come up with a solution for reading write-only registers. virtual auto PeekByteIO(u32 address) -> u8 = 0; virtual auto PeekHalfIO(u32 address) -> u16 = 0; diff --git a/src/nba/src/core.cpp b/src/nba/src/core.cpp index 262ff7018..1368143a6 100644 --- a/src/nba/src/core.cpp +++ b/src/nba/src/core.cpp @@ -160,6 +160,10 @@ auto Core::GetVRAM() -> u8* { return ppu.GetVRAM(); } +auto Core::GetOAM() -> u8* { + return ppu.GetOAM(); +} + auto Core::PeekByteIO(u32 address) -> u8 { return bus.hw.ReadByte(address); } diff --git a/src/nba/src/core.hpp b/src/nba/src/core.hpp index a636fd6ac..27a1f64bf 100644 --- a/src/nba/src/core.hpp +++ b/src/nba/src/core.hpp @@ -35,6 +35,7 @@ struct Core final : CoreBase { auto GetROM() -> ROM& override; auto GetPRAM() -> u8* override; auto GetVRAM() -> u8* override; + auto GetOAM() -> u8* override; auto PeekByteIO(u32 address) -> u8 override; auto PeekHalfIO(u32 address) -> u16 override; auto PeekWordIO(u32 address) -> u32 override; diff --git a/src/nba/src/hw/ppu/ppu.hpp b/src/nba/src/hw/ppu/ppu.hpp index ec07f479a..a24aead1b 100644 --- a/src/nba/src/hw/ppu/ppu.hpp +++ b/src/nba/src/hw/ppu/ppu.hpp @@ -43,6 +43,10 @@ struct PPU { return vram; } + auto GetOAM() -> u8* { + return oam; + } + template auto ALWAYS_INLINE ReadPRAM(u32 address) noexcept -> T { return read(pram, address & 0x3FF); diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 3a4c52c08..4a1732616 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -1,26 +1,32 @@ + include(version.cmake) set(SOURCES - src/widget/background_viewer_window.cpp - src/widget/background_viewer.cpp + src/widget/debugger/ppu/background_viewer_window.cpp + src/widget/debugger/ppu/background_viewer.cpp + src/widget/debugger/ppu/sprite_viewer.cpp + src/widget/debugger/ppu/sprite_viewer_window.cpp + src/widget/debugger/ppu/palette_box.cpp + src/widget/debugger/ppu/palette_viewer_window.cpp src/widget/controller_manager.cpp src/widget/input_window.cpp src/widget/main_window.cpp src/widget/screen.cpp - src/widget/palette_box.cpp - src/widget/palette_viewer_window.cpp src/config.cpp src/main.cpp ) set(HEADERS - src/widget/background_viewer_window.hpp - src/widget/background_viewer.hpp + src/widget/debugger/ppu/background_viewer_window.hpp + src/widget/debugger/ppu/background_viewer.hpp + src/widget/debugger/ppu/palette_box.hpp + src/widget/debugger/ppu/palette_viewer_window.hpp + src/widget/debugger/ppu/sprite_viewer.hpp + src/widget/debugger/ppu/sprite_viewer_window.hpp + src/widget/debugger/utility.hpp src/widget/controller_manager.hpp src/widget/input_window.hpp src/widget/main_window.hpp - src/widget/palette_box.hpp - src/widget/palette_viewer_window.hpp src/widget/screen.hpp src/config.hpp version.in.hpp diff --git a/src/platform/qt/src/widget/background_viewer.cpp b/src/platform/qt/src/widget/debugger/ppu/background_viewer.cpp similarity index 97% rename from src/platform/qt/src/widget/background_viewer.cpp rename to src/platform/qt/src/widget/debugger/ppu/background_viewer.cpp index a5dabeb68..0dc514713 100644 --- a/src/platform/qt/src/widget/background_viewer.cpp +++ b/src/platform/qt/src/widget/debugger/ppu/background_viewer.cpp @@ -18,6 +18,7 @@ #include #include +#include "widget/debugger/utility.hpp" #include "background_viewer.hpp" BackgroundViewer::BackgroundViewer(nba::CoreBase* core, QWidget* parent) : QWidget(parent), core(core) { @@ -66,6 +67,7 @@ BackgroundViewer::BackgroundViewer(nba::CoreBase* core, QWidget* parent) : QWidg const auto group_box = new QGroupBox{}; group_box->setLayout(grid); group_box->setTitle(tr("Background")); + group_box->setMinimumWidth(220); info_vbox->addWidget(group_box); } @@ -100,6 +102,7 @@ BackgroundViewer::BackgroundViewer(nba::CoreBase* core, QWidget* parent) : QWidg const auto group_box = new QGroupBox{}; group_box->setLayout(grid); group_box->setTitle(tr("Tile")); + group_box->setMinimumWidth(220); info_vbox->addWidget(group_box); @@ -431,14 +434,7 @@ void BackgroundViewer::PresentBackground() { for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) { - const u16 color_rgb565 = image_rgb565[i]; - - // @todo: de-duplicate the RGB565 to RGB32 conversion. - const int r = (color_rgb565 >> 0) & 31; - const int g = ((color_rgb565 >> 4) & 62) | (color_rgb565 >> 15); - const int b = (color_rgb565 >> 10) & 31; - - destination[i++] = 0xFF000000 | (r << 3 | r >> 2) << 16 | (g << 2 | g >> 4) << 8 | (b << 3 | b >> 2); + destination[i++] = Rgb565ToArgb8888(image_rgb565[i]); } i += skip; diff --git a/src/platform/qt/src/widget/background_viewer.hpp b/src/platform/qt/src/widget/debugger/ppu/background_viewer.hpp similarity index 98% rename from src/platform/qt/src/widget/background_viewer.hpp rename to src/platform/qt/src/widget/debugger/ppu/background_viewer.hpp index 4dd3464c9..35cab1112 100644 --- a/src/platform/qt/src/widget/background_viewer.hpp +++ b/src/platform/qt/src/widget/debugger/ppu/background_viewer.hpp @@ -13,7 +13,7 @@ #include #include -#include "widget/palette_box.hpp" +#include "palette_box.hpp" struct BackgroundViewer : QWidget { BackgroundViewer(nba::CoreBase* core, QWidget* parent = nullptr); diff --git a/src/platform/qt/src/widget/background_viewer_window.cpp b/src/platform/qt/src/widget/debugger/ppu/background_viewer_window.cpp similarity index 100% rename from src/platform/qt/src/widget/background_viewer_window.cpp rename to src/platform/qt/src/widget/debugger/ppu/background_viewer_window.cpp diff --git a/src/platform/qt/src/widget/background_viewer_window.hpp b/src/platform/qt/src/widget/debugger/ppu/background_viewer_window.hpp similarity index 91% rename from src/platform/qt/src/widget/background_viewer_window.hpp rename to src/platform/qt/src/widget/debugger/ppu/background_viewer_window.hpp index 79ee543b0..8245fa178 100644 --- a/src/platform/qt/src/widget/background_viewer_window.hpp +++ b/src/platform/qt/src/widget/debugger/ppu/background_viewer_window.hpp @@ -11,7 +11,7 @@ #include #include -#include "widget/background_viewer.hpp" +#include "background_viewer.hpp" struct BackgroundViewerWindow : QDialog { BackgroundViewerWindow(nba::CoreBase* core, QWidget* parent = nullptr); diff --git a/src/platform/qt/src/widget/palette_box.cpp b/src/platform/qt/src/widget/debugger/ppu/palette_box.cpp similarity index 84% rename from src/platform/qt/src/widget/palette_box.cpp rename to src/platform/qt/src/widget/debugger/ppu/palette_box.cpp index e6a7d82ab..247c09ec0 100644 --- a/src/platform/qt/src/widget/palette_box.cpp +++ b/src/platform/qt/src/widget/debugger/ppu/palette_box.cpp @@ -9,6 +9,7 @@ #include #include +#include "widget/debugger/utility.hpp" #include "palette_box.hpp" PaletteBox::PaletteBox(int rows, int columns, QWidget* parent) : QWidget(parent), rows(rows), columns(columns) { @@ -27,13 +28,7 @@ void PaletteBox::Draw(u16* palette_rgb565, int stride) { for(int y = 0; y < rows; y++) { for(int x = 0; x < columns; x++) { - const u16 color_rgb565 = palette_rgb565[y * stride + x]; - - const int r = (color_rgb565 >> 0) & 31; - const int g = ((color_rgb565 >> 4) & 62) | (color_rgb565 >> 15); - const int b = (color_rgb565 >> 10) & 31; - - palette_argb8888[i++] = 0xFF000000 | (r << 3 | r >> 2) << 16 | (g << 2 | g >> 4) << 8 | (b << 3 | b >> 2); + palette_argb8888[i++] = Rgb565ToArgb8888(palette_rgb565[y * stride + x]); } } diff --git a/src/platform/qt/src/widget/palette_box.hpp b/src/platform/qt/src/widget/debugger/ppu/palette_box.hpp similarity index 100% rename from src/platform/qt/src/widget/palette_box.hpp rename to src/platform/qt/src/widget/debugger/ppu/palette_box.hpp diff --git a/src/platform/qt/src/widget/palette_viewer_window.cpp b/src/platform/qt/src/widget/debugger/ppu/palette_viewer_window.cpp similarity index 100% rename from src/platform/qt/src/widget/palette_viewer_window.cpp rename to src/platform/qt/src/widget/debugger/ppu/palette_viewer_window.cpp diff --git a/src/platform/qt/src/widget/palette_viewer_window.hpp b/src/platform/qt/src/widget/debugger/ppu/palette_viewer_window.hpp similarity index 94% rename from src/platform/qt/src/widget/palette_viewer_window.hpp rename to src/platform/qt/src/widget/debugger/ppu/palette_viewer_window.hpp index 08f83c17f..8fe619bd0 100644 --- a/src/platform/qt/src/widget/palette_viewer_window.hpp +++ b/src/platform/qt/src/widget/debugger/ppu/palette_viewer_window.hpp @@ -11,7 +11,7 @@ #include #include -#include "widget/palette_box.hpp" +#include "palette_box.hpp" struct PaletteViewerWindow : QDialog { PaletteViewerWindow(nba::CoreBase* core, QWidget* parent = nullptr); diff --git a/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp new file mode 100644 index 000000000..7efec1ad6 --- /dev/null +++ b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2023 fleroviux + * + * Licensed under GPLv3 or any later version. + * Refer to the included LICENSE file. + */ + +#include +#include +#include + +#include +#include +#include + +#include "widget/debugger/utility.hpp" +#include "sprite_viewer.hpp" + +// -------------------------------------------------------------------- + +static const auto CreateMonospaceLabel = [](QLabel*& label) { + label = new QLabel{"-"}; + label->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + label->setTextInteractionFlags(Qt::TextSelectableByMouse); + return label; +}; + +static const auto CreateCheckBox = [](QCheckBox*& check_box) { + check_box = new QCheckBox{}; + check_box->setEnabled(false); + return check_box; +}; + +// -------------------------------------------------------------------- + + +SpriteViewer::SpriteViewer(nba::CoreBase* core, QWidget* parent) : QWidget{parent}, core{core} { + // TODO(fleroviux): do lots of cleanup. + + pram = (u16 *) core->GetPRAM(); + vram = core->GetVRAM(); + oam = core->GetOAM(); + + QHBoxLayout* hbox = new QHBoxLayout{}; + + { + QVBoxLayout* vbox = new QVBoxLayout{}; + + // OAM # and sprite magnification + { + sprite_index_input = new QSpinBox{}; + sprite_index_input->setMinimum(0); + sprite_index_input->setMaximum(127); + + magnification_input = new QSpinBox{}; + magnification_input->setMinimum(1); + magnification_input->setMaximum(16); + + const auto grid = new QGridLayout{}; + int row = 0; + grid->addWidget(new QLabel(tr("OAM #:")), row, 0); + grid->addWidget(sprite_index_input, row++, 1); + grid->addWidget(new QLabel(tr("Magnification:")), row, 0); + grid->addWidget(magnification_input, row++, 1); + vbox->addLayout(grid); + } + + // Sprite attributes + { + QGroupBox *box = new QGroupBox{}; + box->setTitle("Object Attributes"); + QGridLayout *grid = new QGridLayout{}; + box->setMinimumWidth(235); + + int row = 0; + + const auto PushRow = [&](const QString &label, QWidget *widget) { + grid->addWidget(new QLabel{QStringLiteral("%1:").arg(label)}, row, 0); + grid->addWidget(widget, row++, 1); + }; + + PushRow("Enabled", CreateCheckBox(check_sprite_enabled)); + PushRow("Position", CreateMonospaceLabel(label_sprite_position)); + PushRow("Size", CreateMonospaceLabel(label_sprite_size)); + PushRow("Tile number", CreateMonospaceLabel(label_sprite_tile_number)); + PushRow("Palette", CreateMonospaceLabel(label_sprite_palette)); + PushRow("8BPP", CreateCheckBox(check_sprite_8bpp)); + PushRow("Flip V", CreateCheckBox(check_sprite_vflip)); + PushRow("Flip H", CreateCheckBox(check_sprite_hflip)); + PushRow("Mode", CreateMonospaceLabel(label_sprite_mode)); + PushRow("Affine", CreateCheckBox(check_sprite_affine)); + PushRow("Transform #", CreateMonospaceLabel(label_sprite_transform)); + PushRow("Double-size", CreateCheckBox(check_sprite_double_size)); + PushRow("Mosaic", CreateCheckBox(check_sprite_mosaic)); + PushRow("Render cycles", CreateMonospaceLabel(label_sprite_render_cycles)); + + box->setLayout(grid); + vbox->addWidget(box); + } + + vbox->addStretch(); + hbox->addLayout(vbox); + } + + { + QVBoxLayout* vbox = new QVBoxLayout{}; + + canvas = new QWidget{}; + canvas->setFixedSize(64, 64); + canvas->setFixedSize(64, 64); + canvas->installEventFilter(this); + vbox->addWidget(canvas); + vbox->addStretch(1); + + hbox->addLayout(vbox); + } + + hbox->addStretch(1); + + setLayout(hbox); +} + +void SpriteViewer::Update() { + const int offset = sprite_index_input->value() << 3; + + const u16 attr0 = nba::read(oam, offset); + const u16 attr1 = nba::read(oam, offset + 2); + const u16 attr2 = nba::read(oam, offset + 4); + + const int shape = attr0 >> 14; + const int size = attr1 >> 14; + + static constexpr int k_sprite_size[4][4][2] = { + { { 8 , 8 }, { 16, 16 }, { 32, 32 }, { 64, 64 } }, // Square + { { 16, 8 }, { 32, 8 }, { 32, 16 }, { 64, 32 } }, // Horizontal + { { 8 , 16 }, { 8 , 32 }, { 16, 32 }, { 32, 64 } }, // Vertical + { { 8 , 8 }, { 8 , 8 }, { 8 , 8 }, { 8 , 8 } } // Prohibited + }; + + const int width = k_sprite_size[shape][size][0]; + const int height = k_sprite_size[shape][size][1]; + + const bool is_8bpp = attr0 & (1 << 13); + const uint tile_number = attr2 & 0x3FFu; + + const bool one_dimensional_mapping = core->PeekHalfIO(0x04000000) & (1 << 6); + + const int tiles_x = width >> 3; + const int tiles_y = height >> 3; + + u32* const buffer = (u32*)image_rgb32.bits(); + + if(is_8bpp) { + const u16* palette = &pram[256]; + + for(int tile_y = 0; tile_y < tiles_y; tile_y++) { + for(int tile_x = 0; tile_x < tiles_x; tile_x++) { + int current_tile_number; + + if(one_dimensional_mapping) { + current_tile_number = (tile_number + tile_y * (width >> 2) + (tile_x << 1)) & 0x3FF; + } else { + current_tile_number = ((tile_number + (tile_y * 32)) & 0x3E0) | (((tile_number & ~1) + (tile_x << 1)) & 0x1F); + } + + // @todo: handle bad tile numbers and overflows and the likes + + u32 tile_address = 0x10000u + (current_tile_number << 5); + + for(int y = 0; y < 8; y++) { + u64 tile_data = nba::read(vram, tile_address); + + u32* dst = &buffer[tile_y << 9 | y << 6 | tile_x << 3]; + + for(int x = 0; x < 8; x++) { + *dst++ = Rgb565ToArgb8888(palette[tile_data & 255u]); + tile_data >>= 8; + } + + tile_address += sizeof(u64); + } + } + } + } else { + const u16* palette = &pram[256 | attr2 >> 12 << 4]; + + for(int tile_y = 0; tile_y < tiles_y; tile_y++) { + for(int tile_x = 0; tile_x < tiles_x; tile_x++) { + int current_tile_number; + + if(one_dimensional_mapping) { + current_tile_number = (tile_number + tile_y * (width >> 3) + tile_x) & 0x3FF; + } else { + current_tile_number = ((tile_number + (tile_y * 32)) & 0x3E0) | ((tile_number + tile_x) & 0x1F); + } + + // @todo: handle bad tile numbers and overflows and the likes + + u32 tile_address = 0x10000u + (current_tile_number << 5); + + for(int y = 0; y < 8; y++) { + u32 tile_data = nba::read(vram, tile_address); + + u32* dst = &buffer[tile_y << 9 | y << 6 | tile_x << 3]; + + for(int x = 0; x < 8; x++) { + *dst++ = Rgb565ToArgb8888(palette[tile_data & 15u]); + tile_data >>= 4; + } + + tile_address += sizeof(u32); + } + } + } + } + + static constexpr const char* k_mode_names[4] { + "Normal", "Semi-Transparent", "Window", "Prohibited" + }; + + const uint x = attr1 & 0x1FFu; + const uint y = attr0 & 0x0FFu; + const bool affine = attr0 & (1 << 8); + const uint palette = is_8bpp ? 0u : (attr2 >> 12); + const uint mode = (attr0 >> 10) & 3; + const bool mosaic = attr0 & (1 << 12); + + label_sprite_position->setText(QStringLiteral("%1, %2").arg(x).arg(y)); + label_sprite_size->setText(QStringLiteral("%1, %2").arg(width).arg(height)); + label_sprite_tile_number->setText(QStringLiteral("%1").arg(tile_number)); + label_sprite_palette->setText(QStringLiteral("%1").arg(palette)); + check_sprite_8bpp->setChecked(is_8bpp); + label_sprite_mode->setText(k_mode_names[mode]); + check_sprite_affine->setChecked(affine); + check_sprite_mosaic->setChecked(mosaic); + + check_sprite_vflip->setEnabled(!affine); + check_sprite_hflip->setEnabled(!affine); + check_sprite_double_size->setEnabled(affine); + + const int signed_x = x >= 240 ? ((int)x - 512) : (int)x; + + int render_cycles = 0; + + if(affine) { + const bool double_size = attr0 & (1 << 9); + const uint transform = (attr1 >> 9) & 31u; + + check_sprite_enabled->setChecked(true); + check_sprite_vflip->setChecked(false); + check_sprite_hflip->setChecked(false); + label_sprite_transform->setText(QStringLiteral("%1").arg(transform)); + check_sprite_double_size->setChecked(double_size); + label_sprite_render_cycles->setText("0 (0%)"); + + const int clipped_draw_width = std::max(0, (double_size ? 2 * width : width) + std::min(signed_x, 0)); + + render_cycles = 10 + 2 * clipped_draw_width; + } else { + const bool enabled = !(attr0 & (1 << 9)); + const bool hflip = attr1 & (1 << 12); + const bool vflip = attr1 & (1 << 13); + + check_sprite_enabled->setChecked(enabled); + check_sprite_vflip->setChecked(vflip); + check_sprite_hflip->setChecked(hflip); + label_sprite_transform->setText("n/a"); + check_sprite_double_size->setChecked(false); + label_sprite_render_cycles->setText("0 (0%)"); + + if(enabled) { + render_cycles = std::max(0, width + std::min(signed_x, 0)); + } + } + + const int available_render_cycles = (core->PeekHalfIO(0x04000000) & (1 << 5)) ? 964 : 1232; + + label_sprite_render_cycles->setText(QString::fromStdString(fmt::format("{} ({:.2f} %)", render_cycles, 100.0f * (float)render_cycles / available_render_cycles))); + + sprite_width = width; + sprite_height = height; + + const int magnification = magnification_input->value(); + magnified_sprite_width = width * magnification; + magnified_sprite_height = height * magnification; + canvas->setFixedSize(magnified_sprite_width, magnified_sprite_height); + canvas->update(); +} + +bool SpriteViewer::eventFilter(QObject* object, QEvent* event) { + if(object == canvas && event->type() == QEvent::Paint) { + const QRect src_rect{0, 0, sprite_width, sprite_height}; + const QRect dst_rect{0, 0, magnified_sprite_width, magnified_sprite_height}; + + QPainter painter{canvas}; + painter.drawImage(dst_rect, image_rgb32, src_rect); + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.hpp b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.hpp new file mode 100644 index 000000000..cbeb9de12 --- /dev/null +++ b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 fleroviux + * + * Licensed under GPLv3 or any later version. + * Refer to the included LICENSE file. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct SpriteViewer : QWidget { + SpriteViewer(nba::CoreBase* core, QWidget* parent = nullptr); + + bool eventFilter(QObject* object, QEvent* event) override; + + void Update(); + +private: + nba::CoreBase* core; + u16* pram; + u8* vram; + u8* oam; + + QImage image_rgb32{64, 64, QImage::Format_RGB32}; + QSpinBox* sprite_index_input; + QSpinBox* magnification_input; + QWidget* canvas; + + QCheckBox* check_sprite_enabled; + QLabel* label_sprite_position; + QLabel* label_sprite_size; + QLabel* label_sprite_tile_number; + QLabel* label_sprite_palette; + QCheckBox* check_sprite_8bpp; + QCheckBox* check_sprite_vflip; + QCheckBox* check_sprite_hflip; + QLabel* label_sprite_mode; + QCheckBox* check_sprite_affine; + QLabel* label_sprite_transform; + QCheckBox* check_sprite_double_size; + QCheckBox* check_sprite_mosaic; + QLabel* label_sprite_render_cycles; + + int sprite_width = 0; + int sprite_height = 0; + int magnified_sprite_width = 0; + int magnified_sprite_height = 0; + + Q_OBJECT +}; diff --git a/src/platform/qt/src/widget/debugger/ppu/sprite_viewer_window.cpp b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer_window.cpp new file mode 100644 index 000000000..46e1f89b6 --- /dev/null +++ b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer_window.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 fleroviux + * + * Licensed under GPLv3 or any later version. + * Refer to the included LICENSE file. + */ + +#include + +#include "sprite_viewer_window.hpp" + +SpriteViewerWindow::SpriteViewerWindow(nba::CoreBase* core, QWidget* parent) : QDialog(parent) { + const auto vbox = new QVBoxLayout{}; + + sprite_viewer = new SpriteViewer{core, nullptr}; + vbox->addWidget(sprite_viewer); + setLayout(vbox); + + setWindowTitle(tr("Sprite Viewer")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +void SpriteViewerWindow::Update() { + if(!isVisible()) { + return; + } + sprite_viewer->Update(); +} \ No newline at end of file diff --git a/src/platform/qt/src/widget/debugger/ppu/sprite_viewer_window.hpp b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer_window.hpp new file mode 100644 index 000000000..57a002106 --- /dev/null +++ b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer_window.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 fleroviux + * + * Licensed under GPLv3 or any later version. + * Refer to the included LICENSE file. + */ + +#pragma once + +#include +#include +#include + +#include "sprite_viewer.hpp" + +struct SpriteViewerWindow : QDialog { + SpriteViewerWindow(nba::CoreBase* core, QWidget* parent = nullptr); + +public slots: + void Update(); + +private: + SpriteViewer* sprite_viewer; + + Q_OBJECT +}; diff --git a/src/platform/qt/src/widget/debugger/utility.hpp b/src/platform/qt/src/widget/debugger/utility.hpp new file mode 100644 index 000000000..f97ad667b --- /dev/null +++ b/src/platform/qt/src/widget/debugger/utility.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include + +constexpr u32 Rgb565ToArgb8888(u16 color_rgb565) { + const uint r = (color_rgb565 >> 0) & 31; + const uint g = ((color_rgb565 >> 4) & 62) | (color_rgb565 >> 15); + const uint b = (color_rgb565 >> 10) & 31; + + return 0xFF000000u | + (r << 3 | r >> 2) << 16 | + (g << 2 | g >> 4) << 8 | + (b << 3 | b >> 2); +} \ No newline at end of file diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index 5207f67b3..472c972a2 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -399,6 +399,12 @@ void MainWindow::CreateToolsMenu() { background_viewer_window->show(); }); + + connect(tools_menu->addAction(tr("Sprite Viewer")), &QAction::triggered, [this]() { + const auto sprite_viewer_window = new SpriteViewerWindow{core.get(), this}; + connect(screen.get(), &Screen::RequestDraw, sprite_viewer_window, &SpriteViewerWindow::Update); + sprite_viewer_window->show(); + }); } void MainWindow::CreateHelpMenu() { diff --git a/src/platform/qt/src/widget/main_window.hpp b/src/platform/qt/src/widget/main_window.hpp index d4cb9c175..1a3c66739 100644 --- a/src/platform/qt/src/widget/main_window.hpp +++ b/src/platform/qt/src/widget/main_window.hpp @@ -22,11 +22,12 @@ #include #include -#include "widget/background_viewer_window.hpp" +#include "widget/debugger/ppu/background_viewer_window.hpp" #include "widget/controller_manager.hpp" #include "widget/input_window.hpp" -#include "widget/palette_viewer_window.hpp" +#include "widget/debugger/ppu/palette_viewer_window.hpp" #include "widget/screen.hpp" +#include "widget/debugger/ppu/sprite_viewer_window.hpp" #include "config.hpp" struct MainWindow : QMainWindow { @@ -69,7 +70,6 @@ private slots: void SelectBIOS(); void SelectSaveFolder(); void RemoveSaveFolder(); - void ClearSaveFolder(); void PromptUserForReset(); auto CreateBooleanOption(