Skip to content

Commit

Permalink
Qt: improve layout of the sprite viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
fleroviux committed Dec 27, 2023
1 parent e57758e commit f7fa55b
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 146 deletions.
248 changes: 103 additions & 145 deletions src/platform/qt/src/widget/sprite_viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,47 @@ static const auto CreateCheckBox = [](QCheckBox*& 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();

QVBoxLayout* layout = new QVBoxLayout{};
QHBoxLayout* hbox = new QHBoxLayout{};
QVBoxLayout* vbox = new QVBoxLayout{};

sprite_index_input = new QSpinBox{};
sprite_index_input->setMinimum(0);
sprite_index_input->setMaximum(127);
layout->addWidget(sprite_index_input);
// 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);
layout->addWidget(magnification_input);
magnification_input = new QSpinBox{};
magnification_input->setMinimum(1);
magnification_input->setMaximum(16);

canvas = new QWidget{};
canvas->installEventFilter(this);
layout->addWidget(canvas);
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{};

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));
Expand All @@ -90,30 +100,21 @@ SpriteViewer::SpriteViewer(nba::CoreBase* core, QWidget* parent) : QWidget{paren
PushRow("Mosaic", CreateCheckBox(check_sprite_mosaic));
PushRow("Render cycles", CreateMonospaceLabel(label_sprite_render_cycles));

// // Temporary workaround:
// grid->setColumnStretch(2, 100);
// grid->setRowStretch(row, 100);

box->setLayout(grid);
layout->addWidget(box);
vbox->addWidget(box);
}

atlas_canvas = new QWidget{};
atlas_canvas->setFixedSize(1024, 512);
layout->addWidget(atlas_canvas);
atlas_canvas->installEventFilter(this);
hbox->addLayout(vbox);

canvas = new QWidget{};
canvas->installEventFilter(this);
hbox->addWidget(canvas);

setLayout(layout);
setLayout(hbox);
}

void SpriteViewer::Update() {
const int index = sprite_index_input->value();

RenderSpriteAtlas();

RenderSprite(index, (u32*)image_rgb32.bits(), 64);

const int offset = index << 3;
const int offset = sprite_index_input->value() << 3;

const u16 attr0 = nba::read<u16>(oam, offset);
const u16 attr1 = nba::read<u16>(oam, offset + 2);
Expand All @@ -135,108 +136,13 @@ void SpriteViewer::Update() {
const bool is_8bpp = attr0 & (1 << 13);
const uint tile_number = attr2 & 0x3FFu;

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 bool fast_hblank_oam_access = core->PeekHalfIO(0x04000000) & (1 << 5);
const int available_render_cycles = fast_hblank_oam_access ? 964 : 1232;
const float render_cycle_percentage = 100.0f * (float)render_cycles / (float)available_render_cycles;

label_sprite_render_cycles->setText(QString::fromStdString(fmt::format("{} ({:.2f} %)", render_cycles, render_cycle_percentage)));

sprite_width = width;
sprite_height = height;

const int magnification = magnification_input->value();
magnified_sprite_width = width * magnification;
magnified_sprite_height = height * magnification;
canvas->resize(magnified_sprite_width, magnified_sprite_height);
canvas->update();
}

void SpriteViewer::RenderSprite(int index, u32* buffer, int stride) {
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 offset = index << 3;

const u16 attr0 = nba::read<u16>(oam, offset);
const u16 attr1 = nba::read<u16>(oam, offset + 2);
const u16 attr2 = nba::read<u16>(oam, offset + 4);

const int shape = attr0 >> 14;
const int size = attr1 >> 14;
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];

Expand All @@ -257,7 +163,7 @@ void SpriteViewer::RenderSprite(int index, u32* buffer, int stride) {
for(int y = 0; y < 8; y++) {
u64 tile_data = nba::read<u64>(vram, tile_address);

u32* dst = &buffer[((tile_y << 3) * stride) | (y * stride) | tile_x << 3];
u32* dst = &buffer[tile_y << 9 | y << 6 | tile_x << 3];

for(int x = 0; x < 8; x++) {
*dst++ = RGB555(palette[tile_data & 255u]);
Expand Down Expand Up @@ -288,7 +194,7 @@ void SpriteViewer::RenderSprite(int index, u32* buffer, int stride) {
for(int y = 0; y < 8; y++) {
u32 tile_data = nba::read<u32>(vram, tile_address);

u32* dst = &buffer[((tile_y << 3) * stride) | (y * stride) | tile_x << 3];
u32* dst = &buffer[tile_y << 9 | y << 6 | tile_x << 3];

for(int x = 0; x < 8; x++) {
*dst++ = RGB555(palette[tile_data & 15u]);
Expand All @@ -300,20 +206,78 @@ void SpriteViewer::RenderSprite(int index, u32* buffer, int stride) {
}
}
}
}

void SpriteViewer::RenderSpriteAtlas() {
int sprite_index = 0;
static constexpr const char* k_mode_names[4] {
"Normal", "Semi-Transparent", "Window", "Prohibited"
};

atlas_image_rgb32.fill(0xFF000000u);
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;

for(int y = 0; y < 8; y++) {
for(int x = 0; x < 16; x++) {
RenderSprite(sprite_index++, &((u32*)atlas_image_rgb32.bits())[y * 65536 + x * 64], 1024);
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));
}
}

atlas_canvas->update();
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->resize(magnified_sprite_width, magnified_sprite_height);
canvas->update();
}

bool SpriteViewer::eventFilter(QObject* object, QEvent* event) {
Expand All @@ -323,13 +287,7 @@ bool SpriteViewer::eventFilter(QObject* object, QEvent* event) {

QPainter painter{canvas};
painter.drawImage(dst_rect, image_rgb32, src_rect);
return true;
}

if(object == atlas_canvas && event->type() == QEvent::Paint) {
QPainter painter{atlas_canvas};
painter.drawImage(0, 0, atlas_image_rgb32);
return true;
//return true;
}

return false;
Expand Down
6 changes: 5 additions & 1 deletion src/platform/qt/src/widget/sprite_viewer_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
#include "sprite_viewer_window.hpp"

SpriteViewerWindow::SpriteViewerWindow(nba::CoreBase* core, QWidget* parent) : QDialog(parent) {
sprite_viewer = new SpriteViewer{core, this};
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);
Expand Down

0 comments on commit f7fa55b

Please sign in to comment.