feat: Implement FPS Tracking and Audio Status Display in Emulator

- Added frame rate tracking to the emulator, calculating and displaying current FPS in the UI.
- Enhanced audio management by monitoring queued audio frames and ensuring the audio buffer is filled appropriately.
- Updated texture handling to align with the PPU output format, improving visual consistency.
- Refactored timing management to cap time accumulation, preventing performance issues during frame processing.
This commit is contained in:
scawful
2025-10-06 20:01:10 -04:00
parent e41ea0df46
commit fdb817987f
6 changed files with 73 additions and 17 deletions

View File

@@ -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_;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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<uint8_t*>(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;

View File

@@ -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_;

View File

@@ -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<uint8_t>& 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);