diff --git a/.clang-tidy b/.clang-tidy index 540d340..6e7502b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: '*,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-llvmlibc-restrict-system-libc-headers,-llvm-header-guard,-llvm-include-order,-modernize-use-trailing-return-type,-google-readability-todo,-hicpp-signed-bitwise,-readability-identifier-length,-fuchsia-trailing-return,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-macro-usage,-bugprone-macro-parentheses,-fuchsia-default-arguments-calls,-hicpp-uppercase-literal-suffix,-readability-uppercase-literal-suffix,-llvm-namespace-comment,-altera-unroll-loops,-altera-id-dependent-backward-branch,-altera-struct-pack-align,-cppcoreguidelines-pro-type-union-access,-bugprone-implicit-widening-of-multiplication-result,-concurrency-mt-unsafe,-fuchsia-default-arguments-declarations,-bugprone-easily-swappable-parameters,-readability-suspicious-call-argument,-fuchsia-overloaded-operator,-readability-convert-member-functions-to-static' +Checks: '*,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-llvmlibc-restrict-system-libc-headers,-llvm-header-guard,-llvm-include-order,-modernize-use-trailing-return-type,-google-readability-todo,-hicpp-signed-bitwise,-readability-identifier-length,-fuchsia-trailing-return,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-macro-usage,-bugprone-macro-parentheses,-fuchsia-default-arguments-calls,-hicpp-uppercase-literal-suffix,-readability-uppercase-literal-suffix,-llvm-namespace-comment,-altera-unroll-loops,-altera-id-dependent-backward-branch,-altera-struct-pack-align,-cppcoreguidelines-pro-type-union-access,-bugprone-implicit-widening-of-multiplication-result,-concurrency-mt-unsafe,-fuchsia-default-arguments-declarations,-bugprone-easily-swappable-parameters,-readability-suspicious-call-argument,-fuchsia-overloaded-operator,-readability-convert-member-functions-to-static,-fuchsia-statically-constructed-objects' WarningsAsErrors: '*' HeaderFilterRegex: '.*' AnalyzeTemporaryDtors: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 8767d4c..4b87639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.17) +cmake_minimum_required(VERSION 3.25) project(game_boy_emulator) set(CMAKE_CXX_STANDARD 20) @@ -22,6 +22,8 @@ find_package(Boost REQUIRED) include(FetchContent) FetchContent_Declare(nativefiledialog-extended + # Without this clang-tidy would warn about things in the libraries header, but this requires cmake 3.25 + SYSTEM GIT_REPOSITORY https://github.com/btzy/nativefiledialog-extended.git GIT_TAG v1.0.1 ) @@ -34,7 +36,16 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules set(CLANG_FORMAT_EXCLUDE_PATTERNS "src/bindings" ${CMAKE_BINARY_DIR}) find_package(ClangFormat) -include(clang-tidy) +if (CMAKE_CXX_COMPILER_ID STREQUAL Clang) + # The CMAKE_CXX_CLANG_TIDY integration passes all compiler arguments to clang-tidy. For gcc we have a required + # compiler option to increase constexpr evaluation limit (fconstexpr-ops-limit), which clang does not know (it has + # a different option). But this flag leads to cland-diagnostics-error and the workaround would be manually removing + # it from the compilation database. Instead activate clang-tidy only for clang. + message(STATUS "Checking with clang-tidy was enabled") + include(clang-tidy) +else() + message(STATUS "clang-tidy is only active for clang, disabling checks") +endif () add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) diff --git a/src/game-boy-emulator/apu.cpp b/src/game-boy-emulator/apu.cpp index b73273a..3b90293 100644 --- a/src/game-boy-emulator/apu.cpp +++ b/src/game-boy-emulator/apu.cpp @@ -29,8 +29,11 @@ uint8_t Apu::read_byte(uint16_t address) { m_logger->trace("APU read {:04X}", address); switch (address) { case NR52_ADDRESS: - return m_apu_enabled << 7 | m_channel4.is_enabled() << 3 | m_channel3.is_enabled() << 2 - | m_channel2.is_enabled() << 1 | m_channel1.is_enabled(); + return static_cast(m_apu_enabled) << 7 + | static_cast(m_channel4.is_enabled()) << 3 + | static_cast(m_channel3.is_enabled()) << 2 + | static_cast(m_channel2.is_enabled()) << 1 + | static_cast(m_channel1.is_enabled()); case NR51_ADDRESS: return m_sound_panning; case NR50_ADDRESS: @@ -158,6 +161,9 @@ void Apu::cycle_elapsed_callback(size_t cycle_count_m) { m_channel1.do_envelope_sweep(); m_channel2.do_envelope_sweep(); break; + default: + assert(false && "There must be an error since this should be unreachable"); + break; } } m_channel1.tick_wave(); @@ -209,43 +215,6 @@ SampleFrame Apu::get_sample() { return {}; } - struct ChannelSamples { - float ch1 = 0; - float ch2 = 0; - float ch3 = 0; - float ch4 = 0; - }; - auto mix = [&](const ChannelSamples& samples) { - // Pan audio channel samples depending on NR51 - SampleFrame out; - auto options = m_emulator->get_options(); - if (options.apu_channel1_enabled && bitmanip::is_bit_set(m_sound_panning, CH1_LEFT)) { - out.left += samples.ch1; - } - if (options.apu_channel1_enabled && bitmanip::is_bit_set(m_sound_panning, CH1_RIGHT)) { - out.right += samples.ch1; - } - if (options.apu_channel2_enabled && bitmanip::is_bit_set(m_sound_panning, CH2_LEFT)) { - out.left += samples.ch2; - } - if (options.apu_channel2_enabled && bitmanip::is_bit_set(m_sound_panning, CH2_RIGHT)) { - out.right += samples.ch2; - } - if (options.apu_channel3_enabled && bitmanip::is_bit_set(m_sound_panning, CH3_LEFT)) { - out.left += samples.ch3; - } - if (options.apu_channel3_enabled && bitmanip::is_bit_set(m_sound_panning, CH3_RIGHT)) { - out.right += samples.ch3; - } - if (options.apu_channel4_enabled && bitmanip::is_bit_set(m_sound_panning, CH4_LEFT)) { - out.left += samples.ch4; - } - if (options.apu_channel4_enabled && bitmanip::is_bit_set(m_sound_panning, CH4_RIGHT)) { - out.right += samples.ch4; - } - return out; - }; - ChannelSamples samples; if (m_channel1.is_enabled()) { // Channel output is 0..15, DAC converts it to -1..1 @@ -263,15 +232,46 @@ SampleFrame Apu::get_sample() { return mixed_sample; } -uint8_t Apu::get_left_output_volume() const { +float Apu::get_left_output_volume() const { // A value of 0 is treated as volume 1 and a value of 7 is treated as volume 8 (no reduction). - return ((m_master_volume & 0b01110000) >> 4) + 1; + return static_cast(((m_master_volume & 0b01110000) >> 4) + 1); } -uint8_t Apu::get_right_output_volume() const { - return (m_master_volume & 0b111) + 1; +float Apu::get_right_output_volume() const { + return static_cast((m_master_volume & 0b111) + 1); } float Apu::convert_dac(uint8_t value) { - return (value - (15.f / 2.f)) / 7.5f; + return (static_cast(value) - (15.f / 2.f)) / 7.5f; +} + +SampleFrame Apu::mix(const ChannelSamples& samples) { + // Pan audio channel samples depending on NR51 + SampleFrame out; + auto options = m_emulator->get_options(); + if (options.apu_channel1_enabled && bitmanip::is_bit_set(m_sound_panning, CH1_LEFT)) { + out.left += samples.ch1; + } + if (options.apu_channel1_enabled && bitmanip::is_bit_set(m_sound_panning, CH1_RIGHT)) { + out.right += samples.ch1; + } + if (options.apu_channel2_enabled && bitmanip::is_bit_set(m_sound_panning, CH2_LEFT)) { + out.left += samples.ch2; + } + if (options.apu_channel2_enabled && bitmanip::is_bit_set(m_sound_panning, CH2_RIGHT)) { + out.right += samples.ch2; + } + if (options.apu_channel3_enabled && bitmanip::is_bit_set(m_sound_panning, CH3_LEFT)) { + out.left += samples.ch3; + } + if (options.apu_channel3_enabled && bitmanip::is_bit_set(m_sound_panning, CH3_RIGHT)) { + out.right += samples.ch3; + } + if (options.apu_channel4_enabled && bitmanip::is_bit_set(m_sound_panning, CH4_LEFT)) { + out.left += samples.ch4; + } + if (options.apu_channel4_enabled && bitmanip::is_bit_set(m_sound_panning, CH4_RIGHT)) { + out.right += samples.ch4; + } + return out; } diff --git a/src/game-boy-emulator/apu.hpp b/src/game-boy-emulator/apu.hpp index 6524388..8bdfd10 100644 --- a/src/game-boy-emulator/apu.hpp +++ b/src/game-boy-emulator/apu.hpp @@ -40,8 +40,21 @@ class Apu { ClockTimer m_frame_sequencer_timer; // Get right/left volume from NR50 - uint8_t get_left_output_volume() const; - uint8_t get_right_output_volume() const; + [[nodiscard]] float get_left_output_volume() const; + [[nodiscard]] float get_right_output_volume() const; + + struct ChannelSamples { + float ch1 = 0; + float ch2 = 0; + float ch3 = 0; + float ch4 = 0; + }; + /** + * Mix all channels into left/right channel according to sound panning register. + */ + SampleFrame mix(const ChannelSamples& samples); + + // Analog-Digital conversion of value in range 0..15 to value in range -1..1 float convert_dac(uint8_t value); @@ -49,7 +62,7 @@ class Apu { Emulator* m_emulator; public: - Apu(Emulator* emulator); + explicit Apu(Emulator* emulator); uint8_t read_byte(uint16_t address); diff --git a/src/game-boy-emulator/audio.cpp b/src/game-boy-emulator/audio.cpp index 346c145..ac23532 100644 --- a/src/game-boy-emulator/audio.cpp +++ b/src/game-boy-emulator/audio.cpp @@ -26,20 +26,10 @@ Audio::Audio(Emulator& emulator) : audio_spec.format = AUDIO_F32; audio_spec.samples = BUFFER_SIZE; audio_spec.channels = 2; - SDL_AudioSpec obtained{}; spdlog::info("Using audio device {}", device_ids[0]); - m_device_id = SDL_OpenAudioDevice(device_ids[0], SDL_AUDIO_PLAYBACK, &audio_spec, &obtained, 0); - assert(obtained.freq == audio_spec.freq && "Audio device configuration mismatch"); - if (m_device_id == 0) { - spdlog::error("Failed to open audio: {}", SDL_GetError()); - std::exit(EXIT_FAILURE); - } + m_audio_ressource = AudioRessource(device_ids[0], audio_spec); // Start playback on device - SDL_PauseAudioDevice(m_device_id, 0); -} - -Audio::~Audio() { - SDL_CloseAudioDevice(m_device_id); + SDL_PauseAudioDevice(m_audio_ressource.get(), 0); } namespace { @@ -48,7 +38,7 @@ namespace { float calc_volume_log(float volume) { return (std::exp(volume) - 1.f) / (std::exp(1.f) - 1.f); } -} +} // namespace void Audio::callback(SampleFrame sample) { m_resampler.submit_sample_data(sample); @@ -56,11 +46,34 @@ void Audio::callback(SampleFrame sample) { // queuing data involves locks on SDLs side. if (m_resampler.available_samples() >= BUFFER_SIZE) { auto resampled_data = m_resampler.get_resampled_data(); - auto volume = calc_volume_log(m_emulator.get_options().volume) * constants::FIXED_VOLUME_SCALE; + auto volume + = calc_volume_log(m_emulator.get_options().volume) * constants::FIXED_VOLUME_SCALE; std::for_each(resampled_data.begin(), resampled_data.end(), [volume](SampleFrame& sf) { sf.left *= volume; sf.right *= volume; }); - SDL_QueueAudio(m_device_id, resampled_data.data(), static_cast(get_buffersize_bytes(resampled_data))); + SDL_QueueAudio(m_audio_ressource.get(), resampled_data.data(), + static_cast(get_buffersize_bytes(resampled_data))); } } +AudioRessource::operator SDL_AudioDeviceID() const { + return m_device_id; +} + +AudioRessource::AudioRessource(std::string_view device, const SDL_AudioSpec& audio_spec) { + SDL_AudioSpec obtained{}; + SDL_OpenAudioDevice(device.data(), SDL_AUDIO_PLAYBACK, &audio_spec, &obtained, 0); + assert(obtained.freq == audio_spec.freq && "Audio device configuration mismatch"); + if (m_device_id == 0) { + spdlog::error("Failed to open audio: {}", SDL_GetError()); + std::exit(EXIT_FAILURE); + } +} + +AudioRessource::~AudioRessource() { + SDL_CloseAudioDevice(m_device_id); +} + +SDL_AudioDeviceID AudioRessource::get() const { + return m_device_id; +} diff --git a/src/game-boy-emulator/audio.hpp b/src/game-boy-emulator/audio.hpp index 4e671c9..e3e027a 100644 --- a/src/game-boy-emulator/audio.hpp +++ b/src/game-boy-emulator/audio.hpp @@ -6,8 +6,28 @@ #include class Emulator; +/** + * Owner class for the SDL ressource AudioDeviceID. + */ +class AudioRessource { + SDL_AudioDeviceID m_device_id{}; + +public: + explicit operator SDL_AudioDeviceID() const; + [[nodiscard]] SDL_AudioDeviceID get() const; + + AudioRessource() = default; + AudioRessource(std::string_view device, const SDL_AudioSpec& audio_spec); + ~AudioRessource(); + // Since we handle a ressource in this class delete copy operators. + AudioRessource(const AudioRessource&) = delete; + AudioRessource& operator=(const AudioRessource&) = delete; + AudioRessource(AudioRessource&&) = default; + AudioRessource& operator=(AudioRessource&&) = default; +}; + class Audio { - SDL_AudioDeviceID m_device_id = 0; + AudioRessource m_audio_ressource; Resampler m_resampler; template @@ -19,7 +39,5 @@ class Audio { public: explicit Audio(Emulator& emulator); - ~Audio(); - void callback(SampleFrame sample); }; diff --git a/src/game-boy-emulator/audiochannel.hpp b/src/game-boy-emulator/audiochannel.hpp index 4caf6b6..cdbca79 100644 --- a/src/game-boy-emulator/audiochannel.hpp +++ b/src/game-boy-emulator/audiochannel.hpp @@ -6,7 +6,7 @@ class AudioChannel { bool m_enabled = false; -protected: + // Registers of this channel uint8_t m_nrx0 = 0; uint8_t m_nrx1 = 0; @@ -18,11 +18,11 @@ class AudioChannel { void set_enabled(bool enabled); [[nodiscard]] bool is_enabled() const; - virtual uint8_t read_nrx0() const; - virtual uint8_t read_nrx1() const; - virtual uint8_t read_nrx2() const; - virtual uint8_t read_nrx3() const; - virtual uint8_t read_nrx4() const; + [[nodiscard]] virtual uint8_t read_nrx0() const; + [[nodiscard]] virtual uint8_t read_nrx1() const; + [[nodiscard]] virtual uint8_t read_nrx2() const; + [[nodiscard]] virtual uint8_t read_nrx3() const; + [[nodiscard]] virtual uint8_t read_nrx4() const; virtual void set_nrx0(uint8_t value); virtual void set_nrx1(uint8_t value); virtual void set_nrx2(uint8_t value); @@ -32,5 +32,10 @@ class AudioChannel { // Generate a sample in range 0..15 virtual uint8_t get_sample() = 0; + AudioChannel() = default; virtual ~AudioChannel() = default; + AudioChannel(const AudioChannel&) = default; + AudioChannel(AudioChannel&&) = default; + AudioChannel& operator=(const AudioChannel&) = default; + AudioChannel& operator=(AudioChannel&&) = default; }; diff --git a/src/game-boy-emulator/cartridge.cpp b/src/game-boy-emulator/cartridge.cpp index 816d6dc..e2edb28 100644 --- a/src/game-boy-emulator/cartridge.cpp +++ b/src/game-boy-emulator/cartridge.cpp @@ -22,6 +22,7 @@ namespace { const int CARTRIDGE_TYPE_OFFSET = 0x147; const int TITLE_BEGIN = 0x134; const int TITLE_END = 0x143; +const int TITLE_SIZE = TITLE_END - TITLE_BEGIN; } // namespace Cartridge::Cartridge(Emulator* emulator, std::vector rom) : @@ -107,7 +108,9 @@ void Cartridge::sync() { } std::string Cartridge::get_title(const std::vector& rom) const { - auto title = std::string{rom.data() + TITLE_BEGIN, rom.data() + TITLE_END}; + std::string title(TITLE_SIZE, '\0'); + auto s = std::span(rom).subspan(); + std::copy(s.begin(), s.end(), title.begin()); std::erase_if(title, [](auto c) { return !std::isprint(c); }); return title; } diff --git a/src/game-boy-emulator/cartridge.hpp b/src/game-boy-emulator/cartridge.hpp index a13d0ec..aa76f34 100644 --- a/src/game-boy-emulator/cartridge.hpp +++ b/src/game-boy-emulator/cartridge.hpp @@ -17,6 +17,13 @@ class Cartridge { public: Cartridge(Emulator* emulator, std::vector rom); ~Cartridge(); + // Sadly, to keep the forward declarations for Mbc and MemoryMappedFile, a destructor for + // Cartridge is required to be defined. This triggers warnings about the rule of five, to + // silence those we have to manually define the other functions. + Cartridge(const Cartridge&) = delete; + Cartridge& operator=(const Cartridge&) = delete; + Cartridge(Cartridge&&) = default; + Cartridge& operator=(Cartridge&&) = default; [[nodiscard]] uint8_t read_byte(uint16_t address) const; void write_byte(uint16_t address, uint8_t value); diff --git a/src/game-boy-emulator/cpu.cpp b/src/game-boy-emulator/cpu.cpp index bd3a9f4..533a56b 100644 --- a/src/game-boy-emulator/cpu.cpp +++ b/src/game-boy-emulator/cpu.cpp @@ -75,7 +75,7 @@ void Cpu::step() { instructionADD(current_instruction, data); break; case opcodes::InstructionType::ADD_Signed: - instructionADD_Signed(data); + instructionADD_Signed(static_cast(data)); break; case opcodes::InstructionType::NOP: break; @@ -911,7 +911,7 @@ void Cpu::instructionADD(opcodes::Instruction instruction, uint16_t data) { void Cpu::instructionADD_Signed(int8_t data) { set_zero_flag(BitValues::Inactive); set_subtract_flag(BitValues::Inactive); - int result = static_cast(registers.sp + data); + auto result = static_cast(registers.sp + data); set_half_carry_flag(((registers.sp ^ data ^ (result & 0xFFFF)) & 0x10) == 0x10); set_carry_flag(((registers.sp ^ data ^ (result & 0xFFFF)) & 0x100) == 0x100); registers.sp = result; diff --git a/src/game-boy-emulator/dmatransfer.cpp b/src/game-boy-emulator/dmatransfer.cpp index ece3baf..dfcccbf 100644 --- a/src/game-boy-emulator/dmatransfer.cpp +++ b/src/game-boy-emulator/dmatransfer.cpp @@ -3,7 +3,7 @@ #include "memorymap.hpp" OamDmaTransfer::OamDmaTransfer(std::shared_ptr address_bus, - std::span target) : + std::span target) : m_address_bus(std::move(address_bus)), m_target(target) {} void OamDmaTransfer::start_transfer(uint16_t start_address) { @@ -21,7 +21,7 @@ void OamDmaTransfer::callback_cycle() { return; } - auto x = m_address_bus->read_byte(m_start_address + m_counter); + auto x = std::byte{m_address_bus->read_byte(m_start_address + m_counter)}; m_target[m_counter] = x; m_counter++; } diff --git a/src/game-boy-emulator/dmatransfer.hpp b/src/game-boy-emulator/dmatransfer.hpp index be02375..9939101 100644 --- a/src/game-boy-emulator/dmatransfer.hpp +++ b/src/game-boy-emulator/dmatransfer.hpp @@ -13,13 +13,13 @@ class OamDmaTransfer { std::shared_ptr m_address_bus; uint16_t m_start_address = 0; - std::span m_target; + std::span m_target; // Count the number of bytes transferred (Total 160 bytes will be transferred) size_t m_counter = 0; public: explicit OamDmaTransfer(std::shared_ptr address_bus, - std::span target); + std::span target); // Will set the transfer state to active void start_transfer(uint16_t start_address); diff --git a/src/game-boy-emulator/graphics.cpp b/src/game-boy-emulator/graphics.cpp index 0b5e23c..fa1396e 100644 --- a/src/game-boy-emulator/graphics.cpp +++ b/src/game-boy-emulator/graphics.cpp @@ -63,7 +63,7 @@ const std::array& convert_tile_line(uint8_t byte1, uint8_t b } namespace { -std::array PALETTE +const std::array PALETTE = {ColorScreen::White, ColorScreen::LightGray, ColorScreen::DarkGray, ColorScreen::Black}; } diff --git a/src/game-boy-emulator/mbc1.hpp b/src/game-boy-emulator/mbc1.hpp index a0b688b..800ed4a 100644 --- a/src/game-boy-emulator/mbc1.hpp +++ b/src/game-boy-emulator/mbc1.hpp @@ -22,8 +22,8 @@ class Mbc1 : public Mbc { void write_registers(uint16_t address, uint8_t value); void write_values(uint16_t address, uint8_t value); - uint32_t get_address_in_rom(uint16_t address, uint32_t bank_number) const; - uint32_t get_address_in_ram(uint16_t address) const; + [[nodiscard]] uint32_t get_address_in_rom(uint16_t address, uint32_t bank_number) const; + [[nodiscard]] uint32_t get_address_in_ram(uint16_t address) const; public: Mbc1(std::vector rom, std::span ram); diff --git a/src/game-boy-emulator/mbc3.hpp b/src/game-boy-emulator/mbc3.hpp index 7549a81..da0b82e 100644 --- a/src/game-boy-emulator/mbc3.hpp +++ b/src/game-boy-emulator/mbc3.hpp @@ -40,7 +40,7 @@ class Mbc3 : public Mbc { }; // Which register is currently mapped - RtcRegisterValue m_current_rtc_register; + RtcRegisterValue m_current_rtc_register = RtcRegisterValue::RTC_S; void write_registers(uint16_t address, uint8_t value); void write_values(uint16_t address, uint8_t value); diff --git a/src/game-boy-emulator/mbc5.cpp b/src/game-boy-emulator/mbc5.cpp index 773a598..a55ff50 100644 --- a/src/game-boy-emulator/mbc5.cpp +++ b/src/game-boy-emulator/mbc5.cpp @@ -9,7 +9,7 @@ Mbc5::Mbc5(std::vector rom, std::span ram) : - Mbc(rom, ram), + Mbc(std::move(rom), ram), m_required_rom_bits(static_cast( std::ceil(std::log2(get_rom_info().size_bytes)))), m_required_ram_bits(static_cast( @@ -29,9 +29,9 @@ uint8_t Mbc5::read_byte(uint16_t address) const { return get_rom()[address]; } if (memmap::is_in(address, memmap::CartridgeRomBankSwitchable)) { - auto address_bank_begin = get_rom_bank_number() * memmap::CartridgeRomBankSwitchableSize; - auto address_in_bank = address - memmap::CartridgeRomBankSwitchableBegin; - auto address_rom = static_cast(address_bank_begin + address_in_bank); + const size_t address_bank_begin = get_rom_bank_number() * memmap::CartridgeRomBankSwitchableSize; + const size_t address_in_bank = address - memmap::CartridgeRomBankSwitchableBegin; + auto address_rom = address_bank_begin + address_in_bank; address_rom = bitmanip::mask(address_rom, m_required_rom_bits); assert(address_rom < get_rom().size() && "Read to cartridge RAM bank out of bounds"); return get_rom()[address_rom]; @@ -41,9 +41,9 @@ uint8_t Mbc5::read_byte(uint16_t address) const { get_logger()->error("Read from disabled cartridge RAM in Mbc5"); return 0xFF; } - auto address_bank_begin = (m_ram_bank_number & 0x0F) * memmap::CartridgeRamSize; - auto address_in_bank = address - memmap::CartridgeRamBegin; - auto address_in_ram = static_cast(address_bank_begin + address_in_bank); + const size_t address_bank_begin = (m_ram_bank_number & 0x0F) * memmap::CartridgeRamSize; + const size_t address_in_bank = address - memmap::CartridgeRamBegin; + auto address_in_ram = address_bank_begin + address_in_bank; address_in_ram = bitmanip::mask(address_in_ram, m_required_ram_bits); assert(address_in_ram < get_ram().size() && "Read to cartridge RAM bank out of bounds"); return get_ram()[address_in_ram]; @@ -87,9 +87,9 @@ void Mbc5::write_values(uint16_t address, uint8_t value) { return; } - auto address_bank_begin = (m_ram_bank_number & 0x0F) * memmap::CartridgeRamSize; - auto address_in_bank = address - memmap::CartridgeRamBegin; - auto address_in_ram = static_cast(address_bank_begin + address_in_bank); + const size_t address_bank_begin = (m_ram_bank_number & 0x0F) * memmap::CartridgeRamSize; + const size_t address_in_bank = address - memmap::CartridgeRamBegin; + auto address_in_ram = address_bank_begin + address_in_bank; address_in_ram = bitmanip::mask(address_in_ram, m_required_ram_bits); assert(address_in_ram < get_ram().size() && "Read to cartridge RAM bank out of bounds"); get_ram()[address_in_ram] = value; diff --git a/src/game-boy-emulator/ppu.cpp b/src/game-boy-emulator/ppu.cpp index 7533cad..7cc7f5a 100644 --- a/src/game-boy-emulator/ppu.cpp +++ b/src/game-boy-emulator/ppu.cpp @@ -20,9 +20,7 @@ Ppu::Ppu(Emulator* emulator) : m_game_framebuffer(graphics::gb::ColorScreen::White), m_background_framebuffer(graphics::gb::ColorScreen::White), m_window_framebuffer(graphics::gb::ColorScreen::White), - m_oam_dma_transfer(emulator->get_bus(), std::span{ - reinterpret_cast(m_oam_ram.data()), - constants::OAM_DMA_NUM_BYTES}) {} + m_oam_dma_transfer(emulator->get_bus(), std::as_writable_bytes(std::span{m_oam_ram})) {} uint8_t Ppu::read_byte(uint16_t address) { @@ -39,7 +37,8 @@ uint8_t Ppu::read_byte(uint16_t address) { m_logger->error("PPU: OAM read at {:04X} during mode {}", address, magic_enum::enum_name(mode)); } - return reinterpret_cast(m_oam_ram.data())[address - memmap::OamRamBegin]; + return std::to_integer( + std::as_bytes(std::span{m_oam_ram})[address - memmap::OamRamBegin]); } if (memmap::is_in(address, memmap::PpuIoRegisters)) { return m_registers.get_register_value(address); @@ -66,7 +65,8 @@ void Ppu::write_byte(uint16_t address, uint8_t value) { m_logger->error("PPU: Oam write at {:04X} during mode {}", address, magic_enum::enum_name(m_registers.get_mode())); } - reinterpret_cast(m_oam_ram.data())[address - memmap::OamRamBegin] = value; + std::as_writable_bytes(std::span{m_oam_ram})[address - memmap::OamRamBegin] + = std::byte{value}; } else if (memmap::is_in(address, memmap::TileMaps)) { if (m_registers.is_ppu_enabled() && m_registers.get_mode() == PpuMode::PixelTransfer_3) { m_logger->error("PPU: VRAM write at {:04X} during pixel transfer", address); @@ -300,6 +300,24 @@ bool bg_window_over_sprite(const OamEntry& oam_entry) { // Bit7 OBJ-to-BG Priority (0=OBJ Above BG, 1=OBJ behind BG color 1-3) return bitmanip::is_bit_set(oam_entry.m_flags, 7); } + +size_t calculate_pixel_index(const OamEntry& oam_entry, size_t x, size_t y) { + const static graphics::gb::TileIndexMirrorBothAxes tim(8, 8); + const static graphics::gb::TileIndexMirrorHorizontal tih(8, 8); + const static graphics::gb::TileIndexMirrorVertical tiv(8, 8); + const static graphics::gb::TileIndex ti(8, 8); + if (should_mirror_horizontally(oam_entry) && should_mirror_vertically(oam_entry)) { + return tim.pixel_index(x, y); + } + if (should_mirror_horizontally(oam_entry)) { + return tih.pixel_index(x, y); + } + if (should_mirror_vertically(oam_entry)) { + return tiv.pixel_index(x, y); + } + return ti.pixel_index(x, y); +}; + } // namespace void Ppu::draw_sprites_line() { @@ -322,6 +340,9 @@ void Ppu::draw_sprites_line() { visible_sprites.begin(), visible_sprites.end(), [](const OamEntry& a, const OamEntry& b) { return a.m_x_position < b.m_x_position; }); // Reverse order so the objects first in OAM stay (because they have higher priority). + // std::ranges::reverse_view doesn't work in clang 15, so we use the iterator alternative. To + // stop clang-tidy from complaining, disable just that warning. + // NOLINTNEXTLINE(modernize-loop-convert) for (auto oam_entry = visible_sprites.rbegin(); oam_entry != visible_sprites.rend(); ++oam_entry) { // Skip offscreen sprites @@ -335,22 +356,6 @@ void Ppu::draw_sprites_line() { auto palette_bit = bitmanip::is_bit_set(oam_entry->m_flags, 4); auto palette = palette_bit ? obj_palette_1 : obj_palette_0; - auto calculate_pixel_index = [&](size_t x, size_t y) { - const static graphics::gb::TileIndexMirrorBothAxes tim(8, 8); - const static graphics::gb::TileIndexMirrorHorizontal tih(8, 8); - const static graphics::gb::TileIndexMirrorVertical tiv(8, 8); - const static graphics::gb::TileIndex ti(8, 8); - if (should_mirror_horizontally(*oam_entry) && should_mirror_vertically(*oam_entry)) { - return tim.pixel_index(x, y); - } - if (should_mirror_horizontally(*oam_entry)) { - return tih.pixel_index(x, y); - } - if (should_mirror_vertically(*oam_entry)) { - return tiv.pixel_index(x, y); - } - return ti.pixel_index(x, y); - }; // Y position in sprite (0...15) const auto sprite_y = screen_y - (oam_entry->m_y_position - 16); for (unsigned sprite_x = 0; sprite_x < 8; ++sprite_x) { @@ -361,7 +366,8 @@ void Ppu::draw_sprites_line() { // This pixel of the sprite is hidden continue; } - auto pixel_index = calculate_pixel_index(sprite_x, static_cast(sprite_y)); + auto pixel_index + = calculate_pixel_index(*oam_entry, sprite_x, static_cast(sprite_y)); auto pixel_color = tile[pixel_index]; if (pixel_color == graphics::gb::UnmappedColorGb::Color0) { // Color 0 is transparent for sprites, so those pixels are not drawn. @@ -408,6 +414,7 @@ void Ppu::draw_tall_sprites_line() { visible_sprites.begin(), visible_sprites.end(), [](const OamEntry& a, const OamEntry& b) { return a.m_x_position < b.m_x_position; }); // Reverse order so the objects first in OAM stay (because they have higher priority). + // NOLINTNEXTLINE(modernize-loop-convert) for (auto oam_entry = visible_sprites.rbegin(); oam_entry != visible_sprites.rend(); ++oam_entry) { // Skip sprites which are completly offscreen @@ -423,22 +430,6 @@ void Ppu::draw_tall_sprites_line() { auto palette_bit = bitmanip::is_bit_set(oam_entry->m_flags, 4); auto palette = palette_bit ? obj_palette_1 : obj_palette_0; - auto calculate_pixel_index = [&](size_t x, size_t y) { - const static graphics::gb::TileIndexMirrorBothAxes tim(8, 16); - const static graphics::gb::TileIndexMirrorHorizontal tih(8, 16); - const static graphics::gb::TileIndexMirrorVertical tiv(8, 16); - const static graphics::gb::TileIndex ti(8, 16); - if (should_mirror_horizontally(*oam_entry) && should_mirror_vertically(*oam_entry)) { - return tim.pixel_index(x, y); - } - if (should_mirror_horizontally(*oam_entry)) { - return tih.pixel_index(x, y); - } - if (should_mirror_vertically(*oam_entry)) { - return tiv.pixel_index(x, y); - } - return ti.pixel_index(x, y); - }; // Y position in sprite (0...15) const auto sprite_y = screen_y - (oam_entry->m_y_position - 16); for (unsigned sprite_x = 0; sprite_x < 8; ++sprite_x) { @@ -449,7 +440,8 @@ void Ppu::draw_tall_sprites_line() { // This pixel of the sprite is hidden continue; } - auto pixel_index = calculate_pixel_index(sprite_x, static_cast(sprite_y)); + auto pixel_index + = calculate_pixel_index(*oam_entry, sprite_x, static_cast(sprite_y)); auto pixel_color = tile[pixel_index]; if (pixel_color == graphics::gb::UnmappedColorGb::Color0) { // Color 0 is transparent for sprites, so those pixels are not drawn. diff --git a/src/game-boy-emulator/ppu.hpp b/src/game-boy-emulator/ppu.hpp index e54f133..72df998 100644 --- a/src/game-boy-emulator/ppu.hpp +++ b/src/game-boy-emulator/ppu.hpp @@ -5,6 +5,7 @@ #include "constants.h" #include "framebuffer.hpp" #include "dmatransfer.hpp" +#include "bitmanipulation.hpp" class Emulator; namespace spdlog { class logger; @@ -20,8 +21,8 @@ struct OamEntry { uint8_t m_tile_index; uint8_t m_flags; }; -static_assert(sizeof(OamEntry) == 4 && "Padding in OamEntry detected"); -static_assert(alignof(OamEntry) == 1 && "Invalid alignment of OamEntry detected"); +static_assert(sizeof(OamEntry) == 4, "Padding in OamEntry detected"); +static_assert(alignof(OamEntry) == 1, "Invalid alignment of OamEntry detected"); class Ppu { // 0x8000-0x97FFF @@ -74,8 +75,9 @@ class Ppu { void draw_window_debug(); void draw_vram_debug(); - std::span get_sprite_tile(uint8_t tile_index); - std::span get_tall_sprite_tile(uint8_t tile_index); + std::span get_sprite_tile(uint8_t tile_index); + std::span get_tall_sprite_tile(uint8_t tile_index); + std::span get_tile(unsigned block, unsigned index_in_block); enum class TileType { Background, Window }; diff --git a/src/game-boy-emulator/pulsechannel.cpp b/src/game-boy-emulator/pulsechannel.cpp index 247ea21..c8e9964 100644 --- a/src/game-boy-emulator/pulsechannel.cpp +++ b/src/game-boy-emulator/pulsechannel.cpp @@ -4,7 +4,7 @@ #include namespace { -std::array, 4> WAVE_DUTY_CYCLES{ +const std::array, 4> WAVE_DUTY_CYCLES{ std::array{0, 1, 1, 1, 1, 1, 1, 1}, std::array{0, 1, 1, 1, 1, 1, 1, 0}, std::array{0, 0, 1, 1, 0, 0, 1, 1}, std::array{0, 0, 0, 0, 0, 0, 1, 1}}; } diff --git a/src/game-boy-emulator/resampler.hpp b/src/game-boy-emulator/resampler.hpp index e6e14f9..dd81cce 100644 --- a/src/game-boy-emulator/resampler.hpp +++ b/src/game-boy-emulator/resampler.hpp @@ -16,10 +16,10 @@ class Resampler { public: Resampler(int format_in, int format_out, size_t samplerate_in, size_t samplerate_out, - int channels_in, int channels_out) { - m_audio_stream - = SDL_NewAudioStream(format_in, channels_in, static_cast(samplerate_in), - format_out, channels_out, static_cast(samplerate_out)); + int channels_in, int channels_out) : + m_audio_stream(SDL_NewAudioStream(format_in, channels_in, + static_cast(samplerate_in), format_out, + channels_out, static_cast(samplerate_out))) { if (m_audio_stream == nullptr) { spdlog::error("Failed to create SDL_AudioStream: {}", SDL_GetError()); std::exit(EXIT_FAILURE); diff --git a/src/game-boy-emulator/serial_port.hpp b/src/game-boy-emulator/serial_port.hpp index 7e5aedb..c7d7ffc 100644 --- a/src/game-boy-emulator/serial_port.hpp +++ b/src/game-boy-emulator/serial_port.hpp @@ -18,6 +18,10 @@ class SerialPort { public: explicit SerialPort(Emulator* emulator); ~SerialPort(); + SerialPort(const SerialPort&) = default; + SerialPort(SerialPort&&) = default; + SerialPort& operator=(const SerialPort&) = default; + SerialPort& operator=(SerialPort&&) = default; [[nodiscard]] std::string get_buffer() const; diff --git a/src/game-boy-emulator/timer.cpp b/src/game-boy-emulator/timer.cpp index c6d7f42..7cbd1df 100644 --- a/src/game-boy-emulator/timer.cpp +++ b/src/game-boy-emulator/timer.cpp @@ -103,6 +103,7 @@ uint8_t Timer::read_byte(uint16_t address) const { return m_timer_modulo; case ADDRESS_TIMER_CONTROL: return m_timer_control; + default: + throw LogicError(fmt::format("Timer invalid read from {:04X}", address)); } - throw LogicError(fmt::format("Timer invalid read from {:04X}", address)); } diff --git a/src/game-boy-emulator/window.cpp b/src/game-boy-emulator/window.cpp index 4e6d476..ab554ad 100644 --- a/src/game-boy-emulator/window.cpp +++ b/src/game-boy-emulator/window.cpp @@ -3,7 +3,6 @@ #include "graphics.hpp" #include "emulator.hpp" #include "ppu.hpp" -#include "constants.h" #include "joypad.hpp" #include "spdlog/spdlog.h" @@ -12,7 +11,6 @@ #include "imgui_impl_sdlrenderer.h" #include "SDL.h" #include "nfd.hpp" -#include "nfd.h" Window::Window(Emulator& emulator) : m_emulator(emulator), m_logger(spdlog::get("")), m_fps_history(5 * 60, 5 * 60, 0) { @@ -237,8 +235,9 @@ void Window::draw_background() { m_background_image.upload_to_texture(background); auto& options = m_emulator.get_options(); ImGui::Begin("Background", &options.draw_debug_background, ImGuiWindowFlags_NoResize); - auto my_tex_id = static_cast(m_background_image.get_texture()); - ImGui::Image(my_tex_id, ImVec2(background.width(), background.height())); + auto* my_tex_id = static_cast(m_background_image.get_texture()); + ImGui::Image(my_tex_id, ImVec2(static_cast(background.width()), + static_cast(background.height()))); ImGui::End(); } @@ -247,8 +246,9 @@ void Window::draw_sprites() { m_sprites_image.upload_to_texture(sprites); auto& options = m_emulator.get_options(); ImGui::Begin("Sprites", &options.draw_debug_sprites, ImGuiWindowFlags_NoResize); - auto my_tex_id = static_cast(m_sprites_image.get_texture()); - ImGui::Image(my_tex_id, ImVec2(sprites.width(), sprites.height())); + auto* my_tex_id = static_cast(m_sprites_image.get_texture()); + ImGui::Image(my_tex_id, + ImVec2(static_cast(sprites.width()), static_cast(sprites.height()))); ImGui::End(); } @@ -257,8 +257,9 @@ void Window::draw_window() { auto& options = m_emulator.get_options(); ImGui::Begin("Window", &options.draw_debug_window, ImGuiWindowFlags_NoResize); m_window_image.upload_to_texture(window); - auto my_tex_id = static_cast(m_window_image.get_texture()); - ImGui::Image(my_tex_id, ImVec2(window.width(), window.height())); + auto* my_tex_id = static_cast(m_window_image.get_texture()); + ImGui::Image(my_tex_id, + ImVec2(static_cast(window.width()), static_cast(window.height()))); ImGui::End(); } @@ -300,8 +301,9 @@ void Window::vblank_callback() { void Window::draw_game() { ImGui::Begin(m_emulator.get_state().game_title.c_str(), nullptr, ImGuiWindowFlags_NoResize); - auto my_tex_id = static_cast(m_game_image.get_texture()); - ImGui::Image(my_tex_id, ImVec2(m_game_image.width() * 3, m_game_image.height() * 3)); + auto* my_tex_id = static_cast(m_game_image.get_texture()); + ImGui::Image(my_tex_id, ImVec2(static_cast(m_game_image.width() * 3), + static_cast(m_game_image.height() * 3))); ImGui::End(); } @@ -310,24 +312,25 @@ void Window::draw_info(const EmulatorState& state) { ImGui::Begin("Info", &options.draw_info_window, ImGuiWindowFlags_NoResize); auto current_ticks = SDL_GetTicks64(); auto ms_since_last_frame = current_ticks - m_previous_ticks; - auto fps = 0; + auto fps = 0.f; if (ms_since_last_frame != 0) { - fps = 1000 / ms_since_last_frame; + fps = 1000.f / static_cast(ms_since_last_frame); } m_fps_history.push_back(fps); // ImGui requires continuous storage of the data to be plotted. m_fps_history.linearize(); auto avg_fps = std::accumulate(m_fps_history.end() - 5, m_fps_history.end(), 0.) / 5; - ImGui::PlotLines("FPS", &m_fps_history[0], m_fps_history.size(), 0, + ImGui::PlotLines("FPS", &m_fps_history[0], static_cast(m_fps_history.size()), 0, fmt::format("{} FPS", avg_fps).c_str(), 0, 120, ImVec2{500, 100}); m_previous_ticks = current_ticks; for (int i = 0; i < 8; ++i) { const std::string_view key_state = m_pressed_keys[i] ? "Down" : "Up"; auto name = magic_enum::enum_name(magic_enum::enum_value(i)); - ImGui::Text("%s", fmt::format("{}: {}", name, key_state).c_str()); + ImGui::Text("%s", fmt::format("{}: {}", name, key_state).c_str()); // NOLINT } + // NOLINTNEXTLINE ImGui::Text("%s", fmt::format("{} instructions elapsed", state.instructions_executed).c_str()); - ImGui::Text("Speed %d", options.game_speed); + ImGui::Text("Speed %d", options.game_speed); // NOLINT ImGui::End(); } @@ -338,107 +341,127 @@ void Window::draw_vram() { m_tiledata_block2.upload_to_texture(*buffers[2]); auto& options = m_emulator.get_options(); ImGui::Begin("Tile block 0,1,2", &options.draw_debug_tiles, ImGuiWindowFlags_NoResize); - auto my_tex_id = static_cast(m_tiledata_block0.get_texture()); + auto* my_tex_id = static_cast(m_tiledata_block0.get_texture()); const auto scale = 2; - ImGui::Image(my_tex_id, - ImVec2(m_tiledata_block0.width() * scale, m_tiledata_block0.height() * scale)); + ImGui::Image(my_tex_id, ImVec2(static_cast(m_tiledata_block0.width() * scale), + static_cast(m_tiledata_block0.height() * scale))); ImGui::SameLine(); my_tex_id = static_cast(m_tiledata_block1.get_texture()); - ImGui::Image(my_tex_id, - ImVec2(m_tiledata_block1.width() * scale, m_tiledata_block1.height() * scale)); + ImGui::Image(my_tex_id, ImVec2(static_cast(m_tiledata_block1.width() * scale), + static_cast(m_tiledata_block1.height() * scale))); my_tex_id = static_cast(m_tiledata_block2.get_texture()); ImGui::SameLine(); - ImGui::Image(my_tex_id, - ImVec2(m_tiledata_block2.width() * scale, m_tiledata_block2.height() * scale)); + ImGui::Image(my_tex_id, ImVec2(static_cast(m_tiledata_block2.width() * scale), + static_cast(m_tiledata_block2.height() * scale))); ImGui::End(); } +void Window::draw_menubar_file() { + if (ImGui::MenuItem("Load game")) { + NFD::UniquePath out_path; + nfdresult_t const result = NFD::OpenDialog(out_path); + if (result == NFD_OKAY) { + std::filesystem::path const p{out_path.get()}; + m_emulator.get_state().new_rom_file_path = p; + } + } + if (ImGui::MenuItem("Quit")) { + m_done = true; + } + ImGui::EndMenu(); +} + +namespace { -void Window::draw_menubar() { +void draw_menubar_settings_ppu(EmulatorOptions& options) { + if (ImGui::MenuItem("Toggle info window")) { + toggle(options.draw_info_window); + } + if (ImGui::MenuItem("Toggle debug background")) { + toggle(options.draw_debug_background); + } + if (ImGui::MenuItem("Toggle debug window")) { + toggle(options.draw_debug_window); + } + if (ImGui::MenuItem("Toggle debug sprites")) { + toggle(options.draw_debug_sprites); + } + if (ImGui::MenuItem("Toggle debug tile viewer")) { + toggle(options.draw_debug_tiles); + } + ImGui::EndMenu(); +} + +void draw_menubar_settings_speed(EmulatorOptions& options) { + if (ImGui::RadioButton("Speed 1", &options.game_speed, 1)) { + options.fast_forward = false; + options.game_speed = 1; + ImGui::CloseCurrentPopup(); + } + if (ImGui::RadioButton("Speed 2", &options.game_speed, 2)) { + options.fast_forward = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::RadioButton("Speed 3", &options.game_speed, 3)) { + options.fast_forward = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::RadioButton("Speed 4", &options.game_speed, 4)) { + options.fast_forward = true; + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); +} + +void draw_menubar_settings_sound(EmulatorOptions& options, float volume) { + ImGui::SliderFloat("Volume", &volume, 0, 1); + options.volume = volume; + auto label + = fmt::format("Toggle channel 1 (Now {})", options.apu_channel1_enabled ? "ON" : "OFF"); + if (ImGui::MenuItem(label.c_str())) { + toggle(options.apu_channel1_enabled); + } + label = fmt::format("Toggle channel 2 (Now {})", options.apu_channel2_enabled ? "ON" : "OFF"); + if (ImGui::MenuItem(label.c_str())) { + toggle(options.apu_channel2_enabled); + } + label = fmt::format("Toggle channel 3 (Now {})", options.apu_channel2_enabled ? "ON" : "OFF"); + if (ImGui::MenuItem(label.c_str())) { + toggle(options.apu_channel3_enabled); + } + label = fmt::format("Toggle channel 4 (Now {})", options.apu_channel2_enabled ? "ON" : "OFF"); + if (ImGui::MenuItem(label.c_str())) { + toggle(options.apu_channel4_enabled); + } + + ImGui::EndMenu(); +} + +} // namespace + +void Window::draw_menubar_settings() { auto& options = m_emulator.get_options(); + if (ImGui::BeginMenu("PPU debug")) { + draw_menubar_settings_ppu(options); + } + if (ImGui::BeginMenu("Emulation speed")) { + draw_menubar_settings_speed(options); + } + if (ImGui::BeginMenu("Sound")) { + float volume = m_emulator.get_options().volume; + draw_menubar_settings_sound(options, volume); + } + + ImGui::EndMenu(); +} + +void Window::draw_menubar() { if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("Load game")) { - NFD::UniquePath out_path; - nfdresult_t const result = NFD::OpenDialog(out_path); - if (result == NFD_OKAY) { - std::filesystem::path const p{out_path.get()}; - m_emulator.get_state().new_rom_file_path = p; - } - } - if (ImGui::MenuItem("Quit")) { - m_done = true; - } - ImGui::EndMenu(); + draw_menubar_file(); } if (ImGui::BeginMenu("Settings")) { - if (ImGui::BeginMenu("PPU debug")) { - if (ImGui::MenuItem("Toggle info window")) { - toggle(options.draw_info_window); - } - if (ImGui::MenuItem("Toggle debug background")) { - toggle(options.draw_debug_background); - } - if (ImGui::MenuItem("Toggle debug window")) { - toggle(options.draw_debug_window); - } - if (ImGui::MenuItem("Toggle debug sprites")) { - toggle(options.draw_debug_sprites); - } - if (ImGui::MenuItem("Toggle debug tile viewer")) { - toggle(options.draw_debug_tiles); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Emulation speed")) { - if (ImGui::RadioButton("Speed 1", &options.game_speed, 1)) { - options.fast_forward = false; - options.game_speed = 1; - ImGui::CloseCurrentPopup(); - } - if (ImGui::RadioButton("Speed 2", &options.game_speed, 2)) { - options.fast_forward = true; - ImGui::CloseCurrentPopup(); - } - if (ImGui::RadioButton("Speed 3", &options.game_speed, 3)) { - options.fast_forward = true; - ImGui::CloseCurrentPopup(); - } - if (ImGui::RadioButton("Speed 4", &options.game_speed, 4)) { - options.fast_forward = true; - ImGui::CloseCurrentPopup(); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Sound")) { - float volume = m_emulator.get_options().volume; - ImGui::SliderFloat("Volume", &volume, 0, 1); - options.volume = volume; - auto label = fmt::format("Toggle channel 1 (Now {})", - options.apu_channel1_enabled ? "ON" : "OFF"); - if (ImGui::MenuItem(label.c_str())) { - toggle(options.apu_channel1_enabled); - } - label = fmt::format("Toggle channel 2 (Now {})", - options.apu_channel2_enabled ? "ON" : "OFF"); - if (ImGui::MenuItem(label.c_str())) { - toggle(options.apu_channel2_enabled); - } - label = fmt::format("Toggle channel 3 (Now {})", - options.apu_channel2_enabled ? "ON" : "OFF"); - if (ImGui::MenuItem(label.c_str())) { - toggle(options.apu_channel3_enabled); - } - label = fmt::format("Toggle channel 4 (Now {})", - options.apu_channel2_enabled ? "ON" : "OFF"); - if (ImGui::MenuItem(label.c_str())) { - toggle(options.apu_channel4_enabled); - } - - ImGui::EndMenu(); - } - - ImGui::EndMenu(); + draw_menubar_settings(); } ImGui::EndMainMenuBar(); } diff --git a/src/game-boy-emulator/window.hpp b/src/game-boy-emulator/window.hpp index fb33ba4..658a5e0 100644 --- a/src/game-boy-emulator/window.hpp +++ b/src/game-boy-emulator/window.hpp @@ -46,6 +46,9 @@ class Window { void handle_user_keyboard_input(const SDL_Event& event, const std::shared_ptr& joypad); + void draw_menubar_file(); + void draw_menubar_settings(); + public: explicit Window(Emulator& emulator); Window(const Window&) = delete; diff --git a/src/main.cpp b/src/main.cpp index 1cdcd90..126b8c7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,7 @@ int main(int argc, char** argv) { // NOLINT } auto rom_path = std::filesystem::absolute(program.get("rom")); - Emulator emulator({false}); + Emulator emulator({}); if (boot_rom_path.has_value()) { emulator.load_boot_game(boot_rom_path.value(), rom_path); } else { @@ -60,7 +60,7 @@ int main(int argc, char** argv) { // NOLINT // and overwrote the existing object, the components would store the stack address of // the temporary object which would be invalid after leaving this block. // TODO Improve this. - new (&emulator) Emulator({false}); + new (&emulator) Emulator({}); emulator.load_game(path); emulator.set_draw_function([&]() { window.vblank_callback(); }); }