diff --git a/src/app/emu/audio/apu.cc b/src/app/emu/audio/apu.cc index 71ed9149..81eabd65 100644 --- a/src/app/emu/audio/apu.cc +++ b/src/app/emu/audio/apu.cc @@ -100,17 +100,17 @@ void Apu::RunCycles(uint64_t master_cycles) { if (current_pc == last_pc) { stuck_counter++; if (stuck_counter > 10000 && cycles_ - last_log_cycle > 10000) { - LOG_WARN("APU", "SPC700 stuck at PC=$%04X for %d iterations", + LOG_DEBUG("APU", "SPC700 stuck at PC=$%04X for %d iterations", current_pc, stuck_counter); - LOG_WARN("APU", "Port Status: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", + LOG_DEBUG("APU", "Port Status: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]); - LOG_WARN("APU", "Out Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", + LOG_DEBUG("APU", "Out Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3]); - LOG_WARN("APU", "IPL ROM enabled: %s", rom_readable_ ? "YES" : "NO"); - LOG_WARN("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X", + LOG_DEBUG("APU", "IPL ROM enabled: %s", rom_readable_ ? "YES" : "NO"); + LOG_DEBUG("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X", spc700_.Y, ram[0x00], ram[0x01]); if (!logged_transfer_state && ram[0x00] == 0x19 && ram[0x01] == 0x00) { - LOG_WARN("APU", "Uploaded byte at $0019 = $%02X", ram[0x0019]); + LOG_DEBUG("APU", "Uploaded byte at $0019 = $%02X", ram[0x0019]); logged_transfer_state = true; } last_log_cycle = cycles_; diff --git a/src/app/emu/audio/spc700.cc b/src/app/emu/audio/spc700.cc index ab7b7b79..9b685fe8 100644 --- a/src/app/emu/audio/spc700.cc +++ b/src/app/emu/audio/spc700.cc @@ -1297,7 +1297,7 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { // Advance timers/DSP via idle callbacks, but do not set stopped_. static int sleep_log = 0; if (sleep_log++ < 5) { - LOG_WARN("SPC", "SLEEP executed at PC=$%04X - entering low power mode", PC - 1); + LOG_DEBUG("SPC", "SLEEP executed at PC=$%04X - entering low power mode", PC - 1); } read(PC); for (int i = 0; i < 4; ++i) callbacks_.idle(true); diff --git a/src/app/emu/cpu/cpu.cc b/src/app/emu/cpu/cpu.cc index 6f0814e6..192ac058 100644 --- a/src/app/emu/cpu/cpu.cc +++ b/src/app/emu/cpu/cpu.cc @@ -68,7 +68,7 @@ void Cpu::RunOpcode() { if (stopped_) { static int stopped_log_count = 0; if (stopped_log_count++ < 5) { - LOG_WARN("CPU", "CPU is STOPPED at $%02X:%04X (STP instruction executed)", PB, PC); + LOG_DEBUG("CPU", "CPU is STOPPED at $%02X:%04X (STP instruction executed)", PB, PC); } callbacks_.idle(true); return; @@ -76,7 +76,7 @@ void Cpu::RunOpcode() { if (waiting_) { static int waiting_log_count = 0; if (waiting_log_count++ < 5) { - LOG_WARN("CPU", "CPU is WAITING at $%02X:%04X - irq_wanted=%d nmi_wanted=%d int_flag=%d", + LOG_DEBUG("CPU", "CPU is WAITING at $%02X:%04X - irq_wanted=%d nmi_wanted=%d int_flag=%d", PB, PC, irq_wanted_, nmi_wanted_, GetInterruptFlag()); } if (irq_wanted_ || nmi_wanted_) { @@ -116,7 +116,7 @@ void Cpu::RunOpcode() { // At $88B3: CMP.w APUIO0 - comparing A with F4 // At $88B6: BNE .wait_for_sync_a - branch if not equal uint8_t f4_val = callbacks_.read_byte(0x2140); // Read F4 directly - LOG_WARN("CPU", "Handshake wait: PC=$%04X A(counter)=$%02X F4(SPC)=$%02X X(remain)=$%04X", + LOG_DEBUG("CPU", "Handshake wait: PC=$%04X A(counter)=$%02X F4(SPC)=$%02X X(remain)=$%04X", cur_pc, A & 0xFF, f4_val, X); } } @@ -135,7 +135,7 @@ void Cpu::RunOpcode() { if (PC - 1 == last_stuck_pc) { stuck_count++; if (stuck_count == 100 || stuck_count == 1000 || stuck_count == 10000) { - LOG_WARN("CPU", "Stuck at $%02X:%04X opcode=$%02X for %d iterations", + LOG_DEBUG("CPU", "Stuck at $%02X:%04X opcode=$%02X for %d iterations", PB, PC - 1, opcode, stuck_count); } } else { @@ -1398,16 +1398,16 @@ void Cpu::ExecuteInstruction(uint8_t opcode) { uint8_t dp1 = ReadByte(D + 0x01); uint8_t dp2 = ReadByte(D + 0x02); uint32_t ptr = dp0 | (dp1 << 8) | (dp2 << 16); - LOG_WARN("CPU", "LDA [$00],Y at PC=$%04X: DP=$%04X, [$00]=$%02X:$%04X, Y=$%04X", + LOG_DEBUG("CPU", "LDA [$00],Y at PC=$%04X: DP=$%04X, [$00]=$%02X:$%04X, Y=$%04X", cur_pc, D, dp2, (uint16_t)(dp0 | (dp1 << 8)), Y); - LOG_WARN("CPU", " -> Reading 16-bit value from address $%06X", ptr + Y); + LOG_DEBUG("CPU", " -> Reading 16-bit value from address $%06X", ptr + Y); } uint32_t low = 0; uint32_t high = AdrIly(&low); Lda(low, high); // Log the value read if (PB == 0x00 && (cur_pc == 0x88CF || cur_pc == 0x88D4)) { - LOG_WARN("CPU", " -> Read value A=$%04X", A); + LOG_DEBUG("CPU", " -> Read value A=$%04X", A); } break; } diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 565915d6..46699bbb 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -49,6 +49,7 @@ using ImGui::Text; void Emulator::Run(Rom* rom) { static bool loaded = false; if (!snes_.running() && rom->is_loaded()) { + // Use ARGB8888 format to match PPU output (XBGR layout with format=1) ppu_texture_ = SDL_CreateTexture(core::Renderer::Get().renderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 512, 480); @@ -58,6 +59,9 @@ void Emulator::Run(Rom* rom) { } rom_data_ = rom->vector(); snes_.Init(rom_data_); + + // Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888 texture + wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0); wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60); loaded = true; @@ -65,6 +69,9 @@ void Emulator::Run(Rom* rom) { count_frequency = SDL_GetPerformanceFrequency(); last_count = SDL_GetPerformanceCounter(); time_adder = 0.0; + frame_count_ = 0; + fps_timer_ = 0.0; + current_fps_ = 0.0; } RenderNavBar(); @@ -75,23 +82,43 @@ void Emulator::Run(Rom* rom) { uint64_t current_count = SDL_GetPerformanceCounter(); uint64_t delta = current_count - last_count; last_count = current_count; - float seconds = delta / (float)count_frequency; + double seconds = delta / (double)count_frequency; time_adder += seconds; + + // Cap time accumulation to prevent spiral of death + if (time_adder > wanted_frames_ * 5.0) { + time_adder = wanted_frames_ * 5.0; + } + // allow 2 ms earlier, to prevent skipping due to being just below wanted while (time_adder >= wanted_frames_ - 0.002) { time_adder -= wanted_frames_; if (loaded) { + // Run frame if (turbo_mode_) { snes_.RunFrame(); } snes_.RunFrame(); + + // Track FPS + frame_count_++; + fps_timer_ += wanted_frames_; + if (fps_timer_ >= 1.0) { + current_fps_ = frame_count_ / fps_timer_; + frame_count_ = 0; + fps_timer_ = 0.0; + } + // Generate and queue audio samples snes_.SetSamples(audio_buffer_, wanted_samples_); - if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) { + uint32_t queued = SDL_GetQueuedAudioSize(audio_device_); + // Keep audio buffer filled but not overflowing (max 6 frames worth) + if (queued <= wanted_samples_ * 4 * 6) { SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4); } + // Update PPU texture void* ppu_pixels_; int ppu_pitch_; if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) != @@ -102,6 +129,11 @@ void Emulator::Run(Rom* rom) { snes_.SetPixels(static_cast(ppu_pixels_)); SDL_UnlockTexture(ppu_texture_); } + + // Don't run multiple frames if we're just slightly ahead + if (!turbo_mode_ && time_adder < wanted_frames_) { + break; + } } } @@ -298,6 +330,21 @@ void Emulator::RenderNavBar() { SameLine(); ImGui::Checkbox("Turbo", &turbo_mode_); + + // Display FPS and Audio Status + SameLine(); + ImGui::Text("|"); + SameLine(); + if (current_fps_ > 0) { + ImGui::Text("FPS: %.1f", current_fps_); + } else { + ImGui::Text("FPS: --"); + } + + SameLine(); + uint32_t audio_queued = SDL_GetQueuedAudioSize(audio_device_); + uint32_t audio_frames = audio_queued / (wanted_samples_ * 4); + ImGui::Text("| Audio: %u frames", audio_frames); static bool show_memory_viewer = false; diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index 86d0ee13..d59a57e3 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -82,7 +82,12 @@ class Emulator { // timing uint64_t count_frequency; uint64_t last_count; - float time_adder = 0.0; + double time_adder = 0.0; + + // FPS tracking + int frame_count_ = 0; + double fps_timer_ = 0.0; + double current_fps_ = 0.0; int16_t* audio_buffer_; SDL_AudioDeviceID audio_device_; diff --git a/src/app/emu/video/ppu.h b/src/app/emu/video/ppu.h index aed94d91..fda6afc1 100644 --- a/src/app/emu/video/ppu.h +++ b/src/app/emu/video/ppu.h @@ -258,6 +258,7 @@ class Ppu { // Initialize the frame buffer void Init() { frame_buffer_.resize(256 * 240, 0); + // Set to XBGR format (1) for compatibility with SDL_PIXELFORMAT_ARGB8888 pixelOutputFormat = 1; } @@ -315,6 +316,9 @@ class Ppu { // Returns the pixel data for the current frame const std::vector& GetFrameBuffer() const { return frame_buffer_; } + + // Set pixel output format (0 = BGRX, 1 = XBGR) + void SetPixelFormat(uint8_t format) { pixelOutputFormat = format; } private: int GetPixelForMode7(int x, int layer, bool priority);