From 65a7b72f6aa4ecd5982d496a10a27586f866f730 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 22 Feb 2024 23:22:58 +0000 Subject: [PATCH 01/21] Add arm64 MacOS build (#361) --- .github/workflows/build.yml | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7dc019dc..6a017a9a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,6 +96,45 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: NanoBoyAdvance-${{ runner.os }} + name: NanoBoyAdvance-${{ runner.os }}-x64 + path: NanoBoyAdvance.dmg + if-no-files-found: error + + build-macOS-arm: + runs-on: macos-14 + steps: + - uses: actions/checkout@v3 + - name: Setup dependencies + env: + HOMEBREW_NO_ANALYTICS: 1 + run: brew install sdl2 glew qt@5 + - name: Build NanoBoyAdvance + run: | + cmake -Bbuild \ + -DCMAKE_CXX_FLAGS="-s" \ + -DUSE_STATIC_SDL=ON \ + -DGLEW_USE_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5);$(brew --prefix glew)" \ + -DMACOS_BUILD_APP_BUNDLE=ON \ + -DMACOS_BUNDLE_QT=ON + cd build + make -j$(getconf _NPROCESSORS_ONLN) + - name: Create disk image + run: | + mkdir dmg + cp -a build/bin/qt/NanoBoyAdvance.app dmg/NanoBoyAdvance.app + codesign -s - --deep -f dmg/NanoBoyAdvance.app + ln -s /Applications dmg/Applications + hdiutil create \ + -fs HFS+ \ + -volname NanoBoyAdvance \ + -srcfolder dmg \ + -ov -format UDBZ \ + NanoBoyAdvance.dmg + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: NanoBoyAdvance-${{ runner.os }}-arm64 path: NanoBoyAdvance.dmg if-no-files-found: error From 47fd03266d2b6a6a3e307fb9335e852d124fea6f Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 25 Feb 2024 02:31:11 +0100 Subject: [PATCH 02/21] MP2K: handle corrupted wave info addresses (fixes #362) --- src/nba/src/bus/bus.cpp | 2 +- src/nba/src/core.cpp | 12 ++++++------ src/nba/src/hw/apu/hle/mp2k.cpp | 18 ++++++++++++++---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/nba/src/bus/bus.cpp b/src/nba/src/bus/bus.cpp index 5747ec22..db0ea00c 100644 --- a/src/nba/src/bus/bus.cpp +++ b/src/nba/src/bus/bus.cpp @@ -398,7 +398,7 @@ auto Bus::GetHostAddress(u32 address, size_t size) -> u8* { } } - Assert(false, "Bus: cannot get host address for 0x{:08X} ({} bytes)", address, size); + return nullptr; } } // namespace nba::core diff --git a/src/nba/src/core.cpp b/src/nba/src/core.cpp index 8b9245fe..73f3b336 100644 --- a/src/nba/src/core.cpp +++ b/src/nba/src/core.cpp @@ -83,12 +83,12 @@ void Core::Run(int cycles) { while(scheduler.GetTimestampNow() < limit) { if(bus.hw.haltcnt == HaltControl::Run) { if(cpu.state.r15 == hle_audio_hook) { - // @todo: cache the SoundInfo pointer once we have it? - apu.GetMP2K().SoundMainRAM( - *bus.GetHostAddress( - *bus.GetHostAddress(0x03007FF0) - ) - ); + const u32 sound_info_addr = *bus.GetHostAddress(0x03007FF0); + const auto sound_info = bus.GetHostAddress(sound_info_addr); + + if(sound_info != nullptr) { + apu.GetMP2K().SoundMainRAM(*sound_info); + } } cpu.Run(); diff --git a/src/nba/src/hw/apu/hle/mp2k.cpp b/src/nba/src/hw/apu/hle/mp2k.cpp index c160f6de..6c694e2f 100644 --- a/src/nba/src/hw/apu/hle/mp2k.cpp +++ b/src/nba/src/hw/apu/hle/mp2k.cpp @@ -70,8 +70,14 @@ void MP2K::SoundMainRAM(SoundInfo const& sound_info) { } hq_envelope_volume[0] = U8ToFloat(channel.envelope_attack); + const Sampler::WaveInfo* const wave_info = bus.GetHostAddress(channel.wave_address); + if(wave_info == nullptr) { + Log("MP2K: channel[{}] wave address is invalid: 0x{:08X}", channel.wave_address); + channel.status = 0; // Disable channel, there is no good way to deal with this. + continue; + } sampler = {}; - sampler.wave_info = *bus.GetHostAddress(channel.wave_address); + sampler.wave_info = *wave_info; if(sampler.wave_info.status & 0xC000) { channel.status |= CHANNEL_LOOP; } @@ -205,9 +211,13 @@ void MP2K::RenderFrame() { wave_size *= 33; wave_size = (wave_size + 63) / 64; } - sampler.wave_data = bus.GetHostAddress( - channel.wave_address + sizeof(Sampler::WaveInfo), wave_size - ); + const u32 wave_data_begin = channel.wave_address + sizeof(Sampler::WaveInfo); + sampler.wave_data = bus.GetHostAddress(wave_data_begin, wave_size); + if(sampler.wave_data == nullptr) { + Log("MP2K: channel[{}] sample data has bad memory range 0x{:08X} - 0x{:08X}.", i, wave_data_begin, wave_data_begin + wave_size); + channel.status = 0; // Disable channel, there is no good way to deal with this. + continue; + } sampler.compressed = compressed; } From bc42666664359ba7ef882107cb8d5b90b1598ba2 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 25 Feb 2024 18:01:33 +0100 Subject: [PATCH 03/21] APU: PSG L/R volume must be remapped from 0-7 to 1-8 --- src/nba/src/hw/apu/apu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nba/src/hw/apu/apu.cpp b/src/nba/src/hw/apu/apu.cpp index 70331feb..075a42e0 100644 --- a/src/nba/src/hw/apu/apu.cpp +++ b/src/nba/src/hw/apu/apu.cpp @@ -146,7 +146,7 @@ void APU::StepMixer() { if(psg.enable[channel][2]) psg_sample += mmio.psg3.GetSample(); if(psg.enable[channel][3]) psg_sample += mmio.psg4.GetSample(); - sample[channel] += psg_sample * psg_volume * psg.master[channel] / (28.0 * 0x200); + sample[channel] += psg_sample * psg_volume * (psg.master[channel] + 1) / (32.0 * 0x200); /* TODO: we assume that MP2K sends right channel to FIFO A and left channel to FIFO B, * but we haven't verified that this is actually correct. @@ -183,7 +183,7 @@ void APU::StepMixer() { if(psg.enable[channel][2]) psg_sample += mmio.psg3.GetSample(); if(psg.enable[channel][3]) psg_sample += mmio.psg4.GetSample(); - sample[channel] += psg_sample * psg_volume * psg.master[channel] / 28; + sample[channel] += psg_sample * psg_volume * (psg.master[channel] + 1) >> 5; for(int fifo = 0; fifo < 2; fifo++) { if(dma[fifo].enable[channel]) { From 2ad2fa8743608893946a950e31e5e8119dafe132 Mon Sep 17 00:00:00 2001 From: Joseph Stalin <11149380+GranMinigun@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:37:51 +0500 Subject: [PATCH 04/21] Log: Switch to fmt::text_style for coloring (#365) --- src/nba/include/nba/log.hpp | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/nba/include/nba/log.hpp b/src/nba/include/nba/log.hpp index e2beff45..505d2d92 100644 --- a/src/nba/include/nba/log.hpp +++ b/src/nba/include/nba/log.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -38,16 +39,35 @@ namespace detail { template inline void Log(std::string_view format, Args&&... args) { if constexpr((detail::kLogMask & level) != 0) { + fmt::text_style style = {}; char const* prefix = "[?]"; - if constexpr(level == Trace) prefix = "\e[36m[T]"; - if constexpr(level == Debug) prefix = "\e[34m[D]"; - if constexpr(level == Info) prefix = "\e[37m[I]"; - if constexpr(level == Warn) prefix = "\e[33m[W]"; - if constexpr(level == Error) prefix = "\e[35m[E]"; - if constexpr(level == Fatal) prefix = "\e[31m[F]"; + if constexpr(level == Trace) { + style = fmt::fg(fmt::terminal_color::cyan); + prefix = "[T]"; + } + if constexpr(level == Debug) { + style = fmt::fg(fmt::terminal_color::blue); + prefix = "[D]"; + } + if constexpr(level == Info) { + prefix = "[I]"; + } + if constexpr(level == Warn) { + style = fmt::fg(fmt::terminal_color::yellow); + prefix = "[W]"; + } + if constexpr(level == Error) { + style = fmt::fg(fmt::terminal_color::magenta); + prefix = "[E]"; + } + if constexpr(level == Fatal) { + style = fmt::fg(fmt::terminal_color::red); + prefix = "[F]"; + } - fmt::print("{} {}\n", prefix, fmt::format(format, std::forward(args)...)); + const auto& style_ref = style; + fmt::print(style_ref, "{} {}\n", prefix, fmt::format(format, std::forward(args)...)); } } From fa16809d24114884c1de451d96deb5c7c4bb055c Mon Sep 17 00:00:00 2001 From: fleroviux Date: Thu, 29 Feb 2024 19:35:30 +0100 Subject: [PATCH 05/21] Qt: implement an option to pause the emulator when the window is inactive --- src/platform/qt/rc/config.toml | 2 ++ src/platform/qt/src/config.cpp | 2 ++ src/platform/qt/src/config.hpp | 1 + src/platform/qt/src/widget/main_window.cpp | 20 +++++++++++++++++--- src/platform/qt/src/widget/main_window.hpp | 2 ++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/rc/config.toml b/src/platform/qt/rc/config.toml index 76f3b897..89b8e66f 100644 --- a/src/platform/qt/rc/config.toml +++ b/src/platform/qt/rc/config.toml @@ -57,3 +57,5 @@ show_fps = false lock_aspect_ratio = true # Snap screen size to integer multiples of the original screen width and height use_integer_scaling = false +# Pause emulator when the main window becomes inactive +pause_emulator_when_inactive = true diff --git a/src/platform/qt/src/config.cpp b/src/platform/qt/src/config.cpp index b09e9b1c..7fbc712b 100644 --- a/src/platform/qt/src/config.cpp +++ b/src/platform/qt/src/config.cpp @@ -59,6 +59,7 @@ void QtConfig::LoadCustomData(toml::value const& data) { window.lock_aspect_ratio = toml::find_or(window_, "lock_aspect_ratio", true); window.use_integer_scaling = toml::find_or(window_, "use_integer_scaling", false); window.show_fps = toml::find_or(window_, "show_fps", false); + window.pause_emulator_when_inactive = toml::find_or(window_, "pause_emulator_when_inactive", true); } } @@ -90,6 +91,7 @@ void QtConfig::SaveCustomData( data["window"]["lock_aspect_ratio"] = window.lock_aspect_ratio; data["window"]["use_integer_scaling"] = window.use_integer_scaling; data["window"]["show_fps"] = window.show_fps; + data["window"]["pause_emulator_when_inactive"] = window.pause_emulator_when_inactive; data["recent_files"] = recent_files; } diff --git a/src/platform/qt/src/config.hpp b/src/platform/qt/src/config.hpp index 17dd7875..91af8291 100644 --- a/src/platform/qt/src/config.hpp +++ b/src/platform/qt/src/config.hpp @@ -77,6 +77,7 @@ struct QtConfig final : nba::PlatformConfig { bool lock_aspect_ratio = true; bool use_integer_scaling = false; bool show_fps = false; + bool pause_emulator_when_inactive = true; } window; std::vector recent_files; diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index ac5ef5b1..9085f4b3 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -366,6 +366,7 @@ void MainWindow::CreateWindowMenu(QMenu* parent) { menu->addSeparator(); CreateBooleanOption(menu, "Show FPS", &config->window.show_fps); + CreateBooleanOption(menu, "Pause emulator when inactive", &config->window.pause_emulator_when_inactive); } void MainWindow::CreateConfigMenu() { @@ -613,7 +614,7 @@ void MainWindow::PromptUserForReset() { } bool MainWindow::eventFilter(QObject* obj, QEvent* event) { - auto type = event->type(); + const auto type = event->type(); if(obj == this && (type == QEvent::KeyPress || type == QEvent::KeyRelease)) { auto key = dynamic_cast(event)->key(); @@ -633,7 +634,7 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) { if(pressed && key == Qt::Key_Escape) { SetFullscreen(false); } - } else if(type == QEvent::FileOpen) { + } if(type == QEvent::FileOpen) { auto file = dynamic_cast(event)->file(); LoadROM(file.toStdU16String()); } else if(type == QEvent::Drop) { @@ -651,6 +652,12 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) { return QObject::eventFilter(obj, event); } +void MainWindow::changeEvent(QEvent* event) { + if(event->type() == QEvent::ActivationChange) { + ApplyPauseState(); + } +} + void MainWindow::dragEnterEvent(QDragEnterEvent* event) { event->acceptProposedAction(); } @@ -679,13 +686,20 @@ void MainWindow::Reset() { } void MainWindow::SetPause(bool paused) { + pause_action->setChecked(paused); + ApplyPauseState(); +} + +void MainWindow::ApplyPauseState() { + const bool window_inactive_and_should_pause = !isActiveWindow() && config->window.pause_emulator_when_inactive; + const bool paused = pause_action->isChecked() || window_inactive_and_should_pause; + if(!paused) { screen->SetForceClear(false); } emu_thread->SetPause(paused); config->audio_dev->SetPause(paused); - pause_action->setChecked(paused); } void MainWindow::Stop() { diff --git a/src/platform/qt/src/widget/main_window.hpp b/src/platform/qt/src/widget/main_window.hpp index e8377346..a5ddead8 100644 --- a/src/platform/qt/src/widget/main_window.hpp +++ b/src/platform/qt/src/widget/main_window.hpp @@ -49,6 +49,7 @@ private slots: protected: bool eventFilter(QObject* obj, QEvent* event) override; + void changeEvent(QEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; @@ -114,6 +115,7 @@ private slots: void Reset(); void SetPause(bool paused); + void ApplyPauseState(); void Stop(); void UpdateMenuBarVisibility(); void UpdateMainWindowActionList(); From d54e9fdbcf731fe784c63d72e49a4dbd5b760033 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Thu, 29 Feb 2024 19:41:11 +0100 Subject: [PATCH 06/21] Qt: fix sprite viewer in paused state --- src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp index a329ea5d..032eeead 100644 --- a/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp +++ b/src/platform/qt/src/widget/debugger/ppu/sprite_viewer.cpp @@ -32,6 +32,7 @@ QWidget* SpriteViewer::CreateSpriteIndexInput() { m_spin_sprite_index = new QSpinBox{}; m_spin_sprite_index->setMinimum(0); m_spin_sprite_index->setMaximum(127); + connect(m_spin_sprite_index, QOverload::of(&QSpinBox::valueChanged), [this](int _) { Update(); }); return m_spin_sprite_index; } @@ -231,10 +232,6 @@ void SpriteViewer::Update() { m_check_sprite_affine->setChecked(affine); m_check_sprite_mosaic->setChecked(mosaic); - m_check_sprite_vflip->setEnabled(!affine); - m_check_sprite_hflip->setEnabled(!affine); - m_check_sprite_double_size->setEnabled(affine); - const int signed_x = x >= 240 ? ((int)x - 512) : (int)x; int render_cycles = 0; From 198ad4f399f58b6f69145f14ba98ef4a02fa1b26 Mon Sep 17 00:00:00 2001 From: Joseph Stalin <11149380+GranMinigun@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:02:22 +0500 Subject: [PATCH 07/21] Qt: Redirect stdout on Windows (#367) --- src/platform/qt/src/main.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/platform/qt/src/main.cpp b/src/platform/qt/src/main.cpp index d6d5149a..9a4a6b96 100644 --- a/src/platform/qt/src/main.cpp +++ b/src/platform/qt/src/main.cpp @@ -63,6 +63,25 @@ int main(int argc, char** argv) { setenv("LC_NUMERIC", "C", 1); #endif +#if defined(WIN32) + constexpr auto terminal_output = "CONOUT$"; + constexpr auto null_output = "NUL:"; + + // Check whether we are already attached to an output stream. + const auto output_handle = GetStdHandle(STD_OUTPUT_HANDLE); + if(!output_handle || (output_handle == INVALID_HANDLE_VALUE)) { + // If started from terminal, attach and output logs to it. + if(AttachConsole(ATTACH_PARENT_PROCESS)) { + if(!freopen(terminal_output, "a", stdout)) { assert(false); } + if(!freopen(terminal_output, "a", stderr)) { assert(false); } + } else { + // Otherwise, discard log output. + if(!freopen(null_output, "w", stdout)) { assert(false); } + if(!freopen(null_output, "w", stderr)) { assert(false); } + } + } +#endif + // On some systems (e.g. macOS) QSurfaceFormat::setDefaultFormat() must be called before constructing QApplication. auto format = QSurfaceFormat{}; format.setProfile(QSurfaceFormat::CoreProfile); From f418956456fd1d2e04c66cf48757100580a22875 Mon Sep 17 00:00:00 2001 From: Vulcalien Date: Sat, 9 Mar 2024 20:45:00 +0100 Subject: [PATCH 08/21] Update Desktop Entry and AppStream metadata files (#356) * Update Desktop Entry and AppStream metadata files Changes to AppStream metadata: - fixed the license (GPL-3.0+ instead of GPL-3.0) - updated the project description and the screenshot - added the latest releases (1.8.0, 1.7.1) to the changelog * Add minor improvements to Desktop Entry and AppStream metadata files --- .../io.nanoboyadvance.NanoBoyAdvance.desktop | 6 +- ...nanoboyadvance.NanoBoyAdvance.metainfo.xml | 64 +++++++++++++++++-- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.desktop b/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.desktop index 66bb8084..03c12966 100644 --- a/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.desktop +++ b/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.desktop @@ -1,12 +1,12 @@ -# Created with jdDesktopEntryEdit 1.0 [Desktop Entry] Type=Application Name=NanoBoyAdvance -Comment=A cycle-accurate Nintendo Game Boy Advance emulator +GenericName=Game Boy Advance Emulator +Comment=Cycle-accurate Nintendo Game Boy Advance emulator Icon=io.nanoboyadvance.NanoBoyAdvance TryExec=NanoBoyAdvance Exec=NanoBoyAdvance %f -MimeType=application/x-gameboy-advance-rom;pplication/x-agb-rom;application/x-gba-rom; +MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom; Categories=Game;Emulator; Keywords=Emulator;Nintendo;GameBoy;Game Boy Advance;GBA;GB; SingleMainWindow=true diff --git a/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.metainfo.xml b/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.metainfo.xml index 968fa467..5deb98f6 100644 --- a/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.metainfo.xml +++ b/src/platform/qt/rc/io.nanoboyadvance.NanoBoyAdvance.metainfo.xml @@ -1,13 +1,12 @@ - io.nanoboyadvance.NanoBoyAdvance NanoBoyAdvance - A cycle-accurate Nintendo Game Boy Advance emulator + Cycle-accurate Nintendo Game Boy Advance emulator fleroviux io.nanoboyadvance.NanoBoyAdvance.desktop CC0-1.0 - GPL-3.0 + GPL-3.0+

NanoBoyAdvance is a cycle-accurate Game Boy Advance emulator. It aims to be as accurate as possible, while also offering enhancements such as improved audio quality.

@@ -18,9 +17,10 @@ It aims to be as accurate as possible, while also offering enhancements such as
  • Post-processing options (color correction, xBRZ upscaling and LCD ghosting simulation)
  • Save State support (10x save slots available)
  • Game controller support (buttons and axises can be remapped)
  • -
  • Loading ROMs from archives (Zip, 7z, Tar and limited RAR1 support)
  • +
  • Loading ROMs from archives (Zip, 7z, Tar and limited RAR support)
  • RTC emulation
  • Solar Sensor emulation (for example: for Boktai - The Sun is in Your Hand)
  • +
  • Debug tools: PPU palette, tile, background and sprite viewers
  • Accuracy

    A lot of research and attention to detail has been put into developing this core and making it accurate.

    @@ -29,14 +29,64 @@ It aims to be as accurate as possible, while also offering enhancements such as
  • Passes all AGS aging cartridge tests (NBA was the first public emulator to achieve this)
  • Passes most tests in the mGBA test suite
  • Passes ARMWrestler, gba-suite and FuzzARM CPU tests
  • +
  • Very high compatibility, including games that require emulation of peculiar hardware edge-cases
  • - https://github.com/nba-emu/NanoBoyAdvance/raw/v1.7/docs/screenshot.png + https://raw.githubusercontent.com/nba-emu/NanoBoyAdvance/c8493948743f6e9e6f72abe057ba3ed5d03ad16a/docs/screenshot.png + + https://github.com/nba-emu/NanoBoyAdvance/releases/tag/v1.8.0 + +
      +
    • UI: implement "Sharp" video filter for better nearest interpolation at non-integer scales (thanks GranMinigun)
    • +
    • UI: implemented PPU viewers for palettes, tiles, backgrounds and sprites
    • +
    • UI: add an option to set the audio volume
    • +
    • UI: add an option to set the audio volume
    • +
    • Input: fix input dropping regression introduced in NBA 1.7.1
    • +
    • Input: move from SDL game controller to SDL joystick API (fixes broken mapping on some controllers)
    • +
    • APU: MP2K HLE: interpolate envelopes and use floating-point math
    • +
    • APU: MP2K HLE: implement a better reverb algorithm
    • +
    • APU: MP2K HLE: add an option to force-enable reverb
    • +
    • APU: remove "Zombie" mode emulation (does not appear to exist on GBA)
    • +
    • APU: fix mid-note envelope frequency changes
    • +
    • APU: FIFO writes should happen in place
    • +
    • APU: reset FIFOs on overflow
    • +
    • APU: add master-enable checks for 16-bit/32-bit IO writes
    • +
    • PPU: fix sprite pixels from previous scanline displayed in the last scanline
    • +
    • PPU: more accurate update of attribute buffer for OBJWIN sprites
    • +
    • PPU: more accurate emulation of horizontal and vertical sprite mosaic
    • +
    • PPU: more accurate handling of mid-frame vertical mosaic reconfiguration
    • +
    • PPU: fix out-of-bounds bitmap fetches in Mode 3 to 5
    • +
    • DMA: fix incorrect mapping of DMA channel to APU FIFO
    • +
    • Timer: somewhat handle timer overflow during one cycle enable delay (thanks alyosha)
    • +
    • Game Pak: simulate ~6 ms EEPROM device busy period after write (not yet accurate to cartridges that e.g. have been in the freezer for 30 minutes)
    • +
    • Game Pak: emulate the Mask ROM internal address register
    • +
    • Audio: fix clicking artifacts when using Sinc resampling
    • +
    • Audio: fix incorrect liner interpolation in Cosine resampling
    • +
    • PlatformCore: fixed hang when fast-forwarding while the emulator is paused
    • +
    • Misc: implemented support for unicode paths
    • +
    +
    +
    + + https://github.com/nba-emu/NanoBoyAdvance/releases/tag/v1.7.1 + +
      +
    • PPU: disallow out-of-bounds BG VRAM tile fetches and return open bus
    • +
    • Core: do not skip to the next event if the CPU woke up during a DMA
    • +
    • KeyPad: always request IRQs from the emulator (not the calling) thread
    • +
    • GameDB: fix entries for a bunch of Classic NES and Famicom Mini titles
    • +
    • IO: do not enter STOP mode when it is not implemented
    • +
    • mGBA log: clear the message buffer after printing the message
    • +
    • mGBA log: flush STDOUT after each message
    • +
    • Catch fmt::system_error when fmt::print() fails to write to STDOUT
    • +
    +
    +
    https://github.com/nba-emu/NanoBoyAdvance/releases/tag/v1.7 @@ -90,7 +140,7 @@ It aims to be as accurate as possible, while also offering enhancements such as
    https://github.com/nba-emu/NanoBoyAdvance - https://github.com/nba-emu/NanoBoyAdvance + https://github.com/nba-emu/NanoBoyAdvance/issues Game Emulator @@ -104,7 +154,7 @@ It aims to be as accurate as possible, while also offering enhancements such as NanoBoyAdvance application/x-gameboy-advance-rom - pplication/x-agb-rom + application/x-agb-rom application/x-gba-rom From 1af19385a0144b12c91f4b4abfb237b8b99a309d Mon Sep 17 00:00:00 2001 From: fleroviux Date: Tue, 12 Mar 2024 01:35:34 +0100 Subject: [PATCH 09/21] Update README --- README.md | 2 -- docs/screenshot.png | Bin 67816 -> 0 bytes 2 files changed, 2 deletions(-) delete mode 100644 docs/screenshot.png diff --git a/README.md b/README.md index 7892bf8e..e9d47f42 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ NanoBoyAdvance is a cycle-accurate Game Boy Advance emulator.
    It aims to be as accurate as possible, while also offering enhancements such as improved audio quality.
    -![screenshot1](docs/screenshot.png) - ## Features - Very high compatibility and accuracy (see [Accuracy](#accuracy)) - HQ audio mixer (for games which use Nintendo's MusicPlayer2000 sound engine) diff --git a/docs/screenshot.png b/docs/screenshot.png deleted file mode 100644 index eabe412bca49ed7d02577fbf78e8ffa3b1518783..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67816 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 U@Yf)AYR+qTmLT+TVO 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
    b#NP>D<_m|GY8^iFag6 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

    t*?A9^$2?`EcaMSG2=~Lj~CSh7yE>w+# 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%0AbBCL1 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 From b253847ed5164e7cd0807d6153bdbd926162f99d Mon Sep 17 00:00:00 2001 From: Mireille Date: Thu, 14 Mar 2024 00:09:11 +0100 Subject: [PATCH 10/21] README: fix incorrect test results for misc edge case tests --- docs/ACCURACY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ACCURACY.md b/docs/ACCURACY.md index 6a89566e..bda77d95 100644 --- a/docs/ACCURACY.md +++ b/docs/ACCURACY.md @@ -15,7 +15,7 @@ Carry | 93 | 93 | 93 | 93 | Multiply Long | 72 | 52 | 52 | 52 | 52 | 52 | BIOS math | 615 | 615 | 615 | 615 | 615 | 615 | DMA tests | 1256 | 1256 | 1232 | 1068 | 1212 | 1256 | -Misc Edge Case| 10 | 9 | 4 | 8 | 1 | 4 | +Misc Edge Case| 10 | 10 | 4 | 8 | 1 | 4 | Layer Toggle | 1 | pass | fail | pass | fail | pass | OAM Update | 1 | pass | fail | fail | fail | pass | From 2d1243aed02007083156fd1459f70a898f1dc01b Mon Sep 17 00:00:00 2001 From: GranMinigun <11149380+GranMinigun@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:06:19 +0500 Subject: [PATCH 11/21] Platform: Core: Replace GLEW with glad --- CMakeLists.txt | 2 +- src/platform/core/CMakeLists.txt | 16 ++++++++++++---- .../include/platform/device/ogl_video_device.hpp | 2 +- .../core/src/device/ogl_video_device.cpp | 2 -- src/platform/qt/src/widget/screen.cpp | 11 ++++++++--- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e26c039..98f4154f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.11...3.28) -project(NanoBoyAdvance LANGUAGES CXX) +project(NanoBoyAdvance LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/src/platform/core/CMakeLists.txt b/src/platform/core/CMakeLists.txt index 3a211153..2bc6d8c3 100644 --- a/src/platform/core/CMakeLists.txt +++ b/src/platform/core/CMakeLists.txt @@ -15,12 +15,21 @@ else() endif() find_package(OpenGL REQUIRED) -find_package(GLEW REQUIRED) + +include(FetchContent) +FetchContent_Declare(glad + GIT_REPOSITORY https://github.com/Dav1dde/glad.git + GIT_TAG adc3d7a1d704e099581ca25bc5bbdf728c2db67b # v2.0.5-2-gadc3d7a + SOURCE_SUBDIR cmake +) +FetchContent_MakeAvailable(glad) +glad_add_library(glad_gl_core_33 STATIC + LANGUAGE c REPRODUCIBLE API gl:core=3.3 EXTENSIONS NONE +) if(USE_SYSTEM_TOML11) find_package(toml11 3.7 REQUIRED) else() - include(FetchContent) FetchContent_Declare(toml11 GIT_REPOSITORY https://github.com/ToruNiina/toml11.git GIT_TAG dcfe39a783a94e8d52c885e5883a6fbb21529019 # v3.7.1 @@ -37,7 +46,6 @@ else() set(USE_SYSTEM_ZLIB OFF CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) - include(FetchContent) FetchContent_Declare(unarr GIT_REPOSITORY https://github.com/selmf/unarr.git GIT_TAG b211040df83dee513362cdeb9bd87afa26fd5e38 # v1.1.1 @@ -104,5 +112,5 @@ endif() target_link_libraries(platform-core PRIVATE unarr::unarr - PUBLIC nba toml11::toml11 OpenGL::GL GLEW::GLEW + PUBLIC nba toml11::toml11 OpenGL::GL glad_gl_core_33 ) diff --git a/src/platform/core/include/platform/device/ogl_video_device.hpp b/src/platform/core/include/platform/device/ogl_video_device.hpp index 1b7f3d1b..ebcf1602 100644 --- a/src/platform/core/include/platform/device/ogl_video_device.hpp +++ b/src/platform/core/include/platform/device/ogl_video_device.hpp @@ -8,8 +8,8 @@ #pragma once #include +#include #include -#include #include #include #include diff --git a/src/platform/core/src/device/ogl_video_device.cpp b/src/platform/core/src/device/ogl_video_device.cpp index 3fb65e63..05e6dffa 100644 --- a/src/platform/core/src/device/ogl_video_device.cpp +++ b/src/platform/core/src/device/ogl_video_device.cpp @@ -47,8 +47,6 @@ OGLVideoDevice::~OGLVideoDevice() { } void OGLVideoDevice::Initialize() { - glewInit(); - // Create a fullscreen quad to render to the viewport. glGenVertexArrays(1, &quad_vao); glGenBuffers(1, &quad_vbo); diff --git a/src/platform/qt/src/widget/screen.cpp b/src/platform/qt/src/widget/screen.cpp index b8546474..cc827998 100644 --- a/src/platform/qt/src/widget/screen.cpp +++ b/src/platform/qt/src/widget/screen.cpp @@ -5,10 +5,11 @@ * Refer to the included LICENSE file. */ -#include - #include "widget/screen.hpp" +#include +#include // Has to go after glad. + Screen::Screen( QWidget* parent, std::shared_ptr config @@ -93,8 +94,12 @@ void Screen::UpdateViewport() { ogl_video_device.SetViewport(viewport_x, viewport_y, viewport_width, viewport_height); } +static auto get_proc_address(const char* proc_name) { + return QOpenGLContext::currentContext()->getProcAddress(proc_name); +} + void Screen::initializeGL() { - makeCurrent(); + gladLoadGL(get_proc_address); ogl_video_device.Initialize(); } From ce15d5a78b65ee26c5a7037418de969a84ce0a15 Mon Sep 17 00:00:00 2001 From: GranMinigun <11149380+GranMinigun@users.noreply.github.com> Date: Sun, 10 Mar 2024 21:10:01 +0500 Subject: [PATCH 12/21] CI: Bump actions --- .github/workflows/build.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a017a9a..9b7a9a6b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: run: shell: msys2 {0} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 with: install: make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-glew mingw-w64-x86_64-qt5-static @@ -31,7 +31,7 @@ jobs: mkdir upload cp -r build/bin/qt/{NanoBoyAdvance.exe,config.toml} upload - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: NanoBoyAdvance-win64 path: upload @@ -40,7 +40,7 @@ jobs: build-linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup dependencies run: | sudo apt-get update -qq @@ -55,7 +55,7 @@ jobs: mkdir upload cp -r build/bin/qt/{NanoBoyAdvance,config.toml} upload - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: NanoBoyAdvance-linux path: upload @@ -64,7 +64,7 @@ jobs: build-macOS: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup dependencies env: HOMEBREW_NO_ANALYTICS: 1 @@ -94,7 +94,7 @@ jobs: -ov -format UDBZ \ NanoBoyAdvance.dmg - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: NanoBoyAdvance-${{ runner.os }}-x64 path: NanoBoyAdvance.dmg @@ -103,7 +103,7 @@ jobs: build-macOS-arm: runs-on: macos-14 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup dependencies env: HOMEBREW_NO_ANALYTICS: 1 @@ -133,7 +133,7 @@ jobs: -ov -format UDBZ \ NanoBoyAdvance.dmg - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: NanoBoyAdvance-${{ runner.os }}-arm64 path: NanoBoyAdvance.dmg From 41c5ea63041f0ba4390e1149a7eaa126de1a6dac Mon Sep 17 00:00:00 2001 From: GranMinigun <11149380+GranMinigun@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:37:31 +0500 Subject: [PATCH 13/21] CI: Remove GLEW and add dependencies of glad --- .github/workflows/build.yml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9b7a9a6b..17c739b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 with: - install: make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-glew mingw-w64-x86_64-qt5-static + install: make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-python-jinja mingw-w64-x86_64-python-lxml mingw-w64-x86_64-SDL2 mingw-w64-x86_64-qt5-static - name: Build NanoBoyAdvance run: | cmake \ @@ -20,9 +20,9 @@ jobs: -G"Unix Makefiles" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS="-s" \ + -DPython_EXECUTABLE="$(which python3)" \ -DPLATFORM_QT_STATIC=ON \ -DUSE_STATIC_SDL=ON \ - -DGLEW_USE_STATIC_LIBS=ON \ -DQT5_STATIC_DIR="/c/tools/msys64/mingw64/qt5-static" cd build make -j$NUMBER_OF_PROCESSORS @@ -44,7 +44,7 @@ jobs: - name: Setup dependencies run: | sudo apt-get update -qq - sudo apt-get install -y libsdl2-dev libglew-dev qtbase5-dev + sudo apt-get install -y python3-jinja2 python3-lxml libsdl2-dev qtbase5-dev - name: Build NanoBoyAdvance run: | cmake -Bbuild -DCMAKE_BUILD_TYPE=Release @@ -65,18 +65,22 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Setup dependencies env: HOMEBREW_NO_ANALYTICS: 1 - run: brew install sdl2 glew qt@5 + run: | + brew install sdl2 qt@5 + python3 -m pip install Jinja2 - name: Build NanoBoyAdvance run: | cmake -Bbuild \ -DCMAKE_CXX_FLAGS="-s" \ -DUSE_STATIC_SDL=ON \ - -DGLEW_USE_STATIC_LIBS=ON \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5);$(brew --prefix glew)" \ + -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5)" \ -DMACOS_BUILD_APP_BUNDLE=ON \ -DMACOS_BUNDLE_QT=ON cd build @@ -104,18 +108,22 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Setup dependencies env: HOMEBREW_NO_ANALYTICS: 1 - run: brew install sdl2 glew qt@5 + run: | + brew install sdl2 qt@5 + python3 -m pip install Jinja2 - name: Build NanoBoyAdvance run: | cmake -Bbuild \ -DCMAKE_CXX_FLAGS="-s" \ -DUSE_STATIC_SDL=ON \ - -DGLEW_USE_STATIC_LIBS=ON \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5);$(brew --prefix glew)" \ + -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5)" \ -DMACOS_BUILD_APP_BUNDLE=ON \ -DMACOS_BUNDLE_QT=ON cd build From e17173bdf6911d232dd004c715841bb1282edd27 Mon Sep 17 00:00:00 2001 From: GranMinigun <11149380+GranMinigun@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:12:46 +0500 Subject: [PATCH 14/21] Docs: Update compiling instructions --- docs/COMPILING.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/COMPILING.md b/docs/COMPILING.md index 36462cc5..2706d9b9 100644 --- a/docs/COMPILING.md +++ b/docs/COMPILING.md @@ -4,10 +4,10 @@ NanoBoyAdvance can be compiled on Windows, Linux, and macOS. - Clang or GCC with C++17 support - CMake 3.11 or higher +- Python modules Jinja and (optionally) lxml - OpenGL (usually provided by the operating system) -- SDL2 library -- GLEW library -- Qt5 library +- SDL 2 library +- Qt 5 library ### Source Code @@ -28,13 +28,13 @@ Here is a list of commands for popular distributions and macOS: ##### Arch Linux ```bash -pacman -S cmake sdl2 glew qt5-base +pacman -S cmake python-jinja python-lxml sdl2 qt5-base ``` ##### Ubuntu or other Debian-derived distribution ```bash -apt install cmake libsdl2-dev libglew-dev qtbase5-dev libqt5opengl5-dev +apt install cmake python3-jinja2 python3-lxml libsdl2-dev qtbase5-dev libqt5opengl5-dev ``` ##### macOS @@ -42,14 +42,15 @@ apt install cmake libsdl2-dev libglew-dev qtbase5-dev libqt5opengl5-dev Get [Brew](https://brew.sh/) and run: ``` bash -brew install cmake sdl2 glew qt@5 +brew install cmake python@3 sdl2 qt@5 +python3 -m pip install Jinja2 ``` ##### FreeBSD ```bash su -pkg install cmake git sdl2 glew qt5 qt5-opengl +pkg install cmake git py39-Jinja2 py39-lxml sdl2 qt5 qt5-opengl ``` #### 2. Setup CMake build directory @@ -67,7 +68,7 @@ NOTE: the location and name of the `build` directory is arbitrary. ``` cd /somewhere/on/your/system/NanoBoyAdvance -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5);$(brew --prefix glew)" +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5)" ``` NOTE: the location and name of the `build` directory is arbitrary. @@ -94,7 +95,7 @@ This guide uses [MSYS2](https://www.msys2.org/) to install Mingw-w64 and other d In your MSYS2 command line, run: ```bash -pacman -S make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-glew mingw-w64-x86_64-qt5-static +pacman -S make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-python-jinja mingw-w64-x86_64-python-lxml mingw-w64-x86_64-SDL2 mingw-w64-x86_64-qt5-static ``` #### 2. Setup CMake build directory From 0d3d011c9db4f6a60530ef2a271f3ef538c26422 Mon Sep 17 00:00:00 2001 From: GranMinigun <11149380+GranMinigun@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:49:11 +0500 Subject: [PATCH 15/21] Qt: Replace QOpenGLWidget with QWidget --- .../core/src/device/ogl_video_device.cpp | 7 +- src/platform/qt/CMakeLists.txt | 4 +- src/platform/qt/src/widget/main_window.cpp | 22 ++--- src/platform/qt/src/widget/screen.cpp | 82 ++++++++++++------- src/platform/qt/src/widget/screen.hpp | 18 ++-- 5 files changed, 77 insertions(+), 56 deletions(-) diff --git a/src/platform/core/src/device/ogl_video_device.cpp b/src/platform/core/src/device/ogl_video_device.cpp index 05e6dffa..a4a68711 100644 --- a/src/platform/core/src/device/ogl_video_device.cpp +++ b/src/platform/core/src/device/ogl_video_device.cpp @@ -62,8 +62,7 @@ void OGLVideoDevice::Initialize() { glGenFramebuffers(1, &fbo); glGenTextures(textures.size(), textures.data()); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0.0, 0.0, 0.0, 1.0); ReloadConfig(); } @@ -352,14 +351,16 @@ void OGLVideoDevice::Draw(u32* buffer) { copy_area_y = view_y; copy_area_width = view_width; copy_area_height = view_height; + glBindFramebuffer(GL_READ_FRAMEBUFFER, default_fbo); + glReadBuffer(GL_BACK); } else { glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[output_index], 0); + glReadBuffer(GL_COLOR_ATTACHMENT0); } glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textures[history_index]); - glReadBuffer(GL_COLOR_ATTACHMENT0); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, copy_area_x, copy_area_y, copy_area_width, copy_area_height); } } diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 056419e4..a28a2833 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -47,8 +47,8 @@ option(USE_QT6 "Use Qt 6" OFF) option(PORTABLE_MODE "Portable Mode" ON) if(USE_QT6) - find_package(Qt6 COMPONENTS Core Gui Widgets OpenGL OpenGLWidgets REQUIRED) - set(QT_DEPS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::OpenGL Qt6::OpenGLWidgets) + find_package(Qt6 COMPONENTS Core Gui Widgets OpenGL REQUIRED) + set(QT_DEPS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::OpenGL) else() find_package(Qt5 COMPONENTS Core Gui Widgets OpenGL REQUIRED) set(QT_DEPS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::OpenGL) diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index 9085f4b3..a13d9d92 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "widget/main_window.hpp" @@ -40,10 +41,12 @@ MainWindow::MainWindow( setWindowTitle(base_window_title); setAcceptDrops(true); + config->Load(); + screen = std::make_shared(this, config); setCentralWidget(screen.get()); - - config->Load(); + screen->windowHandle()->create(); + screen->Initialize(); auto menu_bar = new QMenuBar(this); setMenuBar(menu_bar); @@ -694,10 +697,6 @@ void MainWindow::ApplyPauseState() { const bool window_inactive_and_should_pause = !isActiveWindow() && config->window.pause_emulator_when_inactive; const bool paused = pause_action->isChecked() || window_inactive_and_should_pause; - if(!paused) { - screen->SetForceClear(false); - } - emu_thread->SetPause(paused); config->audio_dev->SetPause(paused); } @@ -706,7 +705,9 @@ void MainWindow::Stop() { if(emu_thread->IsRunning()) { core = emu_thread->Stop(); config->audio_dev->Close(); - screen->SetForceClear(true); + + // Clear the screen. + screen->Draw(nullptr); // Clear the list of save state slots: game_loaded = false; @@ -832,13 +833,6 @@ void MainWindow::LoadROM(std::u16string const& path) { core->Reset(); emu_thread->Start(std::move(core)); - /** - * If the the emulator is paused we force-clear the screen to avoid - * presenting the last frame from before pausing, since that could be confusing. - * In the other case we explicitly disable force-clear, because it is currently enabled (due to the call to Stop() at the top). - */ - screen->SetForceClear(pause_action->isChecked()); - UpdateSolarSensorLevel(); UpdateMenuBarVisibility(); } diff --git a/src/platform/qt/src/widget/screen.cpp b/src/platform/qt/src/widget/screen.cpp index cc827998..867c3155 100644 --- a/src/platform/qt/src/widget/screen.cpp +++ b/src/platform/qt/src/widget/screen.cpp @@ -9,26 +9,42 @@ #include #include // Has to go after glad. +#include Screen::Screen( QWidget* parent, std::shared_ptr config -) : QOpenGLWidget(parent) +) : QWidget(parent) , ogl_video_device(config) , config(config) { connect(this, &Screen::RequestDraw, this, &Screen::OnRequestDraw); + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_OpaquePaintEvent); + setAttribute(Qt::WA_PaintOnScreen); + windowHandle()->setSurfaceType(QWindow::OpenGLSurface); } -void Screen::Draw(u32* buffer) { - emit RequestDraw(buffer); +static auto get_proc_address(const char* proc_name) { + return QOpenGLContext::currentContext()->getProcAddress(proc_name); } -void Screen::SetForceClear(bool force_clear) { - this->force_clear = force_clear; - update(); +void Screen::Initialize() { + context = new QOpenGLContext(); + context->create(); + context->makeCurrent(this->windowHandle()); + + gladLoadGL(get_proc_address); + ogl_video_device.Initialize(); + + context->doneCurrent(); +} + +void Screen::Draw(u32* buffer) { + emit RequestDraw(buffer); } void Screen::ReloadConfig() { + context->makeCurrent(this->windowHandle()); ogl_video_device.ReloadConfig(); UpdateViewport(); } @@ -38,7 +54,36 @@ void Screen::OnRequestDraw(u32* buffer) { update(); } +void Screen::paintEvent([[maybe_unused]] QPaintEvent* event) { + Render(); +} + +void Screen::resizeEvent([[maybe_unused]] QResizeEvent* event) { + UpdateViewport(); +} + +void Screen::Render() { + if(!context) { + return; + } + + context->makeCurrent(this->windowHandle()); + glClear(GL_COLOR_BUFFER_BIT); + + if(buffer) { + ogl_video_device.SetDefaultFBO(context->defaultFramebufferObject()); + ogl_video_device.Draw(buffer); + } + + context->swapBuffers(this->windowHandle()); + context->doneCurrent(); +} + void Screen::UpdateViewport() { + if(!context) { + return; + } + auto dpr = devicePixelRatio(); int width = size().width() * dpr; int height = size().height() * dpr; @@ -91,28 +136,7 @@ void Screen::UpdateViewport() { viewport_x = (width - viewport_width ) / 2; viewport_y = (height - viewport_height) / 2; + context->makeCurrent(this->windowHandle()); ogl_video_device.SetViewport(viewport_x, viewport_y, viewport_width, viewport_height); -} - -static auto get_proc_address(const char* proc_name) { - return QOpenGLContext::currentContext()->getProcAddress(proc_name); -} - -void Screen::initializeGL() { - gladLoadGL(get_proc_address); - ogl_video_device.Initialize(); -} - -void Screen::paintGL() { - if(force_clear) { - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); - } else if(buffer != nullptr) { - ogl_video_device.SetDefaultFBO(defaultFramebufferObject()); - ogl_video_device.Draw(buffer); - } -} - -void Screen::resizeGL(int width, int height) { - UpdateViewport(); + context->doneCurrent(); } diff --git a/src/platform/qt/src/widget/screen.hpp b/src/platform/qt/src/widget/screen.hpp index 02c50ac5..ed16feaa 100644 --- a/src/platform/qt/src/widget/screen.hpp +++ b/src/platform/qt/src/widget/screen.hpp @@ -8,19 +8,21 @@ #pragma once #include -#include +#include +#include #include "config.hpp" -struct Screen : QOpenGLWidget, nba::VideoDevice { - Screen( +struct Screen : QWidget, nba::VideoDevice { + explicit Screen( QWidget* parent, std::shared_ptr config ); + void Initialize(); void Draw(u32* buffer) final; - void SetForceClear(bool force_clear); void ReloadConfig(); + QPaintEngine* paintEngine() const override { return nullptr; }; // Silence Qt. signals: void RequestDraw(u32* buffer); @@ -29,19 +31,19 @@ private slots: void OnRequestDraw(u32* buffer); protected: - void initializeGL() override; - void paintGL() override; - void resizeGL(int width, int height) override; + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; private: static constexpr int kGBANativeWidth = 240; static constexpr int kGBANativeHeight = 160; static constexpr float kGBANativeAR = static_cast(kGBANativeWidth) / static_cast(kGBANativeHeight); + void Render(); void UpdateViewport(); u32* buffer = nullptr; - bool force_clear = false; + QOpenGLContext* context = nullptr; nba::OGLVideoDevice ogl_video_device; std::shared_ptr config; From 00110eac1946529c870ef3a9e75cf4b008cb7942 Mon Sep 17 00:00:00 2001 From: GranMinigun <11149380+GranMinigun@users.noreply.github.com> Date: Thu, 14 Mar 2024 18:52:58 +0500 Subject: [PATCH 16/21] Qt: Check version of created OpenGL context --- src/platform/qt/src/main.cpp | 6 +++++ src/platform/qt/src/widget/main_window.cpp | 15 +++++++++-- src/platform/qt/src/widget/main_window.hpp | 1 + src/platform/qt/src/widget/screen.cpp | 31 +++++++++++++++++++--- src/platform/qt/src/widget/screen.hpp | 2 +- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/src/main.cpp b/src/platform/qt/src/main.cpp index 9a4a6b96..e85cc979 100644 --- a/src/platform/qt/src/main.cpp +++ b/src/platform/qt/src/main.cpp @@ -48,6 +48,9 @@ auto create_window(QApplication& app, int argc, char** argv) -> std::unique_ptr< } auto window = std::make_unique(&app); + if(!window->Initialize()) { + return nullptr; + } if(!rom.empty()) { window->LoadROM(rom.u16string()); @@ -104,6 +107,9 @@ int main(int argc, char** argv) { QGuiApplication::setDesktopFileName("io.github.nba_emuNanoBoyAdvance"); auto window = create_window(app, argc, argv); + if(!window) { + return EXIT_FAILURE; + } return app.exec(); } diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index a13d9d92..222e3c17 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -45,8 +45,6 @@ MainWindow::MainWindow( screen = std::make_shared(this, config); setCentralWidget(screen.get()); - screen->windowHandle()->create(); - screen->Initialize(); auto menu_bar = new QMenuBar(this); setMenuBar(menu_bar); @@ -93,6 +91,19 @@ MainWindow::~MainWindow() { delete controller_manager; } +bool MainWindow::Initialize() { + screen->windowHandle()->create(); + if(!screen->Initialize()) { + QMessageBox::critical(this, QApplication::instance()->applicationName(), + tr("Failed to initialize graphics subsystem.\n\n" + "Make sure that your hardware supports OpenGL 3.3 or later, " + "and you have the latest driver version installed.")); + return false; + } + + return true; +} + void MainWindow::CreateFileMenu() { auto file_menu = menuBar()->addMenu(tr("File")); diff --git a/src/platform/qt/src/widget/main_window.hpp b/src/platform/qt/src/widget/main_window.hpp index a5ddead8..32c0be86 100644 --- a/src/platform/qt/src/widget/main_window.hpp +++ b/src/platform/qt/src/widget/main_window.hpp @@ -39,6 +39,7 @@ struct MainWindow : QMainWindow { ~MainWindow(); + bool Initialize(); void LoadROM(std::u16string const& path); signals: diff --git a/src/platform/qt/src/widget/screen.cpp b/src/platform/qt/src/widget/screen.cpp index 867c3155..8c5a8b45 100644 --- a/src/platform/qt/src/widget/screen.cpp +++ b/src/platform/qt/src/widget/screen.cpp @@ -28,15 +28,38 @@ static auto get_proc_address(const char* proc_name) { return QOpenGLContext::currentContext()->getProcAddress(proc_name); } -void Screen::Initialize() { +bool Screen::Initialize() { context = new QOpenGLContext(); - context->create(); - context->makeCurrent(this->windowHandle()); + if(!context->create()) { + delete context; + context = nullptr; + return false; + } + if(context->format().majorVersion() < 3 || + context->format().majorVersion() == 3 && context->format().minorVersion() < 3) { + delete context; + context = nullptr; + return false; + } + + if(!context->makeCurrent(this->windowHandle())) { + delete context; + context = nullptr; + return false; + } + + if(!gladLoadGL(get_proc_address)) { + delete context; + context = nullptr; + return false; + } - gladLoadGL(get_proc_address); ogl_video_device.Initialize(); + UpdateViewport(); context->doneCurrent(); + + return true; } void Screen::Draw(u32* buffer) { diff --git a/src/platform/qt/src/widget/screen.hpp b/src/platform/qt/src/widget/screen.hpp index ed16feaa..b69bfb74 100644 --- a/src/platform/qt/src/widget/screen.hpp +++ b/src/platform/qt/src/widget/screen.hpp @@ -19,7 +19,7 @@ struct Screen : QWidget, nba::VideoDevice { std::shared_ptr config ); - void Initialize(); + bool Initialize(); void Draw(u32* buffer) final; void ReloadConfig(); QPaintEngine* paintEngine() const override { return nullptr; }; // Silence Qt. From 2756a6b3081d13eaba92db12c0558115911babed Mon Sep 17 00:00:00 2001 From: Raphael Robatsch Date: Sun, 31 Mar 2024 23:28:31 +0200 Subject: [PATCH 17/21] GameDB: Japanese Boktai releases have a solar sensor --- src/platform/core/src/game_db.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/core/src/game_db.cpp b/src/platform/core/src/game_db.cpp index ec65bd49..3132cad0 100644 --- a/src/platform/core/src/game_db.cpp +++ b/src/platform/core/src/game_db.cpp @@ -99,9 +99,9 @@ const std::map g_game_db { { "KHPJ", { Config::BackupType::EEPROM_64/*_SENSOR*/, GPIODeviceType::None, false } }, /* Koro Koro Puzzle - Happy Panechu! (Japan) */ { "KYGJ", { Config::BackupType::EEPROM_64/*_SENSOR*/, GPIODeviceType::None, false } }, /* Yoshi no Banyuuinryoku (Japan) */ { "PSAJ", { Config::BackupType::FLASH_128, GPIODeviceType::None, false } }, /* Card e-Reader+ (Japan) */ - { "U3IJ", { Config::BackupType::Detect, GPIODeviceType::RTC, false } }, /* Bokura no Taiyou - Taiyou Action RPG (Japan) */ - { "U32J", { Config::BackupType::Detect, GPIODeviceType::RTC, false } }, /* Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan) */ - { "U33J", { Config::BackupType::Detect, GPIODeviceType::RTC, false } }, /* Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan) */ + { "U3IJ", { Config::BackupType::Detect, GPIODeviceType::RTC | GPIODeviceType::SolarSensor, false } }, /* Bokura no Taiyou - Taiyou Action RPG (Japan) */ + { "U32J", { Config::BackupType::Detect, GPIODeviceType::RTC | GPIODeviceType::SolarSensor, false } }, /* Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan) */ + { "U33J", { Config::BackupType::Detect, GPIODeviceType::RTC | GPIODeviceType::SolarSensor, false } }, /* Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan) */ { "AXPF", { Config::BackupType::FLASH_128, GPIODeviceType::RTC, false } }, /* Pokemon - Version Saphir (France) */ { "AXVF", { Config::BackupType::FLASH_128, GPIODeviceType::RTC, false } }, /* Pokemon - Version Rubis (France) */ { "BPEF", { Config::BackupType::FLASH_128, GPIODeviceType::RTC, false } }, /* Pokemon - Version Emeraude (France) */ From e83ea6cffa07d5b05f53ea273d524ea727018031 Mon Sep 17 00:00:00 2001 From: Mireille Date: Mon, 1 Apr 2024 21:57:30 +0200 Subject: [PATCH 18/21] Docs: Accuracy: update table with Ares v137 results --- docs/ACCURACY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ACCURACY.md b/docs/ACCURACY.md index bda77d95..0f948ef5 100644 --- a/docs/ACCURACY.md +++ b/docs/ACCURACY.md @@ -3,11 +3,11 @@ Comparison of NBA and other emulators on the [mGBA test suite](https://github.com/mgba-emu/suite) by endrift: -Testname | Test Count | NanoBoyAdvance 1.8 | mGBA 0.10.3 | VBA-M 2.1.9 | Ares v135 | SkyEmu V3 | +Testname | Test Count | NanoBoyAdvance 1.8 | mGBA 0.10.3 | VBA-M 2.1.9 | Ares v137 | SkyEmu V3 | --------------|------------|--------------------|-------------|----------------|-----------|------------| Memory | 1552 | 1552 | 1552 | 1426 | 1552 | 1552 | IO read | 130 | 130 | 120 | 100 | 130 | 130 | -Timing | 2020 | 2020 | 1768 | 1024 | 1608 | 2020 | +Timing | 2020 | 2020 | 1768 | 1024 | 1890 | 2020 | Timer | 936 | 936 | 744 | 440 | 465 | 706 | Timer IRQ | 90 | 90 | 70 | 8 | 0 | 90 | Shifter | 140 | 140 | 140 | 132 | 140 | 140 | From d67238773a791a7a2e6b765d036447edce662a1c Mon Sep 17 00:00:00 2001 From: fleroviux Date: Mon, 1 Apr 2024 22:44:56 +0200 Subject: [PATCH 19/21] Qt: fix solar sensor menu regression (fixes #370) --- src/platform/qt/src/widget/main_window.cpp | 4 ++-- src/platform/qt/src/widget/main_window.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index 222e3c17..0688e3dc 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -974,8 +974,8 @@ void MainWindow::SetFullscreen(bool value) { void MainWindow::UpdateSolarSensorLevel() { auto level = config->cartridge.solar_sensor_level; - if(core) { - auto solar_sensor = core->GetROM().GetGPIODevice(); + if(core_not_thread_safe) { + nba::SolarSensor* solar_sensor = core_not_thread_safe->GetROM().GetGPIODevice(); if(solar_sensor) { solar_sensor->SetLightLevel(level); diff --git a/src/platform/qt/src/widget/main_window.hpp b/src/platform/qt/src/widget/main_window.hpp index 32c0be86..8b406ab8 100644 --- a/src/platform/qt/src/widget/main_window.hpp +++ b/src/platform/qt/src/widget/main_window.hpp @@ -143,7 +143,7 @@ private slots: // The PPU debuggers do not access the core in a thread-safe way yet. // So until that is fixed we have to keep a raw pointer around... - nba::CoreBase* core_not_thread_safe; + nba::CoreBase* core_not_thread_safe{}; QAction* pause_action; InputWindow* input_window; From 86ef5791224313f1ea663d07de802b357dea9f95 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Mon, 1 Apr 2024 22:55:41 +0200 Subject: [PATCH 20/21] GPIO: fix broken deserialization of port directions The GPIO port directions were deserialized correctly into the `rd_mask` and `wr_mask` fields, but weren't propagated to the GPIO devices (i.e. RTC, solar sensor). --- src/nba/src/hw/rom/gpio/serialization.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nba/src/hw/rom/gpio/serialization.cpp b/src/nba/src/hw/rom/gpio/serialization.cpp index 2eabb67e..526b1648 100644 --- a/src/nba/src/hw/rom/gpio/serialization.cpp +++ b/src/nba/src/hw/rom/gpio/serialization.cpp @@ -22,6 +22,7 @@ void GPIO::LoadState(SaveState const& state) { for(auto& device : devices) { device->LoadState(state); + device->SetPortDirections(wr_mask); } } From f53171877606cd6916c5b13c022b06bde3e93aa5 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Mon, 1 Apr 2024 23:01:35 +0200 Subject: [PATCH 21/21] Bump version to 1.8.1 --- src/platform/qt/version.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/version.cmake b/src/platform/qt/version.cmake index 71de716b..43dffb52 100644 --- a/src/platform/qt/version.cmake +++ b/src/platform/qt/version.cmake @@ -3,7 +3,7 @@ find_package(Git) set(VERSION_MAJOR 1) set(VERSION_MINOR 8) -set(VERSION_PATCH 0) +set(VERSION_PATCH 1) option(RELEASE_BUILD "Build a release version" OFF)