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:
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user