yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emulator.cc
Go to the documentation of this file.
1#include "app/emu/emulator.h"
2
3#include <cmath>
4#include <cstdint>
5#include <cstdlib>
6#include <vector>
7
8#include "absl/strings/str_format.h"
10#include "util/log.h"
11
12namespace yaze::core {
13extern bool g_window_is_resizing;
14}
15
21#include "app/gui/core/color.h"
22#include "app/gui/core/icons.h"
25#include "imgui/imgui.h"
26
27#ifdef __EMSCRIPTEN__
29#endif
30
31namespace yaze {
32namespace emu {
33
34namespace {
35// SNES audio native sample rate (APU/DSP output rate)
36// The actual SNES APU runs at 32040 Hz (not 32000 Hz).
37// Using 32040 ensures we generate enough samples to prevent buffer underruns.
38constexpr int kNativeSampleRate = 32040;
39
40constexpr int kMusicEditorSampleRate = 22050;
41
42// Accurate SNES frame rates based on master clock calculations
43// NTSC: 21477272 Hz / (262 * 341) = ~60.0988 Hz
44// PAL: 21281370 Hz / (312 * 341) = ~50.007 Hz
45constexpr double kNtscFrameRate = 60.0988;
46constexpr double kPalFrameRate = 50.007;
47
48// Speed calibration factor for audio playback timing
49// This compensates for any accumulated timing errors in the emulation.
50// Value of 1.0 means no calibration. Values < 1.0 slow down playback.
51// This can be exposed as a user-adjustable setting if needed.
52constexpr double kSpeedCalibration = 1.0;
53} // namespace
54
56 // Don't call Cleanup() in destructor - renderer is already destroyed
57 // Just stop emulation
58 running_ = false;
59}
60
62 // Stop emulation
63 running_ = false;
64
65 // Don't try to destroy PPU texture during shutdown
66 // The renderer is destroyed before the emulator, so attempting to
67 // call renderer_->DestroyTexture() will crash
68 // The texture will be cleaned up automatically when SDL quits
69 ppu_texture_ = nullptr;
70
71 // Reset state
72 snes_initialized_ = false;
74}
75
80
82 if (use_sdl_audio_stream_ != enabled) {
83 use_sdl_audio_stream_ = enabled;
85 }
86}
87
89#ifdef __EMSCRIPTEN__
90 if (audio_backend_) {
91 // Safe cast because we know we created a WasmAudioBackend in WASM builds
92 auto* wasm_backend =
93 static_cast<audio::WasmAudioBackend*>(audio_backend_.get());
94 wasm_backend->HandleUserInteraction();
95 }
96#endif
97}
98
101 return;
102 // Clamp to valid range (0-4)
103 int safe_type = std::clamp(type, 0, 4);
104 snes_.apu().dsp().interpolation_type =
105 static_cast<InterpolationType>(safe_type);
106}
107
110 return 0; // Default to Linear if not initialized
111 return static_cast<int>(snes_.apu().dsp().interpolation_type);
112}
113
115 const std::vector<uint8_t>& rom_data) {
116 // This method is now optional - emulator can be initialized lazily in Run()
118 rom_data_ = rom_data;
119
121 const char* env_value = std::getenv("YAZE_USE_SDL_AUDIO_STREAM");
122 if (env_value && std::atoi(env_value) != 0) {
124 }
126 }
127
128 // Panels are registered in EditorManager::Initialize() to avoid duplication
129
130 // Reset state for new ROM
131 running_ = false;
132 snes_initialized_ = false;
133
134 // Initialize audio backend if not already done
135 if (!audio_backend_) {
136#ifdef __EMSCRIPTEN__
139#else
142#endif
143
144 audio::AudioConfig config;
145 config.sample_rate = 48000;
146 config.channels = 2;
147 // Use moderate buffer size - 1024 samples = ~21ms latency
148 // This is a good balance between latency and stability
149 config.buffer_frames = 1024;
151
152 if (!audio_backend_->Initialize(config)) {
153 LOG_WARN("Emulator",
154 "Failed to initialize audio backend; falling back to Null");
157 if (!audio_backend_->Initialize(config)) {
158 LOG_ERROR("Emulator", "Failed to initialize Null audio backend");
159 }
160 }
161 if (audio_backend_->IsInitialized()) {
162 LOG_INFO("Emulator", "Audio backend initialized: %s",
163 audio_backend_->GetBackendName().c_str());
165 }
166 }
167
168 // Set up CPU breakpoint callback
169 snes_.cpu().on_breakpoint_hit_ = [this](uint32_t pc) -> bool {
172 };
173
174 // Set up instruction recording callback for DisassemblyViewer
175 snes_.cpu().on_instruction_executed_ =
176 [this](uint32_t address, uint8_t opcode,
177 const std::vector<uint8_t>& operands, const std::string& mnemonic,
178 const std::string& operand_str) {
179 disassembly_viewer_.RecordInstruction(address, opcode, operands,
180 mnemonic, operand_str);
181 };
182
183 initialized_ = true;
184}
185
187 if (!rom || !rom->is_loaded()) {
188 return false;
189 }
190
191 // Initialize audio backend if not already done
192 // Skip if using external (shared) audio backend
194 LOG_INFO("Emulator", "Using external (shared) audio backend");
195 } else if (!audio_backend_ || !audio_backend_->IsInitialized()) {
196#ifdef __EMSCRIPTEN__
199#else
202#endif
203
204 audio::AudioConfig config;
205 config.sample_rate = 48000;
206 config.channels = 2;
207 config.buffer_frames = 1024;
209
210 if (!backend->Initialize(config)) {
211 LOG_WARN("Emulator",
212 "Failed to initialize audio backend; falling back to Null");
215 if (!backend->Initialize(config)) {
216 LOG_ERROR("Emulator", "Failed to initialize Null audio backend");
217 return false;
218 }
219 }
220
221 audio_backend_ = std::move(backend);
223 LOG_INFO("Emulator", "Audio backend initialized for headless mode: %s",
224 audio_backend_->GetBackendName().c_str());
225 }
226
227 // Initialize SNES if not already done
228 if (!snes_initialized_) {
229 if (rom_data_.empty()) {
230 rom_data_ = rom->vector();
231 }
233
234 // Use accurate SNES frame rates for proper timing
235 const double frame_rate =
236 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
237 wanted_frames_ = 1.0 / frame_rate;
238 // When resampling is enabled (which we just did above), we need to generate
239 // samples at the NATIVE rate (32kHz). The backend will resample them to 48kHz.
240 // Calculate samples per frame based on actual frame rate for accurate timing.
242 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
243 snes_initialized_ = true;
244
245 count_frequency = SDL_GetPerformanceFrequency();
246 last_count = SDL_GetPerformanceCounter();
247 time_adder = 0.0;
248
249 LOG_INFO("Emulator", "SNES initialized for headless mode");
250 }
251
252 // Always update timing constants based on current ROM region
253 // This ensures MusicPlayer gets correct timing even if ROM changed
254 if (snes_initialized_) {
255 const double frame_rate =
256 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
257 wanted_frames_ = 1.0 / frame_rate;
259 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
260 }
261
262 return true;
263}
264
265absl::Status Emulator::ReloadRuntimeRom(const std::vector<uint8_t>& rom_data) {
266 if (rom_data.empty()) {
267 return absl::InvalidArgumentError("runtime ROM data is empty");
268 }
269
270 rom_data_ = rom_data;
272 snes_initialized_ = true;
273
274 const double frame_rate =
275 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
276 wanted_frames_ = 1.0 / frame_rate;
278 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
279
280 count_frequency = SDL_GetPerformanceFrequency();
281 last_count = SDL_GetPerformanceCounter();
282 time_adder = 0.0;
283 frame_count_ = 0;
284 fps_timer_ = 0.0;
285 current_fps_ = 0.0;
288
291
292 running_ = true;
293 return absl::OkStatus();
294}
295
297 if (!snes_initialized_ || !running_) {
298 return;
299 }
300
301 // If audio focus mode is active (Music Editor), skip standard frame processing
302 // because MusicPlayer drives the emulator via RunAudioFrame()
303 if (audio_focus_mode_) {
304 return;
305 }
306
307 // Ensure audio stream resampling is configured (32040 Hz -> 48000 Hz)
308 // Without this, samples are fed at wrong rate causing 1.5x speedup
310 if (use_sdl_audio_stream_ && audio_backend_->SupportsAudioStream()) {
311 audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate, 2);
313 } else {
314 audio_backend_->SetAudioStreamResampling(false, kNativeSampleRate, 2);
315 audio_stream_active_ = false;
316 }
318 }
319
320 // Calculate timing
321 uint64_t current_count = SDL_GetPerformanceCounter();
322 uint64_t delta = current_count - last_count;
323 last_count = current_count;
324 double seconds = delta / (double)count_frequency;
325
326 time_adder += seconds;
327
328 // Cap time accumulation to prevent runaway (max 2 frames worth)
329 double max_accumulation = wanted_frames_ * 2.0;
330 if (time_adder > max_accumulation) {
331 time_adder = max_accumulation;
332 }
333
334 // Process frames - limit to 2 frames max per update to prevent fast-forward
335 int frames_processed = 0;
336 constexpr int kMaxFramesPerUpdate = 2;
337
338 // Local buffer for audio samples (533 stereo samples per frame)
339 static int16_t native_audio_buffer[2048];
340
341 while (time_adder >= wanted_frames_ &&
342 frames_processed < kMaxFramesPerUpdate) {
344 frames_processed++;
345
346 // Mark frame boundary for DSP sample reading
347 // snes_.apu().dsp().NewFrame(); // Removed in favor of readOffset tracking
348
349 // Run SNES frame (generates audio samples)
350 snes_.RunFrame();
351
352 // Queue audio samples (always resampled to backend rate)
353 if (audio_backend_) {
354 auto status = audio_backend_->GetStatus();
355 const uint32_t max_buffer = static_cast<uint32_t>(wanted_samples_ * 6);
356
357 if (status.queued_frames < max_buffer) {
358 snes_.SetSamples(native_audio_buffer, wanted_samples_);
359 // Try native rate resampling first (if audio stream is enabled)
360 // Falls back to direct queueing if not available
361 if (!audio_backend_->QueueSamplesNative(
362 native_audio_buffer, wanted_samples_, 2, kNativeSampleRate)) {
363 static int log_counter = 0;
364 if (++log_counter % 60 == 0) {
365 int backend_rate = audio_backend_->GetConfig().sample_rate;
366 LOG_WARN("Emulator",
367 "Resampling failed (Native=%d, Backend=%d) - Dropping "
368 "audio to prevent speedup/pitch shift",
369 kNativeSampleRate, backend_rate);
370 }
371 }
372 }
373 }
374 }
375}
376
378 // Reset timing state to prevent accumulated time from causing fast playback
379 count_frequency = SDL_GetPerformanceFrequency();
380 last_count = SDL_GetPerformanceCounter();
381 time_adder = 0.0;
382
383 // Clear audio buffer to prevent static from stale data
384 // Use accessor to get correct backend (external or owned)
385 if (auto* backend = audio_backend()) {
386 backend->Clear();
387 }
388}
389
391 // Simplified audio-focused frame execution for music editor
392 // Runs exactly one SNES frame per call - caller controls timing
393
394 // Use accessor to get correct backend (external or owned)
395 auto* backend = audio_backend();
396
397 // DIAGNOSTIC: Always log entry to verify this function is being called
398 static int entry_count = 0;
399 if (entry_count < 5 || entry_count % 300 == 0) {
400 LOG_INFO("Emulator",
401 "RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p "
402 "(external=%p, owned=%p)",
403 entry_count, snes_initialized_, running_,
404 static_cast<void*>(backend),
405 static_cast<void*>(external_audio_backend_),
406 static_cast<void*>(audio_backend_.get()));
407 }
408 entry_count++;
409
410 if (!snes_initialized_ || !running_) {
411 static int skip_count = 0;
412 if (skip_count < 5) {
413 LOG_WARN("Emulator", "RunAudioFrame SKIPPED: init=%d, running=%d",
415 }
416 skip_count++;
417 return;
418 }
419
420 // Ensure audio stream resampling is configured (32040 Hz -> 48000 Hz)
421 if (backend && audio_stream_config_dirty_) {
422 if (use_sdl_audio_stream_ && backend->SupportsAudioStream()) {
423 backend->SetAudioStreamResampling(true, kNativeSampleRate, 2);
425 }
427 }
428
429 // Run exactly one SNES audio frame
430 // Note: NewFrame() is called inside Snes::RunCycle() at vblank start
432
433 // Queue audio samples to backend
434 if (backend) {
435 static int16_t audio_buffer[2048]; // 533 stereo samples max
436 snes_.SetSamples(audio_buffer, wanted_samples_);
437
438 bool queued = backend->QueueSamplesNative(audio_buffer, wanted_samples_, 2,
439 kNativeSampleRate);
440
441 // Diagnostic: Log first few calls and then periodically
442 static int frame_log_count = 0;
443 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
444 LOG_INFO("Emulator", "RunAudioFrame: wanted=%d, queued=%s, stream=%s",
445 wanted_samples_, queued ? "YES" : "NO",
446 audio_stream_active_ ? "active" : "inactive");
447 }
448 frame_log_count++;
449
450 if (!queued && backend->SupportsAudioStream()) {
451 // Try to re-enable resampling and retry once
452 LOG_INFO("Emulator",
453 "RunAudioFrame: First queue failed, re-enabling resampling");
454 backend->SetAudioStreamResampling(true, kNativeSampleRate, 2);
456 queued = backend->QueueSamplesNative(audio_buffer, wanted_samples_, 2,
457 kNativeSampleRate);
458 LOG_INFO("Emulator", "RunAudioFrame: Retry queued=%s",
459 queued ? "YES" : "NO");
460 }
461
462 if (!queued) {
463 LOG_WARN("Emulator",
464 "RunAudioFrame: AUDIO DROPPED - resampling not working!");
465 }
466 }
467}
468
469void Emulator::Run(Rom* rom) {
471 const char* env_value = std::getenv("YAZE_USE_SDL_AUDIO_STREAM");
472 if (env_value && std::atoi(env_value) != 0) {
474 }
476 }
477
478 // Lazy initialization: set renderer from Controller if not set yet
479 if (!renderer_) {
480 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
481 "Emulator renderer not initialized");
482 return;
483 }
484
485 // Initialize audio backend if not already done (lazy initialization)
486 if (!audio_backend_) {
487#ifdef __EMSCRIPTEN__
490#else
493#endif
494
495 audio::AudioConfig config;
496 config.sample_rate = 48000;
497 config.channels = 2;
498 // Use moderate buffer size - 1024 samples = ~21ms latency
499 // This is a good balance between latency and stability
500 config.buffer_frames = 1024;
502
503 if (!audio_backend_->Initialize(config)) {
504 LOG_ERROR("Emulator", "Failed to initialize audio backend");
505 } else {
506 LOG_INFO("Emulator", "Audio backend initialized (lazy): %s",
507 audio_backend_->GetBackendName().c_str());
509 }
510 }
511
512 // Initialize input manager if not already done
517 LOG_ERROR("Emulator", "Failed to initialize input manager");
518 } else {
520 LOG_INFO("Emulator", "Input manager initialized: %s",
522 }
523 } else {
525 }
526
527 // Initialize SNES and create PPU texture on first run
528 // This happens lazily when user opens the emulator window
529 if (!snes_initialized_ && rom->is_loaded()) {
530 // Create PPU texture with correct format for SNES emulator
531 // ARGB8888 matches the XBGR format used by the SNES PPU (pixel format 1)
532 if (!ppu_texture_) {
534 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
535 if (ppu_texture_ == NULL) {
536 printf("Failed to create PPU texture: %s\n", SDL_GetError());
537 return;
538 }
539 }
540
541 // Initialize SNES with ROM data (either from Initialize() or from rom
542 // parameter)
543 if (rom_data_.empty()) {
544 rom_data_ = rom->vector();
545 }
547
548 // Note: DisassemblyViewer recording is always enabled via callback
549 // No explicit setup needed - callback is set in Initialize()
550
551 // Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888
552 // texture
553
554 // Use accurate SNES frame rates for proper timing
555 const double frame_rate =
556 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
557 wanted_frames_ = 1.0 / frame_rate;
558 // Use native SNES sample rate (32kHz), not backend rate (48kHz)
559 // The audio backend handles resampling from 32kHz -> 48kHz
561 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
562 snes_initialized_ = true;
563
564 count_frequency = SDL_GetPerformanceFrequency();
565 last_count = SDL_GetPerformanceCounter();
566 time_adder = 0.0;
567 frame_count_ = 0;
568 fps_timer_ = 0.0;
569 current_fps_ = 0.0;
572
573 // Start emulator in running state by default
574 // User can press Space to pause if needed
575 running_ = true;
576 }
577
578 // Auto-pause emulator during window resize to prevent crashes
579 // MODERN APPROACH: Only pause on actual window resize, not focus loss
580 static bool was_running_before_resize = false;
581
582 // Check if window is being resized (set in HandleEvents)
584 was_running_before_resize = true;
585 running_ = false;
587 was_running_before_resize) {
588 // Auto-resume after resize completes
589 running_ = true;
590 was_running_before_resize = false;
591 }
592
593 // REMOVED: Aggressive focus-based pausing
594 // Modern emulators (RetroArch, bsnes, etc.) continue running in background
595 // Users can manually pause with Space if they want to save CPU/battery
596
597 if (running_) {
598 // NOTE: Input polling moved inside frame loops below to ensure fresh
599 // input state for each SNES frame. This is critical for edge detection
600 // (naming screen) when multiple SNES frames run per GUI frame.
601
602 uint64_t current_count = SDL_GetPerformanceCounter();
603 uint64_t delta = current_count - last_count;
604 last_count = current_count;
605 double seconds = delta / (double)count_frequency;
606 time_adder += seconds;
607
608 // Cap time accumulation to prevent spiral of death and improve stability
609 if (time_adder > wanted_frames_ * 3.0) {
611 }
612
613 // Track frames to skip for performance with progressive skip
614 int frames_to_process = 0;
615 while (time_adder >= wanted_frames_ - 0.002) {
617 frames_to_process++;
618 }
619
620 // Progressive frame skip for smoother degradation:
621 // - 1 frame behind: process normally
622 // - 2-3 frames behind: process but skip some rendering
623 // - 4+ frames behind: hard cap to prevent spiral of death
624 int max_frames = 4; // Hard cap
625 if (frames_to_process > max_frames) {
626 // When severely behind, drop extra accumulated time to catch up smoothly
627 // This prevents the "spiral of death" where we never catch up
628 time_adder = 0.0;
629 frames_to_process = max_frames;
630 }
631
632 // Turbo mode: run many frames without timing constraints
634 constexpr int kTurboFrames = 8; // Run 8 frames per iteration (~480 fps)
635 for (int i = 0; i < kTurboFrames; i++) {
636 // Poll input BEFORE each frame for proper edge detection
637 // Poll player 0 (controller 1) so JOY1* latches correct state
639 snes_.RunFrame();
640 frame_count_++;
641 }
642 // Reset timing to prevent catch-up spiral after turbo
643 time_adder = 0.0;
644 frames_to_process = 1; // Still render one frame
645 }
646
647 if (snes_initialized_ && frames_to_process > 0) {
648 // Process frames (skip rendering for all but last frame if falling
649 // behind)
650 for (int i = 0; i < frames_to_process; i++) {
652 uint64_t frame_start = SDL_GetPerformanceCounter();
653 bool should_render = (i == frames_to_process - 1);
654 uint32_t queued_frames = 0;
655 float audio_rms_left = 0.0f;
656 float audio_rms_right = 0.0f;
657
658 // Poll input BEFORE each frame for proper edge detection
659 // This ensures the game sees button release between frames
660 // Critical for naming screen A button registration
661 if (!turbo_mode_) {
662 // Poll player 0 (controller 1) for correct JOY1* state
664 snes_.RunFrame();
665 }
666
667 // Queue audio for every emulated frame (not just the rendered one) to
668 // avoid starving the SDL queue when we process multiple frames while
669 // behind.
670 if (audio_backend_) {
671 int16_t temp_audio_buffer[2048];
672 int16_t* frame_buffer =
673 audio_buffer_ ? audio_buffer_ : temp_audio_buffer;
674
677 audio_backend_->SupportsAudioStream()) {
678 LOG_INFO(
679 "Emulator",
680 "Enabling audio stream resampling (32040Hz -> Device Rate)");
681 audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate,
682 2);
684 } else {
685 LOG_INFO("Emulator", "Disabling audio stream resampling");
686 audio_backend_->SetAudioStreamResampling(false, kNativeSampleRate,
687 2);
688 audio_stream_active_ = false;
689 }
691 }
692
693 auto audio_status = audio_backend_->GetStatus();
694 queued_frames = audio_status.queued_frames;
695
696 const uint32_t samples_per_frame = wanted_samples_;
697 const uint32_t max_buffer = samples_per_frame * 6;
698 const uint32_t optimal_buffer = 2048; // ~40ms target
699
700 if (queued_frames < max_buffer) {
701 // Generate samples for this emulated frame
702 snes_.SetSamples(frame_buffer, wanted_samples_);
703
704 if (should_render) {
705 // Compute RMS only once per rendered frame for metrics
706 const int num_samples = wanted_samples_ * 2; // Stereo
707 auto compute_rms = [&](int total_samples) {
708 if (total_samples <= 0 || frame_buffer == nullptr) {
709 audio_rms_left = 0.0f;
710 audio_rms_right = 0.0f;
711 return;
712 }
713 double sum_l = 0.0;
714 double sum_r = 0.0;
715 const int frames = total_samples / 2;
716 for (int s = 0; s < frames; ++s) {
717 const float l = static_cast<float>(frame_buffer[2 * s]);
718 const float r = static_cast<float>(frame_buffer[2 * s + 1]);
719 sum_l += l * l;
720 sum_r += r * r;
721 }
722 audio_rms_left =
723 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
724 audio_rms_right =
725 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
726 };
727 compute_rms(num_samples);
728 }
729
730 // Dynamic Rate Control (DRC)
731 int effective_rate = kNativeSampleRate;
732 if (queued_frames > optimal_buffer + 256) {
733 effective_rate += 60; // subtle speed up
734 } else if (queued_frames < optimal_buffer - 256) {
735 effective_rate -= 60; // subtle slow down
736 }
737
738 bool queue_ok = audio_backend_->QueueSamplesNative(
739 frame_buffer, wanted_samples_, 2, effective_rate);
740
741 if (!queue_ok && audio_backend_->SupportsAudioStream()) {
742 // Try to re-enable resampling and retry once
743 audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate,
744 2);
746 queue_ok = audio_backend_->QueueSamplesNative(
747 frame_buffer, wanted_samples_, 2, effective_rate);
748 }
749
750 if (!queue_ok) {
751 // Drop audio rather than playing at wrong speed
752 static int error_count = 0;
753 if (++error_count % 300 == 0) {
754 LOG_WARN(
755 "Emulator",
756 "Resampling failed, dropping audio to prevent 1.5x speed "
757 "(count: %d)",
758 error_count);
759 }
760 }
761 } else {
762 // Buffer overflow - skip this frame's audio
763 static int overflow_count = 0;
764 if (++overflow_count % 60 == 0) {
765 LOG_WARN("Emulator",
766 "Audio buffer overflow (count: %d, queued: %u)",
767 overflow_count, queued_frames);
768 }
769 }
770 }
771
772 // Track FPS
773 frame_count_++;
775 if (fps_timer_ >= 1.0) {
777 frame_count_ = 0;
778 fps_timer_ = 0.0;
779 }
780
781 // Only render UI/texture on the last frame
782 if (should_render) {
783 // Record frame timing and audio queue depth for plots
784 {
785 const uint64_t frame_end = SDL_GetPerformanceCounter();
786 const double elapsed_ms =
787 1000.0 * (static_cast<double>(frame_end - frame_start) /
788 static_cast<double>(count_frequency));
789 PushFrameMetrics(static_cast<float>(elapsed_ms), queued_frames,
791 audio_rms_left, audio_rms_right);
792 }
793
794 // Update PPU texture only on rendered frames
795 void* ppu_pixels_;
796 int ppu_pitch_;
797 if (renderer_->LockTexture(ppu_texture_, NULL, &ppu_pixels_,
798 &ppu_pitch_)) {
799 snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels_));
801
802#ifndef __EMSCRIPTEN__
803 // WORKAROUND: Tiny delay after texture unlock to prevent macOS
804 // Metal crash. macOS CoreAnimation/Metal driver bug in
805 // layer_presented() callback. Without this, rapid texture updates
806 // corrupt Metal's frame tracking.
807 // NOTE: Not needed in WASM builds (WebGL doesn't have this issue)
808 SDL_Delay(1);
809#endif
810 }
811 }
812 }
813 }
814 }
815
817}
818
819void Emulator::PushFrameMetrics(float frame_ms, uint32_t audio_frames,
820 uint64_t dma_bytes, uint64_t vram_bytes,
821 float audio_rms_left, float audio_rms_right) {
823 fps_history_[metric_history_head_] = static_cast<float>(current_fps_);
824 audio_queue_history_[metric_history_head_] = static_cast<float>(audio_frames);
825 dma_bytes_history_[metric_history_head_] = static_cast<float>(dma_bytes);
826 vram_bytes_history_[metric_history_head_] = static_cast<float>(vram_bytes);
832 }
833}
834
835namespace {
836std::vector<float> CopyHistoryOrdered(
837 const std::array<float, Emulator::kMetricHistorySize>& data, int head,
838 int count) {
839 std::vector<float> out;
840 out.reserve(count);
841 int start = (head - count + Emulator::kMetricHistorySize) %
843 for (int i = 0; i < count; ++i) {
844 int idx = (start + i) % Emulator::kMetricHistorySize;
845 out.push_back(data[idx]);
846 }
847 return out;
848}
849} // namespace
850
851std::vector<float> Emulator::FrameTimeHistory() const {
852 return CopyHistoryOrdered(frame_time_history_, metric_history_head_,
854}
855
856std::vector<float> Emulator::FpsHistory() const {
857 return CopyHistoryOrdered(fps_history_, metric_history_head_,
859}
860
861std::vector<float> Emulator::AudioQueueHistory() const {
862 return CopyHistoryOrdered(audio_queue_history_, metric_history_head_,
864}
865
866std::vector<float> Emulator::DmaBytesHistory() const {
867 return CopyHistoryOrdered(dma_bytes_history_, metric_history_head_,
869}
870
871std::vector<float> Emulator::VramBytesHistory() const {
872 return CopyHistoryOrdered(vram_bytes_history_, metric_history_head_,
874}
875
876std::vector<float> Emulator::AudioRmsLeftHistory() const {
877 return CopyHistoryOrdered(audio_rms_left_history_, metric_history_head_,
879}
880
881std::vector<float> Emulator::AudioRmsRightHistory() const {
882 return CopyHistoryOrdered(audio_rms_right_history_, metric_history_head_,
884}
885
886std::vector<float> Emulator::RomBankFreeBytes() const {
887 constexpr size_t kBankSize = 0x8000; // LoROM bank size (32KB)
888 if (rom_data_.empty()) {
889 return {};
890 }
891 const size_t bank_count = rom_data_.size() / kBankSize;
892 std::vector<float> free_bytes;
893 free_bytes.reserve(bank_count);
894 for (size_t bank = 0; bank < bank_count; ++bank) {
895 size_t free_count = 0;
896 const size_t base = bank * kBankSize;
897 for (size_t i = 0; i < kBankSize && (base + i) < rom_data_.size(); ++i) {
898 if (rom_data_[base + i] == 0xFF) {
899 free_count++;
900 }
901 }
902 free_bytes.push_back(static_cast<float>(free_count));
903 }
904 return free_bytes;
905}
906
908 try {
909 if (!window_manager_)
910 return; // Workspace window manager must be injected
911
912 static gui::PanelWindow cpu_card("CPU Debugger", ICON_MD_BUG_REPORT);
913 static gui::PanelWindow ppu_card("PPU Viewer", ICON_MD_VIDEOGAME_ASSET);
914 static gui::PanelWindow memory_card("Memory Viewer", ICON_MD_MEMORY);
915 static gui::PanelWindow breakpoints_card("Breakpoints", ICON_MD_STOP);
916 static gui::PanelWindow performance_card("Performance", ICON_MD_SPEED);
917 static gui::PanelWindow ai_card("AI Agent", ICON_MD_SMART_TOY);
918 static gui::PanelWindow save_states_card("Save States", ICON_MD_SAVE);
919 static gui::PanelWindow keyboard_card("Keyboard Config", ICON_MD_KEYBOARD);
920 static gui::PanelWindow apu_card("APU Debugger", ICON_MD_AUDIOTRACK);
921 static gui::PanelWindow audio_card("Audio Mixer", ICON_MD_AUDIO_FILE);
922
923 cpu_card.SetDefaultSize(400, 500);
924 ppu_card.SetDefaultSize(550, 520);
925 memory_card.SetDefaultSize(800, 600);
926 breakpoints_card.SetDefaultSize(400, 350);
927 performance_card.SetDefaultSize(350, 300);
928
929 // Get visibility flags from registry and pass them to Begin() for proper X
930 // button functionality This ensures each card window can be closed by the
931 // user via the window close button
932 bool* cpu_visible =
933 window_manager_->GetWindowVisibilityFlag("emulator.cpu_debugger");
934 if (cpu_visible && *cpu_visible) {
935 if (cpu_card.Begin(cpu_visible)) {
937 }
938 cpu_card.End();
939 }
940
941 bool* ppu_visible =
942 window_manager_->GetWindowVisibilityFlag("emulator.ppu_viewer");
943 if (ppu_visible && *ppu_visible) {
944 if (ppu_card.Begin(ppu_visible)) {
945 RenderNavBar();
947 }
948 ppu_card.End();
949 }
950
951 bool* memory_visible =
952 window_manager_->GetWindowVisibilityFlag("emulator.memory_viewer");
953 if (memory_visible && *memory_visible) {
954 if (memory_card.Begin(memory_visible)) {
956 }
957 memory_card.End();
958 }
959
960 bool* breakpoints_visible =
961 window_manager_->GetWindowVisibilityFlag("emulator.breakpoints");
962 if (breakpoints_visible && *breakpoints_visible) {
963 if (breakpoints_card.Begin(breakpoints_visible)) {
965 }
966 breakpoints_card.End();
967 }
968
969 bool* performance_visible =
970 window_manager_->GetWindowVisibilityFlag("emulator.performance");
971 if (performance_visible && *performance_visible) {
972 if (performance_card.Begin(performance_visible)) {
974 }
975 performance_card.End();
976 }
977
978 bool* ai_agent_visible =
979 window_manager_->GetWindowVisibilityFlag("emulator.ai_agent");
980 if (ai_agent_visible && *ai_agent_visible) {
981 if (ai_card.Begin(ai_agent_visible)) {
983 }
984 ai_card.End();
985 }
986
987 bool* save_states_visible =
988 window_manager_->GetWindowVisibilityFlag("emulator.save_states");
989 if (save_states_visible && *save_states_visible) {
990 if (save_states_card.Begin(save_states_visible)) {
992 }
993 save_states_card.End();
994 }
995
996 bool* keyboard_config_visible =
997 window_manager_->GetWindowVisibilityFlag("emulator.keyboard_config");
998 if (keyboard_config_visible && *keyboard_config_visible) {
999 if (keyboard_card.Begin(keyboard_config_visible)) {
1001 }
1002 keyboard_card.End();
1003 }
1004
1005 static gui::PanelWindow controller_card("Virtual Controller",
1007 controller_card.SetDefaultSize(250, 450);
1008 bool* virtual_controller_visible =
1009 window_manager_->GetWindowVisibilityFlag("emulator.virtual_controller");
1010 if (virtual_controller_visible && *virtual_controller_visible) {
1011 if (controller_card.Begin(virtual_controller_visible)) {
1013 }
1014 controller_card.End();
1015 }
1016
1017 bool* apu_debugger_visible =
1018 window_manager_->GetWindowVisibilityFlag("emulator.apu_debugger");
1019 if (apu_debugger_visible && *apu_debugger_visible) {
1020 if (apu_card.Begin(apu_debugger_visible)) {
1022 }
1023 apu_card.End();
1024 }
1025
1026 bool* audio_mixer_visible =
1027 window_manager_->GetWindowVisibilityFlag("emulator.audio_mixer");
1028 if (audio_mixer_visible && *audio_mixer_visible) {
1029 if (audio_card.Begin(audio_mixer_visible)) {
1031 }
1032 audio_card.End();
1033 }
1034
1035 } catch (const std::exception& e) {
1036 // Fallback to basic UI if theming fails
1037 ImGui::Text("Error loading emulator UI: %s", e.what());
1038 if (ImGui::Button("Retry")) {
1039 // Force theme manager reinitialization
1040 auto& theme_manager = gui::ThemeManager::Get();
1041 theme_manager.InitializeBuiltInThemes();
1042 }
1043 }
1044}
1045
1047 // Delegate to UI layer
1048 ui::RenderSnesPpu(this);
1049}
1050
1052 // Delegate to UI layer
1053 ui::RenderNavBar(this);
1054}
1055
1056// REMOVED: HandleEvents() - replaced by ui::InputHandler::Poll()
1057// The old ImGui::IsKeyPressed/Released approach was event-based and didn't work
1058// properly for continuous game input. Now using SDL_GetKeyboardState() for
1059// proper polling.
1060
1062 // Delegate to UI layer
1064}
1065
1067 // Delegate to UI layer
1069}
1070
1072 try {
1073 auto& theme_manager = gui::ThemeManager::Get();
1074 const auto& theme = theme_manager.GetCurrentTheme();
1075
1076 // Debugger controls toolbar
1077 if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
1078 running_ = true;
1079 }
1080 ImGui::SameLine();
1081 if (ImGui::Button(ICON_MD_PAUSE)) {
1082 running_ = false;
1083 }
1084 ImGui::SameLine();
1085 if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) {
1086 if (!running_)
1087 snes_.cpu().RunOpcode();
1088 }
1089 ImGui::SameLine();
1090 if (ImGui::Button(ICON_MD_REFRESH)) {
1091 snes_.Reset(true);
1092 }
1093
1094 ImGui::Separator();
1095
1096 // Breakpoint controls
1097 static char bp_addr[16] = "00FFD9";
1098 ImGui::Text(ICON_MD_BUG_REPORT " Breakpoints:");
1099 ImGui::PushItemWidth(100);
1100 ImGui::InputText("##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1101 ImGuiInputTextFlags_CharsHexadecimal |
1102 ImGuiInputTextFlags_CharsUppercase);
1103 ImGui::PopItemWidth();
1104 ImGui::SameLine();
1105 if (ImGui::Button(ICON_MD_ADD " Add")) {
1106 uint32_t addr = std::strtoul(bp_addr, nullptr, 16);
1109 "",
1110 absl::StrFormat("BP at $%06X", addr));
1111 }
1112
1113 // List breakpoints
1114 ImGui::BeginChild("##BPList", ImVec2(0, 100), true);
1115 for (const auto& bp : breakpoint_manager_.GetAllBreakpoints()) {
1117 bool enabled = bp.enabled;
1118 if (ImGui::Checkbox(absl::StrFormat("##en%d", bp.id).c_str(),
1119 &enabled)) {
1120 breakpoint_manager_.SetEnabled(bp.id, enabled);
1121 }
1122 ImGui::SameLine();
1123 ImGui::Text("$%06X", bp.address);
1124 ImGui::SameLine();
1125 ImGui::TextDisabled("(hits: %d)", bp.hit_count);
1126 ImGui::SameLine();
1127 if (ImGui::SmallButton(
1128 absl::StrFormat(ICON_MD_DELETE "##%d", bp.id).c_str())) {
1130 }
1131 }
1132 }
1133 ImGui::EndChild();
1134
1135 ImGui::Separator();
1136
1137 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "CPU Status");
1138 {
1139 gui::StyledChild cpu_status_child(
1140 "##CpuStatus", ImVec2(0, 180),
1141 {.bg = ConvertColorToImVec4(theme.child_bg)}, true);
1142
1143 // Compact register display in a table
1144 if (ImGui::BeginTable(
1145 "Registers", 4,
1146 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1147 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1148 60);
1149 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 80);
1150 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1151 60);
1152 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 80);
1153 ImGui::TableHeadersRow();
1154
1155 ImGui::TableNextRow();
1156 ImGui::TableNextColumn();
1157 ImGui::Text("A");
1158 ImGui::TableNextColumn();
1159 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1160 snes_.cpu().A);
1161 ImGui::TableNextColumn();
1162 ImGui::Text("D");
1163 ImGui::TableNextColumn();
1164 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1165 snes_.cpu().D);
1166
1167 ImGui::TableNextRow();
1168 ImGui::TableNextColumn();
1169 ImGui::Text("X");
1170 ImGui::TableNextColumn();
1171 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1172 snes_.cpu().X);
1173 ImGui::TableNextColumn();
1174 ImGui::Text("DB");
1175 ImGui::TableNextColumn();
1176 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1177 snes_.cpu().DB);
1178
1179 ImGui::TableNextRow();
1180 ImGui::TableNextColumn();
1181 ImGui::Text("Y");
1182 ImGui::TableNextColumn();
1183 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1184 snes_.cpu().Y);
1185 ImGui::TableNextColumn();
1186 ImGui::Text("PB");
1187 ImGui::TableNextColumn();
1188 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1189 snes_.cpu().PB);
1190
1191 ImGui::TableNextRow();
1192 ImGui::TableNextColumn();
1193 ImGui::Text("PC");
1194 ImGui::TableNextColumn();
1195 ImGui::TextColored(ConvertColorToImVec4(theme.success), "0x%04X",
1196 snes_.cpu().PC);
1197 ImGui::TableNextColumn();
1198 ImGui::Text("SP");
1199 ImGui::TableNextColumn();
1200 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1201 snes_.memory().mutable_sp());
1202
1203 ImGui::TableNextRow();
1204 ImGui::TableNextColumn();
1205 ImGui::Text("PS");
1206 ImGui::TableNextColumn();
1207 ImGui::TextColored(ConvertColorToImVec4(theme.warning), "0x%02X",
1208 snes_.cpu().status);
1209 ImGui::TableNextColumn();
1210 ImGui::Text("Cycle");
1211 ImGui::TableNextColumn();
1212 ImGui::TextColored(ConvertColorToImVec4(theme.info), "%llu",
1214
1215 ImGui::EndTable();
1216 }
1217 }
1218
1219 // SPC700 Status Panel
1220 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "SPC700 Status");
1221 {
1222 gui::StyledChild spc_status_child(
1223 "##SpcStatus", ImVec2(0, 150),
1224 {.bg = ConvertColorToImVec4(theme.child_bg)}, true);
1225
1226 if (ImGui::BeginTable(
1227 "SPCRegisters", 4,
1228 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1229 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1230 50);
1231 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 60);
1232 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1233 50);
1234 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 60);
1235 ImGui::TableHeadersRow();
1236
1237 ImGui::TableNextRow();
1238 ImGui::TableNextColumn();
1239 ImGui::Text("A");
1240 ImGui::TableNextColumn();
1241 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1242 snes_.apu().spc700().A);
1243 ImGui::TableNextColumn();
1244 ImGui::Text("PC");
1245 ImGui::TableNextColumn();
1246 ImGui::TextColored(ConvertColorToImVec4(theme.success), "0x%04X",
1247 snes_.apu().spc700().PC);
1248
1249 ImGui::TableNextRow();
1250 ImGui::TableNextColumn();
1251 ImGui::Text("X");
1252 ImGui::TableNextColumn();
1253 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1254 snes_.apu().spc700().X);
1255 ImGui::TableNextColumn();
1256 ImGui::Text("SP");
1257 ImGui::TableNextColumn();
1258 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1259 snes_.apu().spc700().SP);
1260
1261 ImGui::TableNextRow();
1262 ImGui::TableNextColumn();
1263 ImGui::Text("Y");
1264 ImGui::TableNextColumn();
1265 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1266 snes_.apu().spc700().Y);
1267 ImGui::TableNextColumn();
1268 ImGui::Text("PSW");
1269 ImGui::TableNextColumn();
1270 ImGui::TextColored(
1271 ConvertColorToImVec4(theme.warning), "0x%02X",
1272 snes_.apu().spc700().FlagsToByte(snes_.apu().spc700().PSW));
1273
1274 ImGui::EndTable();
1275 }
1276 }
1277
1278 // New Disassembly Viewer
1279 if (ImGui::CollapsingHeader("Disassembly Viewer",
1280 ImGuiTreeNodeFlags_DefaultOpen)) {
1281 uint32_t current_pc =
1282 (static_cast<uint32_t>(snes_.cpu().PB) << 16) | snes_.cpu().PC;
1283 auto& disasm = snes_.cpu().disassembly_viewer();
1284 if (disasm.IsAvailable()) {
1285 disasm.Render(current_pc, snes_.cpu().breakpoints_);
1286 } else {
1287 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1288 "Disassembly viewer unavailable.");
1289 }
1290 }
1291 } catch (const std::exception& e) {
1292 // RAII guards handle style cleanup automatically
1293 ImGui::Text("CPU Debugger Error: %s", e.what());
1294 }
1295}
1296
1298 // Delegate to UI layer
1300}
1301
1303 // Delegate to UI layer
1305}
1306
1308 const std::vector<InstructionEntry>& instruction_log) {
1309 // Delegate to UI layer (legacy log deprecated)
1310 ui::RenderCpuInstructionLog(this, instruction_log.size());
1311}
1312
1314 // TODO: Create ui::RenderSaveStates() when save state system is implemented
1315 auto& theme_manager = gui::ThemeManager::Get();
1316 const auto& theme = theme_manager.GetCurrentTheme();
1317
1318 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1319 ICON_MD_SAVE " Save States - Coming Soon");
1320 ImGui::TextWrapped("Save state functionality will be implemented here.");
1321}
1322
1324 // Delegate to the input manager UI
1326 [this](const input::InputConfig& config) {
1327 input_config_ = config;
1330 }
1331 });
1332}
1333
1335 // Delegate to UI layer
1337}
1338
1340 if (!audio_backend_)
1341 return;
1342
1343 // Master Volume
1344 float volume = audio_backend_->GetVolume();
1345 if (ImGui::SliderFloat("Master Volume", &volume, 0.0f, 1.0f, "%.2f")) {
1346 audio_backend_->SetVolume(volume);
1347 }
1348
1349 ImGui::Separator();
1350 ImGui::Text("Channel Mutes (Debug)");
1351
1352 auto& dsp = snes_.apu().dsp();
1353
1354 if (ImGui::BeginTable("AudioChannels", 4)) {
1355 for (int i = 0; i < 8; ++i) {
1356 ImGui::TableNextColumn();
1357 bool mute = dsp.GetChannelMute(i);
1358 std::string label = "Ch " + std::to_string(i + 1);
1359 if (ImGui::Checkbox(label.c_str(), &mute)) {
1360 dsp.SetChannelMute(i, mute);
1361 }
1362 }
1363 ImGui::EndTable();
1364 }
1365
1366 ImGui::Separator();
1367 if (ImGui::Button("Mute All")) {
1368 for (int i = 0; i < 8; ++i)
1369 dsp.SetChannelMute(i, true);
1370 }
1371 ImGui::SameLine();
1372 if (ImGui::Button("Unmute All")) {
1373 for (int i = 0; i < 8; ++i)
1374 dsp.SetChannelMute(i, false);
1375 }
1376}
1377
1378} // namespace emu
1379} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
const auto & vector() const
Definition rom.h:155
bool is_loaded() const
Definition rom.h:144
bool * GetWindowVisibilityFlag(size_t session_id, const std::string &base_window_id)
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu)
Check if execution should break at this address.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
void ClearAll()
Clear all breakpoints.
void SetEnabled(uint32_t id, bool enabled)
Enable or disable a breakpoint.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
std::array< float, kMetricHistorySize > audio_queue_history_
Definition emulator.h:226
gfx::IRenderer * renderer()
Definition emulator.h:103
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
Definition emulator.cc:114
void RenderModernCpuDebugger()
Definition emulator.cc:1071
std::unique_ptr< audio::IAudioBackend > audio_backend_
Definition emulator.h:241
std::vector< float > FrameTimeHistory() const
Definition emulator.cc:851
void SetInputConfig(const input::InputConfig &config)
Definition emulator.cc:76
std::array< float, kMetricHistorySize > vram_bytes_history_
Definition emulator.h:228
bool audio_stream_env_checked_
Definition emulator.h:253
static constexpr int kMetricHistorySize
Definition emulator.h:222
void set_use_sdl_audio_stream(bool enabled)
Definition emulator.cc:81
std::vector< float > RomBankFreeBytes() const
Definition emulator.cc:886
std::array< float, kMetricHistorySize > audio_rms_left_history_
Definition emulator.h:229
std::vector< float > AudioRmsRightHistory() const
Definition emulator.cc:881
audio::IAudioBackend * external_audio_backend_
Definition emulator.h:242
bool audio_stream_config_dirty_
Definition emulator.h:251
std::array< float, kMetricHistorySize > frame_time_history_
Definition emulator.h:224
uint64_t count_frequency
Definition emulator.h:211
void set_interpolation_type(int type)
Definition emulator.cc:99
std::vector< float > VramBytesHistory() const
Definition emulator.cc:871
std::array< float, kMetricHistorySize > fps_history_
Definition emulator.h:225
std::vector< float > AudioQueueHistory() const
Definition emulator.cc:861
void RenderKeyboardConfig()
Definition emulator.cc:1323
std::vector< float > DmaBytesHistory() const
Definition emulator.cc:866
debug::DisassemblyViewer disassembly_viewer_
Definition emulator.h:260
std::function< void(const input::InputConfig &) input_config_changed_callback_)
Definition emulator.h:267
void PushFrameMetrics(float frame_ms, uint32_t audio_frames, uint64_t dma_bytes, uint64_t vram_bytes, float audio_rms_left, float audio_rms_right)
Definition emulator.cc:819
std::vector< uint8_t > rom_data_
Definition emulator.h:262
void RenderPerformanceMonitor()
Definition emulator.cc:1297
bool EnsureInitialized(Rom *rom)
Definition emulator.cc:186
input::InputConfig input_config_
Definition emulator.h:266
void RenderBreakpointList()
Definition emulator.cc:1061
BreakpointManager breakpoint_manager_
Definition emulator.h:258
editor::WorkspaceWindowManager * window_manager_
Definition emulator.h:270
uint64_t last_count
Definition emulator.h:212
std::vector< float > FpsHistory() const
Definition emulator.cc:856
input::InputManager input_manager_
Definition emulator.h:265
std::array< float, kMetricHistorySize > audio_rms_right_history_
Definition emulator.h:230
void RenderCpuInstructionLog(const std::vector< InstructionEntry > &instructionLog)
Definition emulator.cc:1307
std::vector< float > AudioRmsLeftHistory() const
Definition emulator.cc:876
void Run(Rom *rom)
Definition emulator.cc:469
audio::IAudioBackend * audio_backend()
Definition emulator.h:77
std::array< float, kMetricHistorySize > dma_bytes_history_
Definition emulator.h:227
void RenderEmulatorInterface()
Definition emulator.cc:907
int16_t * audio_buffer_
Definition emulator.h:237
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
Definition emulator.cc:265
gfx::IRenderer * renderer_
Definition emulator.h:248
int get_interpolation_type() const
Definition emulator.cc:108
auto mutable_cycles() -> uint64_t &
Definition snes.h:90
void SetSamples(int16_t *sample_data, int wanted_samples)
Definition snes.cc:856
void Reset(bool hard=false)
Definition snes.cc:181
uint64_t vram_bytes_frame() const
Definition snes.h:108
void RunFrame()
Definition snes.cc:229
auto apu() -> Apu &
Definition snes.h:86
void ResetFrameMetrics()
Definition snes.h:101
auto cpu() -> Cpu &
Definition snes.h:84
void RunAudioFrame()
Definition snes.cc:241
void Init(const std::vector< uint8_t > &rom_data)
Definition snes.cc:162
uint64_t dma_bytes_frame() const
Definition snes.h:107
auto memory() -> MemoryImpl &
Definition snes.h:88
void SetPixels(uint8_t *pixel_data)
Definition snes.cc:860
static std::unique_ptr< IAudioBackend > Create(BackendType type)
void Clear()
Clear all recorded instructions.
void RecordInstruction(uint32_t address, uint8_t opcode, const std::vector< uint8_t > &operands, const std::string &mnemonic, const std::string &operand_str)
Record an instruction execution.
virtual std::string GetBackendName() const =0
Get backend name for debugging.
void Poll(Snes *snes, int player=1)
void SetConfig(const InputConfig &config)
InputConfig GetConfig() const
bool Initialize(InputBackendFactory::BackendType type=InputBackendFactory::BackendType::SDL2)
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
virtual void UnlockTexture(TextureHandle texture)=0
virtual bool LockTexture(TextureHandle texture, SDL_Rect *rect, void **pixels, int *pitch)=0
virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access)=0
Creates a new texture with a specific pixel format.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
RAII guard for ImGui child windows with optional styling.
static ThemeManager & Get()
#define ICON_MD_PAUSE
Definition icons.h:1389
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_STOP
Definition icons.h:1862
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_SPEED
Definition icons.h:1817
#define ICON_MD_AUDIOTRACK
Definition icons.h:213
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_SKIP_NEXT
Definition icons.h:1773
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_SPORTS_ESPORTS
Definition icons.h:1826
#define ICON_MD_AUDIO_FILE
Definition icons.h:212
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
std::vector< float > CopyHistoryOrdered(const std::array< float, Emulator::kMetricHistorySize > &data, int head, int count)
Definition emulator.cc:836
void RenderKeyboardConfig(input::InputManager *manager, const std::function< void(const input::InputConfig &)> &on_config_changed)
Render keyboard configuration UI.
void RenderPerformanceMonitor(Emulator *emu)
Performance metrics (FPS, frame time, audio status)
void RenderAIAgentPanel(Emulator *emu)
AI Agent panel for automated testing/gameplay.
void RenderSnesPpu(Emulator *emu)
SNES PPU output display.
void RenderNavBar(Emulator *emu)
Navigation bar with play/pause, step, reset controls.
void RenderBreakpointList(Emulator *emu)
Breakpoint list and management.
void RenderApuDebugger(Emulator *emu)
APU/Audio debugger with handshake tracker.
void RenderMemoryViewer(Emulator *emu)
Memory viewer/editor.
void RenderCpuInstructionLog(Emulator *emu, uint32_t log_size)
CPU instruction log (legacy, prefer DisassemblyViewer)
void RenderVirtualController(Emulator *emu)
Virtual SNES controller for testing input without keyboard Useful for debugging input issues - bypass...
InterpolationType
Definition dsp.h:10
Input configuration (platform-agnostic key codes)