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:
scawful
2025-10-06 19:16:26 -04:00
parent a5d4722d13
commit 293ece69aa
9 changed files with 242 additions and 159 deletions

View File

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