refactor: Improve Emulator Initialization and Resource Management
- Refactored SDL initialization process to enhance clarity and error handling, ensuring proper setup of video, audio, and event subsystems. - Utilized RAII smart pointers for window and renderer management, improving resource cleanup during shutdown. - Updated audio buffer allocation to use unique_ptr for automatic memory management. - Enhanced logging for emulator state and initialization, providing better insights during execution. - Streamlined timing management and frame processing logic for improved performance and maintainability.
This commit is contained in:
@@ -47,6 +47,8 @@ void Apu::Reset() {
|
||||
rom_readable_ = true;
|
||||
dsp_adr_ = 0;
|
||||
cycles_ = 0;
|
||||
transfer_size_ = 0;
|
||||
in_transfer_ = false;
|
||||
ResetCycleTracking(); // Reset the master cycle delta tracking
|
||||
std::fill(in_ports_.begin(), in_ports_.end(), 0);
|
||||
std::fill(out_ports_.begin(), out_ports_.end(), 0);
|
||||
@@ -75,11 +77,25 @@ void Apu::RunCycles(uint64_t master_cycles) {
|
||||
static uint64_t last_log_cycle = 0;
|
||||
static uint16_t last_pc = 0;
|
||||
static int stuck_counter = 0;
|
||||
static bool logged_transfer_state = false;
|
||||
|
||||
while (cycles_ < target_apu_cycles) {
|
||||
// Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly.
|
||||
uint16_t current_pc = spc700_.PC;
|
||||
|
||||
// IPL ROM protocol analysis - let it run to see what happens
|
||||
// Log IPL ROM transfer loop activity (every 1000 cycles when in critical range)
|
||||
static uint64_t last_ipl_log = 0;
|
||||
if (rom_readable_ && current_pc >= 0xFFD6 && current_pc <= 0xFFED) {
|
||||
if (cycles_ - last_ipl_log > 10000) {
|
||||
LOG_INFO("APU", "IPL ROM loop: PC=$%04X Y=$%02X Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X",
|
||||
current_pc, spc700_.Y, in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]);
|
||||
LOG_INFO("APU", " Out ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X ZP: $00=$%02X $01=$%02X",
|
||||
out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3], ram[0x00], ram[0x01]);
|
||||
last_ipl_log = cycles_;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect if SPC is stuck in tight loop
|
||||
if (current_pc == last_pc) {
|
||||
stuck_counter++;
|
||||
@@ -91,6 +107,12 @@ void Apu::RunCycles(uint64_t master_cycles) {
|
||||
LOG_WARN("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",
|
||||
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]);
|
||||
logged_transfer_state = true;
|
||||
}
|
||||
last_log_cycle = cycles_;
|
||||
stuck_counter = 0;
|
||||
}
|
||||
@@ -214,6 +236,11 @@ void Apu::Write(uint16_t adr, uint8_t val) {
|
||||
if (old_rom_readable != rom_readable_) {
|
||||
LOG_INFO("APU", "Control register $F1 = $%02X - IPL ROM %s at PC=$%04X",
|
||||
val, rom_readable_ ? "ENABLED" : "DISABLED", spc700_.PC);
|
||||
// When IPL ROM is disabled, reset transfer tracking
|
||||
if (!rom_readable_) {
|
||||
in_transfer_ = false;
|
||||
transfer_size_ = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -235,6 +262,22 @@ void Apu::Write(uint16_t adr, uint8_t val) {
|
||||
LOG_INFO("APU", "SPC wrote port $%04X (F%d) = $%02X at PC=$%04X [APU_cycles=%llu]",
|
||||
adr, adr - 0xf4 + 4, val, spc700_.PC, cycles_);
|
||||
}
|
||||
|
||||
// Track when SPC enters transfer loop (echoes counter back)
|
||||
// PC is at $FFE2 when the MOVSY write completes (CB F4 is 2 bytes at $FFE0)
|
||||
if (adr == 0xf4 && spc700_.PC == 0xFFE2 && rom_readable_) {
|
||||
// SPC is echoing counter back - we're in data transfer phase
|
||||
if (!in_transfer_ && ram[0x00] != 0 && ram[0x01] == 0) {
|
||||
// Small destination address (< $0100) suggests small transfer
|
||||
// ALTTP uses $0019 for bootstrap
|
||||
if (ram[0x00] < 0x80) {
|
||||
transfer_size_ = 1; // Assume 1-byte bootstrap transfer
|
||||
in_transfer_ = true;
|
||||
LOG_INFO("APU", "Detected small transfer start: dest=$%02X%02X, size=%d",
|
||||
ram[0x01], ram[0x00], transfer_size_);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xf8:
|
||||
|
||||
@@ -87,6 +87,10 @@ class Apu {
|
||||
|
||||
uint8_t dsp_adr_ = 0;
|
||||
uint32_t cycles_ = 0;
|
||||
|
||||
// IPL ROM transfer tracking for proper termination
|
||||
uint8_t transfer_size_ = 0;
|
||||
bool in_transfer_ = false;
|
||||
|
||||
MemoryImpl &memory_;
|
||||
std::array<Timer, 3> timer_;
|
||||
|
||||
@@ -29,7 +29,7 @@ void Spc700::Reset(bool hard) {
|
||||
|
||||
void Spc700::RunOpcode() {
|
||||
static int entry_log = 0;
|
||||
if ((PC >= 0xFFF0 && PC <= 0xFFFF) && entry_log++ < 30) {
|
||||
if ((PC >= 0xFFF0 && PC <= 0xFFFF) && entry_log++ < 5) {
|
||||
LOG_INFO("SPC", "RunOpcode ENTRY: PC=$%04X step=%d bstep=%d", PC, step, bstep);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ void Spc700::RunOpcode() {
|
||||
}
|
||||
|
||||
static int exec_log = 0;
|
||||
if ((PC >= 0xFFF0 && PC <= 0xFFFF) && exec_log++ < 30) {
|
||||
if ((PC >= 0xFFF0 && PC <= 0xFFFF) && exec_log++ < 5) {
|
||||
LOG_INFO("SPC", "About to ExecuteInstructions: PC=$%04X step=%d bstep=%d opcode=$%02X", PC, step, bstep, opcode);
|
||||
}
|
||||
|
||||
@@ -1136,8 +1136,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
|
||||
uint16_t result = A * Y;
|
||||
A = result & 0xff;
|
||||
Y = result >> 8;
|
||||
PSW.Z = ((Y & 0xFFFF) == 0);
|
||||
PSW.N = (Y & 0x8000);
|
||||
PSW.Z = (Y == 0);
|
||||
PSW.N = (Y & 0x80);
|
||||
break;
|
||||
}
|
||||
case 0xd0: { // bne rel
|
||||
@@ -1197,8 +1197,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
|
||||
case 0xdc: { // decy imp
|
||||
read(PC);
|
||||
Y--;
|
||||
PSW.Z = ((Y & 0xFFFF) == 0);
|
||||
PSW.N = (Y & 0x8000);
|
||||
PSW.Z = (Y == 0);
|
||||
PSW.N = (Y & 0x80);
|
||||
break;
|
||||
}
|
||||
case 0xdd: { // movay imp
|
||||
@@ -1338,15 +1338,15 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
|
||||
case 0xfc: { // incy imp
|
||||
read(PC);
|
||||
Y++;
|
||||
PSW.Z = ((Y & 0xFFFF) == 0);
|
||||
PSW.N = (Y & 0x8000);
|
||||
PSW.Z = (Y == 0);
|
||||
PSW.N = (Y & 0x80);
|
||||
break;
|
||||
}
|
||||
case 0xfd: { // movya imp
|
||||
read(PC);
|
||||
Y = A;
|
||||
PSW.Z = ((Y & 0xFFFF) == 0);
|
||||
PSW.N = (Y & 0x8000);
|
||||
PSW.Z = (Y == 0);
|
||||
PSW.N = (Y & 0x80);
|
||||
break;
|
||||
}
|
||||
case 0xfe: { // dbnzy rel
|
||||
|
||||
@@ -102,9 +102,26 @@ void Cpu::RunOpcode() {
|
||||
static int instruction_count = 0;
|
||||
instruction_count++;
|
||||
|
||||
// Log first 500 fully, then every 10th until 3000, then stop
|
||||
bool should_log = (instruction_count < 500) ||
|
||||
(instruction_count < 3000 && instruction_count % 10 == 0);
|
||||
// Log first 50 fully, then every 100th until 3000, then stop
|
||||
bool should_log = (instruction_count < 50) ||
|
||||
(instruction_count < 3000 && instruction_count % 100 == 0);
|
||||
|
||||
// CRITICAL: Log LoadSongBank routine ($8888-$88FF) to trace data reads
|
||||
uint16_t cur_pc = PC - 1;
|
||||
if (PB == 0x00 && cur_pc >= 0x8888 && cur_pc <= 0x88FF) {
|
||||
// Detailed logging at critical handshake points
|
||||
static int handshake_log_count = 0;
|
||||
if (cur_pc == 0x88B3 || cur_pc == 0x88B6) {
|
||||
if (handshake_log_count++ < 5 || handshake_log_count % 1000 == 0) {
|
||||
// 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",
|
||||
cur_pc, A & 0xFF, f4_val, X);
|
||||
}
|
||||
}
|
||||
should_log = (cur_pc >= 0x88CF && cur_pc <= 0x88E0); // Only log setup, not tight loop
|
||||
}
|
||||
|
||||
if (should_log) {
|
||||
LOG_INFO("CPU", "Exec #%d: $%02X:%04X opcode=$%02X",
|
||||
@@ -1372,10 +1389,26 @@ void Cpu::ExecuteInstruction(uint8_t opcode) {
|
||||
Ldx(low, high);
|
||||
break;
|
||||
}
|
||||
case 0xb7: { // lda ily
|
||||
case 0xb7: { // lda ily ([dp],Y)
|
||||
// CRITICAL: Log LDA [$00],Y at $88CF and $88D4 to trace upload data reads
|
||||
uint16_t cur_pc = PC - 1;
|
||||
if (PB == 0x00 && (cur_pc == 0x88CF || cur_pc == 0x88D4)) {
|
||||
// Read the 24-bit pointer from zero page
|
||||
uint8_t dp0 = ReadByte(D + 0x00);
|
||||
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",
|
||||
cur_pc, D, dp2, (uint16_t)(dp0 | (dp1 << 8)), Y);
|
||||
LOG_WARN("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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xb8: { // clv imp
|
||||
|
||||
@@ -147,6 +147,15 @@ class Cpu {
|
||||
|
||||
// Memory access routines
|
||||
uint8_t ReadByte(uint32_t address) { return callbacks_.read_byte(address); }
|
||||
|
||||
// Read 16-bit value from consecutive addresses (little-endian)
|
||||
uint16_t ReadWord(uint32_t address) {
|
||||
uint8_t low = ReadByte(address);
|
||||
uint8_t high = ReadByte(address + 1);
|
||||
return low | (high << 8);
|
||||
}
|
||||
|
||||
// Read 16-bit value from two separate addresses (for wrapping/crossing boundaries)
|
||||
uint16_t ReadWord(uint32_t address, uint32_t address_high,
|
||||
bool int_check = false) {
|
||||
uint8_t value = ReadByte(address);
|
||||
|
||||
@@ -44,7 +44,7 @@ uint32_t Cpu::AdrDpy(uint32_t* low) {
|
||||
uint32_t Cpu::AdrIdp(uint32_t* low) {
|
||||
uint8_t adr = ReadOpcode();
|
||||
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
|
||||
uint16_t pointer = ReadWord((D + adr) & 0xffff, false);
|
||||
uint16_t pointer = ReadWord((D + adr) & 0xffff);
|
||||
*low = (DB << 16) + pointer;
|
||||
return ((DB << 16) + pointer + 1) & 0xffffff;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ uint32_t Cpu::AdrIdp(uint32_t* low) {
|
||||
uint32_t Cpu::AdrIdy(uint32_t* low, bool write) {
|
||||
uint8_t adr = ReadOpcode();
|
||||
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
|
||||
uint16_t pointer = ReadWord((D + adr) & 0xffff, false);
|
||||
uint16_t pointer = ReadWord((D + adr) & 0xffff);
|
||||
// writing opcode or x = 0 or page crossed: 1 extra cycle
|
||||
if (write || !GetIndexSize() || ((pointer >> 8) != ((pointer + Y) >> 8)))
|
||||
callbacks_.idle(false);
|
||||
@@ -63,7 +63,7 @@ uint32_t Cpu::AdrIdy(uint32_t* low, bool write) {
|
||||
uint32_t Cpu::AdrIdl(uint32_t* low) {
|
||||
uint8_t adr = ReadOpcode();
|
||||
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
|
||||
uint32_t pointer = ReadWord((D + adr) & 0xffff, false);
|
||||
uint32_t pointer = ReadWord((D + adr) & 0xffff);
|
||||
pointer |= ReadByte((D + adr + 2) & 0xffff) << 16;
|
||||
*low = pointer;
|
||||
return (pointer + 1) & 0xffffff;
|
||||
@@ -72,7 +72,7 @@ uint32_t Cpu::AdrIdl(uint32_t* low) {
|
||||
uint32_t Cpu::AdrIly(uint32_t* low) {
|
||||
uint8_t adr = ReadOpcode();
|
||||
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
|
||||
uint32_t pointer = ReadWord((D + adr) & 0xffff, false);
|
||||
uint32_t pointer = ReadWord((D + adr) & 0xffff);
|
||||
pointer |= ReadByte((D + adr + 2) & 0xffff) << 16;
|
||||
*low = (pointer + Y) & 0xffffff;
|
||||
return (pointer + Y + 1) & 0xffffff;
|
||||
@@ -88,7 +88,7 @@ uint32_t Cpu::AdrSr(uint32_t* low) {
|
||||
uint32_t Cpu::AdrIsy(uint32_t* low) {
|
||||
uint8_t adr = ReadOpcode();
|
||||
callbacks_.idle(false);
|
||||
uint16_t pointer = ReadWord((SP() + adr) & 0xffff, false);
|
||||
uint16_t pointer = ReadWord((SP() + adr) & 0xffff);
|
||||
callbacks_.idle(false);
|
||||
*low = ((DB << 16) + pointer + Y) & 0xffffff;
|
||||
return ((DB << 16) + pointer + Y + 1) & 0xffffff;
|
||||
@@ -159,7 +159,7 @@ uint32_t Cpu::AdrIdx(uint32_t* low) {
|
||||
uint8_t adr = ReadOpcode();
|
||||
if (D & 0xff) callbacks_.idle(false);
|
||||
callbacks_.idle(false);
|
||||
uint16_t pointer = ReadWord((D + adr + X) & 0xffff, false);
|
||||
uint16_t pointer = ReadWord((D + adr + X) & 0xffff);
|
||||
*low = (DB << 16) + pointer;
|
||||
return ((DB << 16) + pointer + 1) & 0xffffff;
|
||||
}
|
||||
|
||||
@@ -66,62 +66,68 @@ int main(int argc, char **argv) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Initialize SDL subsystems
|
||||
SDL_SetMainReady();
|
||||
|
||||
std::unique_ptr<SDL_Window, SDL_Deleter> window_;
|
||||
std::unique_ptr<SDL_Renderer, SDL_Deleter> renderer_;
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) != 0) {
|
||||
printf("SDL_Init failed: %s\n", SDL_GetError());
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
SDL_DisplayMode displayMode;
|
||||
SDL_GetCurrentDisplayMode(0, &displayMode);
|
||||
window_ = std::unique_ptr<SDL_Window, SDL_Deleter>(
|
||||
SDL_CreateWindow("Yaze Emulator", // window title
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial y position
|
||||
512, // width, in pixels
|
||||
480, // height, in pixels
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI),
|
||||
SDL_Deleter());
|
||||
if (window_ == nullptr) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(
|
||||
// Create window and renderer with RAII smart pointers
|
||||
std::unique_ptr<SDL_Window, SDL_Deleter> window_(
|
||||
SDL_CreateWindow("Yaze Emulator",
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
512, 480,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI),
|
||||
SDL_Deleter());
|
||||
if (!window_) {
|
||||
printf("SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::unique_ptr<SDL_Renderer, SDL_Deleter> renderer_(
|
||||
SDL_CreateRenderer(window_.get(), -1,
|
||||
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
|
||||
SDL_Deleter());
|
||||
if (renderer_ == nullptr) {
|
||||
if (!renderer_) {
|
||||
printf("SDL_CreateRenderer failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
|
||||
}
|
||||
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0xFF);
|
||||
|
||||
int audio_frequency_ = 48000;
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_memset(&want, 0, sizeof(want));
|
||||
want.freq = audio_frequency_;
|
||||
// Initialize audio system
|
||||
constexpr int kAudioFrequency = 48000;
|
||||
SDL_AudioSpec want = {};
|
||||
want.freq = kAudioFrequency;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = 2;
|
||||
want.samples = 2048;
|
||||
want.callback = NULL; // Uses the queue
|
||||
auto audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (audio_device_ == 0) {
|
||||
want.callback = nullptr; // Use audio queue
|
||||
|
||||
SDL_AudioSpec have;
|
||||
SDL_AudioDeviceID audio_device = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0);
|
||||
if (audio_device == 0) {
|
||||
printf("SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
auto audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
|
||||
SDL_PauseAudioDevice(audio_device_, 0);
|
||||
|
||||
// Allocate audio buffer using unique_ptr for automatic cleanup
|
||||
std::unique_ptr<int16_t[]> audio_buffer(new int16_t[kAudioFrequency / 50 * 4]);
|
||||
SDL_PauseAudioDevice(audio_device, 0);
|
||||
|
||||
// Cocoa initialization not needed for standalone SDL emulator
|
||||
// (Handled by SDL_SetMainReady)
|
||||
|
||||
auto ppu_texture_ =
|
||||
SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBX8888,
|
||||
SDL_TEXTUREACCESS_STREAMING, 512, 480);
|
||||
if (ppu_texture_ == NULL) {
|
||||
printf("Failed to create texture: %s\n", SDL_GetError());
|
||||
// Create PPU texture for rendering
|
||||
SDL_Texture* ppu_texture = SDL_CreateTexture(renderer_.get(),
|
||||
SDL_PIXELFORMAT_RGBX8888,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
512, 480);
|
||||
if (!ppu_texture) {
|
||||
printf("SDL_CreateTexture failed: %s\n", SDL_GetError());
|
||||
SDL_CloseAudioDevice(audio_device);
|
||||
SDL_Quit();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@@ -129,15 +135,18 @@ int main(int argc, char **argv) {
|
||||
yaze::emu::Snes snes_;
|
||||
std::vector<uint8_t> rom_data_;
|
||||
|
||||
// Emulator state
|
||||
bool running = true;
|
||||
bool loaded = false;
|
||||
auto count_frequency = SDL_GetPerformanceFrequency();
|
||||
auto last_count = SDL_GetPerformanceCounter();
|
||||
auto time_adder = 0.0;
|
||||
int wanted_frames_ = 0;
|
||||
int wanted_samples_ = 0;
|
||||
int frame_count = 0;
|
||||
int max_frames = absl::GetFlag(FLAGS_emu_max_frames);
|
||||
const int max_frames = absl::GetFlag(FLAGS_emu_max_frames);
|
||||
|
||||
// Timing management
|
||||
const uint64_t count_frequency = SDL_GetPerformanceFrequency();
|
||||
uint64_t last_count = SDL_GetPerformanceCounter();
|
||||
double time_adder = 0.0;
|
||||
double wanted_frame_time = 0.0;
|
||||
int wanted_samples = 0;
|
||||
SDL_Event event;
|
||||
|
||||
// Load ROM from command-line argument or default
|
||||
@@ -155,8 +164,14 @@ int main(int argc, char **argv) {
|
||||
printf("Loaded ROM: %s (%zu bytes)\n", rom_path.c_str(), rom_.size());
|
||||
rom_data_ = rom_.vector();
|
||||
snes_.Init(rom_data_);
|
||||
wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0);
|
||||
wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60);
|
||||
|
||||
// Calculate timing based on PAL/NTSC
|
||||
const bool is_pal = snes_.memory().pal_timing();
|
||||
const double refresh_rate = is_pal ? 50.0 : 60.0;
|
||||
wanted_frame_time = 1.0 / refresh_rate;
|
||||
wanted_samples = kAudioFrequency / static_cast<int>(refresh_rate);
|
||||
|
||||
printf("Emulator initialized: %s mode (%.1f Hz)\n", is_pal ? "PAL" : "NTSC", refresh_rate);
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
@@ -164,12 +179,17 @@ int main(int argc, char **argv) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_DROPFILE:
|
||||
rom_.LoadFromFile(event.drop.file);
|
||||
if (rom_.is_loaded()) {
|
||||
if (rom_.LoadFromFile(event.drop.file).ok() && rom_.is_loaded()) {
|
||||
rom_data_ = rom_.vector();
|
||||
snes_.Init(rom_data_);
|
||||
wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0);
|
||||
wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60);
|
||||
|
||||
const bool is_pal = snes_.memory().pal_timing();
|
||||
const double refresh_rate = is_pal ? 50.0 : 60.0;
|
||||
wanted_frame_time = 1.0 / refresh_rate;
|
||||
wanted_samples = kAudioFrequency / static_cast<int>(refresh_rate);
|
||||
|
||||
printf("Loaded new ROM via drag-and-drop: %s\n", event.drop.file);
|
||||
frame_count = 0; // Reset frame counter
|
||||
loaded = true;
|
||||
}
|
||||
SDL_free(event.drop.file);
|
||||
@@ -194,14 +214,15 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t current_count = SDL_GetPerformanceCounter();
|
||||
uint64_t delta = current_count - last_count;
|
||||
const uint64_t current_count = SDL_GetPerformanceCounter();
|
||||
const uint64_t delta = current_count - last_count;
|
||||
last_count = current_count;
|
||||
float seconds = delta / (float)count_frequency;
|
||||
const double seconds = static_cast<double>(delta) / static_cast<double>(count_frequency);
|
||||
time_adder += seconds;
|
||||
// allow 2 ms earlier, to prevent skipping due to being just below wanted
|
||||
while (time_adder >= wanted_frames_ - 0.002) {
|
||||
time_adder -= wanted_frames_;
|
||||
|
||||
// Run frame if enough time has elapsed (allow 2ms grace period)
|
||||
while (time_adder >= wanted_frame_time - 0.002) {
|
||||
time_adder -= wanted_frame_time;
|
||||
|
||||
if (loaded) {
|
||||
snes_.RunFrame();
|
||||
@@ -239,46 +260,52 @@ int main(int argc, char **argv) {
|
||||
break; // Exit inner loop immediately
|
||||
}
|
||||
|
||||
snes_.SetSamples(audio_buffer_, wanted_samples_);
|
||||
if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) {
|
||||
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
|
||||
// Generate audio samples and queue them
|
||||
snes_.SetSamples(audio_buffer.get(), wanted_samples);
|
||||
const uint32_t queued_size = SDL_GetQueuedAudioSize(audio_device);
|
||||
const uint32_t max_queued = wanted_samples * 4 * 6; // Keep up to 6 frames queued
|
||||
if (queued_size <= max_queued) {
|
||||
SDL_QueueAudio(audio_device, audio_buffer.get(), wanted_samples * 4);
|
||||
}
|
||||
|
||||
void *ppu_pixels_;
|
||||
int ppu_pitch_;
|
||||
if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) !=
|
||||
0) {
|
||||
// Render PPU output to texture
|
||||
void *ppu_pixels = nullptr;
|
||||
int ppu_pitch = 0;
|
||||
if (SDL_LockTexture(ppu_texture, nullptr, &ppu_pixels, &ppu_pitch) != 0) {
|
||||
printf("Failed to lock texture: %s\n", SDL_GetError());
|
||||
return EXIT_FAILURE;
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
snes_.SetPixels(static_cast<uint8_t *>(ppu_pixels_));
|
||||
SDL_UnlockTexture(ppu_texture_);
|
||||
snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels));
|
||||
SDL_UnlockTexture(ppu_texture);
|
||||
}
|
||||
}
|
||||
|
||||
// Present rendered frame
|
||||
SDL_RenderClear(renderer_.get());
|
||||
SDL_RenderCopy(renderer_.get(), ppu_texture_, NULL, NULL);
|
||||
SDL_RenderPresent(renderer_.get()); // should vsync
|
||||
SDL_RenderCopy(renderer_.get(), ppu_texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_.get());
|
||||
}
|
||||
|
||||
printf("[EMULATOR] Cleaning up SDL resources...\n");
|
||||
|
||||
// Clean up audio
|
||||
SDL_PauseAudioDevice(audio_device_, 1);
|
||||
SDL_ClearQueuedAudio(audio_device_);
|
||||
SDL_CloseAudioDevice(audio_device_);
|
||||
delete[] audio_buffer_;
|
||||
// === Cleanup SDL resources (in reverse order of initialization) ===
|
||||
printf("\n[EMULATOR] Shutting down...\n");
|
||||
|
||||
// Clean up texture
|
||||
if (ppu_texture_) {
|
||||
SDL_DestroyTexture(ppu_texture_);
|
||||
if (ppu_texture) {
|
||||
SDL_DestroyTexture(ppu_texture);
|
||||
ppu_texture = nullptr;
|
||||
}
|
||||
|
||||
// Clean up renderer and window (done by unique_ptr destructors)
|
||||
// Clean up audio (audio_buffer cleaned up automatically by unique_ptr)
|
||||
SDL_PauseAudioDevice(audio_device, 1);
|
||||
SDL_ClearQueuedAudio(audio_device);
|
||||
SDL_CloseAudioDevice(audio_device);
|
||||
|
||||
// Clean up renderer and window (done automatically by unique_ptr destructors)
|
||||
renderer_.reset();
|
||||
window_.reset();
|
||||
|
||||
// Quit SDL
|
||||
// Quit SDL subsystems
|
||||
SDL_Quit();
|
||||
|
||||
printf("[EMULATOR] Shutdown complete.\n");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@@ -11,47 +12,28 @@ namespace emu {
|
||||
void MemoryImpl::Initialize(const std::vector<uint8_t>& rom_data,
|
||||
bool verbose) {
|
||||
verbose_ = verbose;
|
||||
type_ = 1;
|
||||
type_ = 1; // LoROM
|
||||
|
||||
auto location = 0x7FC0; // GetHeaderOffset();
|
||||
auto location = 0x7FC0; // LoROM header location
|
||||
rom_size_ = 0x400 << rom_data[location + 0x17];
|
||||
sram_size_ = 0x400 << rom_data[location + 0x18];
|
||||
|
||||
// Allocate ROM and SRAM storage
|
||||
rom_.resize(rom_size_);
|
||||
|
||||
// Copy memory into rom_
|
||||
std::copy(rom_data.begin(), rom_data.begin() + rom_size_, rom_.begin());
|
||||
const size_t copy_size = std::min<size_t>(rom_size_, rom_data.size());
|
||||
std::copy(rom_data.begin(), rom_data.begin() + copy_size, rom_.begin());
|
||||
|
||||
ram_.resize(sram_size_);
|
||||
std::fill(ram_.begin(), ram_.end(), 0);
|
||||
|
||||
// Clear memory
|
||||
memory_.resize(0x1000000); // 16 MB
|
||||
std::fill(memory_.begin(), memory_.end(), 0);
|
||||
|
||||
// Load ROM data into memory based on LoROM mapping
|
||||
size_t rom_data_size = rom_data.size();
|
||||
size_t rom_address = 0;
|
||||
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
|
||||
for (size_t bank = 0x00; bank <= 0x3F; ++bank) {
|
||||
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
|
||||
if (rom_address < rom_data_size) {
|
||||
std::copy(rom_data.begin() + rom_address,
|
||||
rom_data.begin() + rom_address + ROM_CHUNK_SIZE,
|
||||
memory_.begin() + (bank << 16) + offset);
|
||||
rom_address += ROM_CHUNK_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug: Log reset vector location
|
||||
uint8_t reset_low = memory_[0x00FFFC];
|
||||
uint8_t reset_high = memory_[0x00FFFD];
|
||||
LOG_INFO("Memory", "LoROM reset vector at $00:FFFC = $%02X%02X (from ROM offset $%04X)",
|
||||
reset_high, reset_low, 0x7FFC);
|
||||
LOG_INFO("Memory", "ROM data at offset $7FFC = $%02X $%02X",
|
||||
rom_data[0x7FFC], rom_data[0x7FFD]);
|
||||
LOG_INFO("Memory", "LoROM initialized: ROM size=$%06X (%zuKB) SRAM size=$%04X",
|
||||
rom_size_, rom_size_ / 1024, sram_size_);
|
||||
LOG_INFO("Memory", "Reset vector at ROM offset $7FFC-$7FFD = $%02X%02X",
|
||||
rom_data[0x7FFD], rom_data[0x7FFC]);
|
||||
}
|
||||
|
||||
uint8_t MemoryImpl::cart_read(uint8_t bank, uint16_t adr) {
|
||||
// Emulator uses this path for all ROM/cart reads
|
||||
switch (type_) {
|
||||
case 0:
|
||||
return open_bus_;
|
||||
@@ -82,16 +64,20 @@ void MemoryImpl::cart_write(uint8_t bank, uint16_t adr, uint8_t val) {
|
||||
}
|
||||
|
||||
uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) {
|
||||
// SRAM access: banks 70-7e and f0-ff, addresses 0000-7fff
|
||||
if (((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 &&
|
||||
sram_size_ > 0) {
|
||||
// banks 70-7e and f0-ff, adr 0000-7fff
|
||||
return ram_[(((bank & 0xf) << 15) | adr) & (sram_size_ - 1)];
|
||||
}
|
||||
|
||||
// ROM access: banks 00-7f (mirrored to 80-ff), addresses 8000-ffff
|
||||
// OR banks 40-7f, all addresses
|
||||
bank &= 0x7f;
|
||||
if (adr >= 0x8000 || bank >= 0x40) {
|
||||
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
|
||||
return rom_[((bank << 15) | (adr & 0x7fff)) & (rom_size_ - 1)];
|
||||
uint32_t rom_offset = ((bank << 15) | (adr & 0x7fff)) & (rom_size_ - 1);
|
||||
return rom_[rom_offset];
|
||||
}
|
||||
|
||||
return open_bus_;
|
||||
}
|
||||
|
||||
@@ -140,29 +126,10 @@ void MemoryImpl::cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val) {
|
||||
}
|
||||
|
||||
uint32_t MemoryImpl::GetMappedAddress(uint32_t address) const {
|
||||
uint8_t bank = address >> 16;
|
||||
uint32_t offset = address & 0xFFFF;
|
||||
|
||||
if (bank <= 0x3F) {
|
||||
if (address <= 0x1FFF) {
|
||||
return (0x7E << 16) + offset; // Shadow RAM
|
||||
} else if (address <= 0x5FFF) {
|
||||
return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers
|
||||
} else if (address <= 0x7FFF) {
|
||||
return offset - 0x6000 + 0x6000; // Expansion RAM
|
||||
} else {
|
||||
// Return lorom mapping
|
||||
return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM
|
||||
}
|
||||
} else if (bank == 0x7D) {
|
||||
return offset + 0x7D0000; // SRAM
|
||||
} else if (bank == 0x7E || bank == 0x7F) {
|
||||
return offset + 0x7E0000; // System RAM
|
||||
} else if (bank >= 0x80) {
|
||||
// Handle HiROM and mirrored areas
|
||||
}
|
||||
|
||||
return address; // Return the original address if no mapping is defined
|
||||
// NOTE: This function is only used by ROM editor via Memory interface.
|
||||
// The emulator core uses cart_read/cart_write instead.
|
||||
// Returns identity mapping for now - full implementation not needed for emulator.
|
||||
return address;
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
|
||||
@@ -306,7 +306,7 @@ uint8_t Snes::ReadBBus(uint8_t adr) {
|
||||
static int cpu_port_read_count = 0;
|
||||
static uint8_t last_f4 = 0xFF, last_f5 = 0xFF;
|
||||
bool value_changed = ((adr & 0x3) == 0 && val != last_f4) || ((adr & 0x3) == 1 && val != last_f5);
|
||||
if (value_changed || cpu_port_read_count++ < 50) {
|
||||
if (value_changed || cpu_port_read_count++ < 5) {
|
||||
LOG_INFO("SNES", "CPU read APU port $21%02X (F%d) = $%02X at PC=$%02X:%04X [AFTER CatchUp: APU_cycles=%llu CPU_cycles=%llu]",
|
||||
0x40 + (adr & 0x3), (adr & 0x3) + 4, val, cpu_.PB, cpu_.PC, apu_.GetCycles(), cycles_);
|
||||
if ((adr & 0x3) == 0) last_f4 = val;
|
||||
|
||||
Reference in New Issue
Block a user