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_ERROR("Emulator", "Failed to initialize audio backend");
154 } else {
155 LOG_INFO("Emulator", "Audio backend initialized: %s",
156 audio_backend_->GetBackendName().c_str());
158 }
159 }
160
161 // Set up CPU breakpoint callback
162 snes_.cpu().on_breakpoint_hit_ = [this](uint32_t pc) -> bool {
165 };
166
167 // Set up instruction recording callback for DisassemblyViewer
168 snes_.cpu().on_instruction_executed_ =
169 [this](uint32_t address, uint8_t opcode,
170 const std::vector<uint8_t>& operands, const std::string& mnemonic,
171 const std::string& operand_str) {
172 disassembly_viewer_.RecordInstruction(address, opcode, operands,
173 mnemonic, operand_str);
174 };
175
176 initialized_ = true;
177}
178
180 if (!rom || !rom->is_loaded()) {
181 return false;
182 }
183
184 // Initialize audio backend if not already done
185 // Skip if using external (shared) audio backend
187 LOG_INFO("Emulator", "Using external (shared) audio backend");
188 } else if (!audio_backend_ || !audio_backend_->IsInitialized()) {
189#ifdef __EMSCRIPTEN__
192#else
195#endif
196
197 audio::AudioConfig config;
198 config.sample_rate = 48000;
199 config.channels = 2;
200 config.buffer_frames = 1024;
202
203 if (!backend->Initialize(config)) {
204 LOG_WARN("Emulator",
205 "Failed to initialize audio backend; falling back to Null");
208 if (!backend->Initialize(config)) {
209 LOG_ERROR("Emulator", "Failed to initialize Null audio backend");
210 return false;
211 }
212 }
213
214 audio_backend_ = std::move(backend);
216 LOG_INFO("Emulator", "Audio backend initialized for headless mode: %s",
217 audio_backend_->GetBackendName().c_str());
218 }
219
220 // Initialize SNES if not already done
221 if (!snes_initialized_) {
222 if (rom_data_.empty()) {
223 rom_data_ = rom->vector();
224 }
226
227 // Use accurate SNES frame rates for proper timing
228 const double frame_rate =
229 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
230 wanted_frames_ = 1.0 / frame_rate;
231 // When resampling is enabled (which we just did above), we need to generate
232 // samples at the NATIVE rate (32kHz). The backend will resample them to 48kHz.
233 // Calculate samples per frame based on actual frame rate for accurate timing.
235 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
236 snes_initialized_ = true;
237
238 count_frequency = SDL_GetPerformanceFrequency();
239 last_count = SDL_GetPerformanceCounter();
240 time_adder = 0.0;
241
242 LOG_INFO("Emulator", "SNES initialized for headless mode");
243 }
244
245 // Always update timing constants based on current ROM region
246 // This ensures MusicPlayer gets correct timing even if ROM changed
247 if (snes_initialized_) {
248 const double frame_rate =
249 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
250 wanted_frames_ = 1.0 / frame_rate;
252 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
253 }
254
255 return true;
256}
257
258absl::Status Emulator::ReloadRuntimeRom(const std::vector<uint8_t>& rom_data) {
259 if (rom_data.empty()) {
260 return absl::InvalidArgumentError("runtime ROM data is empty");
261 }
262
263 rom_data_ = rom_data;
265 snes_initialized_ = true;
266
267 const double frame_rate =
268 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
269 wanted_frames_ = 1.0 / frame_rate;
271 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
272
273 count_frequency = SDL_GetPerformanceFrequency();
274 last_count = SDL_GetPerformanceCounter();
275 time_adder = 0.0;
276 frame_count_ = 0;
277 fps_timer_ = 0.0;
278 current_fps_ = 0.0;
281
284
285 running_ = true;
286 return absl::OkStatus();
287}
288
290 if (!snes_initialized_ || !running_) {
291 return;
292 }
293
294 // If audio focus mode is active (Music Editor), skip standard frame processing
295 // because MusicPlayer drives the emulator via RunAudioFrame()
296 if (audio_focus_mode_) {
297 return;
298 }
299
300 // Ensure audio stream resampling is configured (32040 Hz -> 48000 Hz)
301 // Without this, samples are fed at wrong rate causing 1.5x speedup
303 if (use_sdl_audio_stream_ && audio_backend_->SupportsAudioStream()) {
304 audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate, 2);
306 } else {
307 audio_backend_->SetAudioStreamResampling(false, kNativeSampleRate, 2);
308 audio_stream_active_ = false;
309 }
311 }
312
313 // Calculate timing
314 uint64_t current_count = SDL_GetPerformanceCounter();
315 uint64_t delta = current_count - last_count;
316 last_count = current_count;
317 double seconds = delta / (double)count_frequency;
318
319 time_adder += seconds;
320
321 // Cap time accumulation to prevent runaway (max 2 frames worth)
322 double max_accumulation = wanted_frames_ * 2.0;
323 if (time_adder > max_accumulation) {
324 time_adder = max_accumulation;
325 }
326
327 // Process frames - limit to 2 frames max per update to prevent fast-forward
328 int frames_processed = 0;
329 constexpr int kMaxFramesPerUpdate = 2;
330
331 // Local buffer for audio samples (533 stereo samples per frame)
332 static int16_t native_audio_buffer[2048];
333
334 while (time_adder >= wanted_frames_ &&
335 frames_processed < kMaxFramesPerUpdate) {
337 frames_processed++;
338
339 // Mark frame boundary for DSP sample reading
340 // snes_.apu().dsp().NewFrame(); // Removed in favor of readOffset tracking
341
342 // Run SNES frame (generates audio samples)
343 snes_.RunFrame();
344
345 // Queue audio samples (always resampled to backend rate)
346 if (audio_backend_) {
347 auto status = audio_backend_->GetStatus();
348 const uint32_t max_buffer = static_cast<uint32_t>(wanted_samples_ * 6);
349
350 if (status.queued_frames < max_buffer) {
351 snes_.SetSamples(native_audio_buffer, wanted_samples_);
352 // Try native rate resampling first (if audio stream is enabled)
353 // Falls back to direct queueing if not available
354 if (!audio_backend_->QueueSamplesNative(
355 native_audio_buffer, wanted_samples_, 2, kNativeSampleRate)) {
356 static int log_counter = 0;
357 if (++log_counter % 60 == 0) {
358 int backend_rate = audio_backend_->GetConfig().sample_rate;
359 LOG_WARN("Emulator",
360 "Resampling failed (Native=%d, Backend=%d) - Dropping "
361 "audio to prevent speedup/pitch shift",
362 kNativeSampleRate, backend_rate);
363 }
364 }
365 }
366 }
367 }
368}
369
371 // Reset timing state to prevent accumulated time from causing fast playback
372 count_frequency = SDL_GetPerformanceFrequency();
373 last_count = SDL_GetPerformanceCounter();
374 time_adder = 0.0;
375
376 // Clear audio buffer to prevent static from stale data
377 // Use accessor to get correct backend (external or owned)
378 if (auto* backend = audio_backend()) {
379 backend->Clear();
380 }
381}
382
384 // Simplified audio-focused frame execution for music editor
385 // Runs exactly one SNES frame per call - caller controls timing
386
387 // Use accessor to get correct backend (external or owned)
388 auto* backend = audio_backend();
389
390 // DIAGNOSTIC: Always log entry to verify this function is being called
391 static int entry_count = 0;
392 if (entry_count < 5 || entry_count % 300 == 0) {
393 LOG_INFO("Emulator",
394 "RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p "
395 "(external=%p, owned=%p)",
396 entry_count, snes_initialized_, running_,
397 static_cast<void*>(backend),
398 static_cast<void*>(external_audio_backend_),
399 static_cast<void*>(audio_backend_.get()));
400 }
401 entry_count++;
402
403 if (!snes_initialized_ || !running_) {
404 static int skip_count = 0;
405 if (skip_count < 5) {
406 LOG_WARN("Emulator", "RunAudioFrame SKIPPED: init=%d, running=%d",
408 }
409 skip_count++;
410 return;
411 }
412
413 // Ensure audio stream resampling is configured (32040 Hz -> 48000 Hz)
414 if (backend && audio_stream_config_dirty_) {
415 if (use_sdl_audio_stream_ && backend->SupportsAudioStream()) {
416 backend->SetAudioStreamResampling(true, kNativeSampleRate, 2);
418 }
420 }
421
422 // Run exactly one SNES audio frame
423 // Note: NewFrame() is called inside Snes::RunCycle() at vblank start
425
426 // Queue audio samples to backend
427 if (backend) {
428 static int16_t audio_buffer[2048]; // 533 stereo samples max
429 snes_.SetSamples(audio_buffer, wanted_samples_);
430
431 bool queued = backend->QueueSamplesNative(audio_buffer, wanted_samples_, 2,
432 kNativeSampleRate);
433
434 // Diagnostic: Log first few calls and then periodically
435 static int frame_log_count = 0;
436 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
437 LOG_INFO("Emulator", "RunAudioFrame: wanted=%d, queued=%s, stream=%s",
438 wanted_samples_, queued ? "YES" : "NO",
439 audio_stream_active_ ? "active" : "inactive");
440 }
441 frame_log_count++;
442
443 if (!queued && backend->SupportsAudioStream()) {
444 // Try to re-enable resampling and retry once
445 LOG_INFO("Emulator",
446 "RunAudioFrame: First queue failed, re-enabling resampling");
447 backend->SetAudioStreamResampling(true, kNativeSampleRate, 2);
449 queued = backend->QueueSamplesNative(audio_buffer, wanted_samples_, 2,
450 kNativeSampleRate);
451 LOG_INFO("Emulator", "RunAudioFrame: Retry queued=%s",
452 queued ? "YES" : "NO");
453 }
454
455 if (!queued) {
456 LOG_WARN("Emulator",
457 "RunAudioFrame: AUDIO DROPPED - resampling not working!");
458 }
459 }
460}
461
462void Emulator::Run(Rom* rom) {
464 const char* env_value = std::getenv("YAZE_USE_SDL_AUDIO_STREAM");
465 if (env_value && std::atoi(env_value) != 0) {
467 }
469 }
470
471 // Lazy initialization: set renderer from Controller if not set yet
472 if (!renderer_) {
473 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
474 "Emulator renderer not initialized");
475 return;
476 }
477
478 // Initialize audio backend if not already done (lazy initialization)
479 if (!audio_backend_) {
480#ifdef __EMSCRIPTEN__
483#else
486#endif
487
488 audio::AudioConfig config;
489 config.sample_rate = 48000;
490 config.channels = 2;
491 // Use moderate buffer size - 1024 samples = ~21ms latency
492 // This is a good balance between latency and stability
493 config.buffer_frames = 1024;
495
496 if (!audio_backend_->Initialize(config)) {
497 LOG_ERROR("Emulator", "Failed to initialize audio backend");
498 } else {
499 LOG_INFO("Emulator", "Audio backend initialized (lazy): %s",
500 audio_backend_->GetBackendName().c_str());
502 }
503 }
504
505 // Initialize input manager if not already done
510 LOG_ERROR("Emulator", "Failed to initialize input manager");
511 } else {
513 LOG_INFO("Emulator", "Input manager initialized: %s",
515 }
516 } else {
518 }
519
520 // Initialize SNES and create PPU texture on first run
521 // This happens lazily when user opens the emulator window
522 if (!snes_initialized_ && rom->is_loaded()) {
523 // Create PPU texture with correct format for SNES emulator
524 // ARGB8888 matches the XBGR format used by the SNES PPU (pixel format 1)
525 if (!ppu_texture_) {
527 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
528 if (ppu_texture_ == NULL) {
529 printf("Failed to create PPU texture: %s\n", SDL_GetError());
530 return;
531 }
532 }
533
534 // Initialize SNES with ROM data (either from Initialize() or from rom
535 // parameter)
536 if (rom_data_.empty()) {
537 rom_data_ = rom->vector();
538 }
540
541 // Note: DisassemblyViewer recording is always enabled via callback
542 // No explicit setup needed - callback is set in Initialize()
543
544 // Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888
545 // texture
546
547 // Use accurate SNES frame rates for proper timing
548 const double frame_rate =
549 snes_.memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
550 wanted_frames_ = 1.0 / frame_rate;
551 // Use native SNES sample rate (32kHz), not backend rate (48kHz)
552 // The audio backend handles resampling from 32kHz -> 48kHz
554 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
555 snes_initialized_ = true;
556
557 count_frequency = SDL_GetPerformanceFrequency();
558 last_count = SDL_GetPerformanceCounter();
559 time_adder = 0.0;
560 frame_count_ = 0;
561 fps_timer_ = 0.0;
562 current_fps_ = 0.0;
565
566 // Start emulator in running state by default
567 // User can press Space to pause if needed
568 running_ = true;
569 }
570
571 // Auto-pause emulator during window resize to prevent crashes
572 // MODERN APPROACH: Only pause on actual window resize, not focus loss
573 static bool was_running_before_resize = false;
574
575 // Check if window is being resized (set in HandleEvents)
577 was_running_before_resize = true;
578 running_ = false;
580 was_running_before_resize) {
581 // Auto-resume after resize completes
582 running_ = true;
583 was_running_before_resize = false;
584 }
585
586 // REMOVED: Aggressive focus-based pausing
587 // Modern emulators (RetroArch, bsnes, etc.) continue running in background
588 // Users can manually pause with Space if they want to save CPU/battery
589
590 if (running_) {
591 // NOTE: Input polling moved inside frame loops below to ensure fresh
592 // input state for each SNES frame. This is critical for edge detection
593 // (naming screen) when multiple SNES frames run per GUI frame.
594
595 uint64_t current_count = SDL_GetPerformanceCounter();
596 uint64_t delta = current_count - last_count;
597 last_count = current_count;
598 double seconds = delta / (double)count_frequency;
599 time_adder += seconds;
600
601 // Cap time accumulation to prevent spiral of death and improve stability
602 if (time_adder > wanted_frames_ * 3.0) {
604 }
605
606 // Track frames to skip for performance with progressive skip
607 int frames_to_process = 0;
608 while (time_adder >= wanted_frames_ - 0.002) {
610 frames_to_process++;
611 }
612
613 // Progressive frame skip for smoother degradation:
614 // - 1 frame behind: process normally
615 // - 2-3 frames behind: process but skip some rendering
616 // - 4+ frames behind: hard cap to prevent spiral of death
617 int max_frames = 4; // Hard cap
618 if (frames_to_process > max_frames) {
619 // When severely behind, drop extra accumulated time to catch up smoothly
620 // This prevents the "spiral of death" where we never catch up
621 time_adder = 0.0;
622 frames_to_process = max_frames;
623 }
624
625 // Turbo mode: run many frames without timing constraints
627 constexpr int kTurboFrames = 8; // Run 8 frames per iteration (~480 fps)
628 for (int i = 0; i < kTurboFrames; i++) {
629 // Poll input BEFORE each frame for proper edge detection
630 // Poll player 0 (controller 1) so JOY1* latches correct state
632 snes_.RunFrame();
633 frame_count_++;
634 }
635 // Reset timing to prevent catch-up spiral after turbo
636 time_adder = 0.0;
637 frames_to_process = 1; // Still render one frame
638 }
639
640 if (snes_initialized_ && frames_to_process > 0) {
641 // Process frames (skip rendering for all but last frame if falling
642 // behind)
643 for (int i = 0; i < frames_to_process; i++) {
645 uint64_t frame_start = SDL_GetPerformanceCounter();
646 bool should_render = (i == frames_to_process - 1);
647 uint32_t queued_frames = 0;
648 float audio_rms_left = 0.0f;
649 float audio_rms_right = 0.0f;
650
651 // Poll input BEFORE each frame for proper edge detection
652 // This ensures the game sees button release between frames
653 // Critical for naming screen A button registration
654 if (!turbo_mode_) {
655 // Poll player 0 (controller 1) for correct JOY1* state
657 snes_.RunFrame();
658 }
659
660 // Queue audio for every emulated frame (not just the rendered one) to
661 // avoid starving the SDL queue when we process multiple frames while
662 // behind.
663 if (audio_backend_) {
664 int16_t temp_audio_buffer[2048];
665 int16_t* frame_buffer =
666 audio_buffer_ ? audio_buffer_ : temp_audio_buffer;
667
670 audio_backend_->SupportsAudioStream()) {
671 LOG_INFO(
672 "Emulator",
673 "Enabling audio stream resampling (32040Hz -> Device Rate)");
674 audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate,
675 2);
677 } else {
678 LOG_INFO("Emulator", "Disabling audio stream resampling");
679 audio_backend_->SetAudioStreamResampling(false, kNativeSampleRate,
680 2);
681 audio_stream_active_ = false;
682 }
684 }
685
686 auto audio_status = audio_backend_->GetStatus();
687 queued_frames = audio_status.queued_frames;
688
689 const uint32_t samples_per_frame = wanted_samples_;
690 const uint32_t max_buffer = samples_per_frame * 6;
691 const uint32_t optimal_buffer = 2048; // ~40ms target
692
693 if (queued_frames < max_buffer) {
694 // Generate samples for this emulated frame
695 snes_.SetSamples(frame_buffer, wanted_samples_);
696
697 if (should_render) {
698 // Compute RMS only once per rendered frame for metrics
699 const int num_samples = wanted_samples_ * 2; // Stereo
700 auto compute_rms = [&](int total_samples) {
701 if (total_samples <= 0 || frame_buffer == nullptr) {
702 audio_rms_left = 0.0f;
703 audio_rms_right = 0.0f;
704 return;
705 }
706 double sum_l = 0.0;
707 double sum_r = 0.0;
708 const int frames = total_samples / 2;
709 for (int s = 0; s < frames; ++s) {
710 const float l = static_cast<float>(frame_buffer[2 * s]);
711 const float r = static_cast<float>(frame_buffer[2 * s + 1]);
712 sum_l += l * l;
713 sum_r += r * r;
714 }
715 audio_rms_left =
716 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
717 audio_rms_right =
718 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
719 };
720 compute_rms(num_samples);
721 }
722
723 // Dynamic Rate Control (DRC)
724 int effective_rate = kNativeSampleRate;
725 if (queued_frames > optimal_buffer + 256) {
726 effective_rate += 60; // subtle speed up
727 } else if (queued_frames < optimal_buffer - 256) {
728 effective_rate -= 60; // subtle slow down
729 }
730
731 bool queue_ok = audio_backend_->QueueSamplesNative(
732 frame_buffer, wanted_samples_, 2, effective_rate);
733
734 if (!queue_ok && audio_backend_->SupportsAudioStream()) {
735 // Try to re-enable resampling and retry once
736 audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate,
737 2);
739 queue_ok = audio_backend_->QueueSamplesNative(
740 frame_buffer, wanted_samples_, 2, effective_rate);
741 }
742
743 if (!queue_ok) {
744 // Drop audio rather than playing at wrong speed
745 static int error_count = 0;
746 if (++error_count % 300 == 0) {
747 LOG_WARN(
748 "Emulator",
749 "Resampling failed, dropping audio to prevent 1.5x speed "
750 "(count: %d)",
751 error_count);
752 }
753 }
754 } else {
755 // Buffer overflow - skip this frame's audio
756 static int overflow_count = 0;
757 if (++overflow_count % 60 == 0) {
758 LOG_WARN("Emulator",
759 "Audio buffer overflow (count: %d, queued: %u)",
760 overflow_count, queued_frames);
761 }
762 }
763 }
764
765 // Track FPS
766 frame_count_++;
768 if (fps_timer_ >= 1.0) {
770 frame_count_ = 0;
771 fps_timer_ = 0.0;
772 }
773
774 // Only render UI/texture on the last frame
775 if (should_render) {
776 // Record frame timing and audio queue depth for plots
777 {
778 const uint64_t frame_end = SDL_GetPerformanceCounter();
779 const double elapsed_ms =
780 1000.0 * (static_cast<double>(frame_end - frame_start) /
781 static_cast<double>(count_frequency));
782 PushFrameMetrics(static_cast<float>(elapsed_ms), queued_frames,
784 audio_rms_left, audio_rms_right);
785 }
786
787 // Update PPU texture only on rendered frames
788 void* ppu_pixels_;
789 int ppu_pitch_;
790 if (renderer_->LockTexture(ppu_texture_, NULL, &ppu_pixels_,
791 &ppu_pitch_)) {
792 snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels_));
794
795#ifndef __EMSCRIPTEN__
796 // WORKAROUND: Tiny delay after texture unlock to prevent macOS
797 // Metal crash. macOS CoreAnimation/Metal driver bug in
798 // layer_presented() callback. Without this, rapid texture updates
799 // corrupt Metal's frame tracking.
800 // NOTE: Not needed in WASM builds (WebGL doesn't have this issue)
801 SDL_Delay(1);
802#endif
803 }
804 }
805 }
806 }
807 }
808
810}
811
812void Emulator::PushFrameMetrics(float frame_ms, uint32_t audio_frames,
813 uint64_t dma_bytes, uint64_t vram_bytes,
814 float audio_rms_left, float audio_rms_right) {
816 fps_history_[metric_history_head_] = static_cast<float>(current_fps_);
817 audio_queue_history_[metric_history_head_] = static_cast<float>(audio_frames);
818 dma_bytes_history_[metric_history_head_] = static_cast<float>(dma_bytes);
819 vram_bytes_history_[metric_history_head_] = static_cast<float>(vram_bytes);
825 }
826}
827
828namespace {
829std::vector<float> CopyHistoryOrdered(
830 const std::array<float, Emulator::kMetricHistorySize>& data, int head,
831 int count) {
832 std::vector<float> out;
833 out.reserve(count);
834 int start = (head - count + Emulator::kMetricHistorySize) %
836 for (int i = 0; i < count; ++i) {
837 int idx = (start + i) % Emulator::kMetricHistorySize;
838 out.push_back(data[idx]);
839 }
840 return out;
841}
842} // namespace
843
844std::vector<float> Emulator::FrameTimeHistory() const {
845 return CopyHistoryOrdered(frame_time_history_, metric_history_head_,
847}
848
849std::vector<float> Emulator::FpsHistory() const {
850 return CopyHistoryOrdered(fps_history_, metric_history_head_,
852}
853
854std::vector<float> Emulator::AudioQueueHistory() const {
855 return CopyHistoryOrdered(audio_queue_history_, metric_history_head_,
857}
858
859std::vector<float> Emulator::DmaBytesHistory() const {
860 return CopyHistoryOrdered(dma_bytes_history_, metric_history_head_,
862}
863
864std::vector<float> Emulator::VramBytesHistory() const {
865 return CopyHistoryOrdered(vram_bytes_history_, metric_history_head_,
867}
868
869std::vector<float> Emulator::AudioRmsLeftHistory() const {
870 return CopyHistoryOrdered(audio_rms_left_history_, metric_history_head_,
872}
873
874std::vector<float> Emulator::AudioRmsRightHistory() const {
875 return CopyHistoryOrdered(audio_rms_right_history_, metric_history_head_,
877}
878
879std::vector<float> Emulator::RomBankFreeBytes() const {
880 constexpr size_t kBankSize = 0x8000; // LoROM bank size (32KB)
881 if (rom_data_.empty()) {
882 return {};
883 }
884 const size_t bank_count = rom_data_.size() / kBankSize;
885 std::vector<float> free_bytes;
886 free_bytes.reserve(bank_count);
887 for (size_t bank = 0; bank < bank_count; ++bank) {
888 size_t free_count = 0;
889 const size_t base = bank * kBankSize;
890 for (size_t i = 0; i < kBankSize && (base + i) < rom_data_.size(); ++i) {
891 if (rom_data_[base + i] == 0xFF) {
892 free_count++;
893 }
894 }
895 free_bytes.push_back(static_cast<float>(free_count));
896 }
897 return free_bytes;
898}
899
901 try {
902 if (!window_manager_)
903 return; // Workspace window manager must be injected
904
905 static gui::PanelWindow cpu_card("CPU Debugger", ICON_MD_BUG_REPORT);
906 static gui::PanelWindow ppu_card("PPU Viewer", ICON_MD_VIDEOGAME_ASSET);
907 static gui::PanelWindow memory_card("Memory Viewer", ICON_MD_MEMORY);
908 static gui::PanelWindow breakpoints_card("Breakpoints", ICON_MD_STOP);
909 static gui::PanelWindow performance_card("Performance", ICON_MD_SPEED);
910 static gui::PanelWindow ai_card("AI Agent", ICON_MD_SMART_TOY);
911 static gui::PanelWindow save_states_card("Save States", ICON_MD_SAVE);
912 static gui::PanelWindow keyboard_card("Keyboard Config", ICON_MD_KEYBOARD);
913 static gui::PanelWindow apu_card("APU Debugger", ICON_MD_AUDIOTRACK);
914 static gui::PanelWindow audio_card("Audio Mixer", ICON_MD_AUDIO_FILE);
915
916 cpu_card.SetDefaultSize(400, 500);
917 ppu_card.SetDefaultSize(550, 520);
918 memory_card.SetDefaultSize(800, 600);
919 breakpoints_card.SetDefaultSize(400, 350);
920 performance_card.SetDefaultSize(350, 300);
921
922 // Get visibility flags from registry and pass them to Begin() for proper X
923 // button functionality This ensures each card window can be closed by the
924 // user via the window close button
925 bool* cpu_visible =
926 window_manager_->GetWindowVisibilityFlag("emulator.cpu_debugger");
927 if (cpu_visible && *cpu_visible) {
928 if (cpu_card.Begin(cpu_visible)) {
930 }
931 cpu_card.End();
932 }
933
934 bool* ppu_visible =
935 window_manager_->GetWindowVisibilityFlag("emulator.ppu_viewer");
936 if (ppu_visible && *ppu_visible) {
937 if (ppu_card.Begin(ppu_visible)) {
938 RenderNavBar();
940 }
941 ppu_card.End();
942 }
943
944 bool* memory_visible =
945 window_manager_->GetWindowVisibilityFlag("emulator.memory_viewer");
946 if (memory_visible && *memory_visible) {
947 if (memory_card.Begin(memory_visible)) {
949 }
950 memory_card.End();
951 }
952
953 bool* breakpoints_visible =
954 window_manager_->GetWindowVisibilityFlag("emulator.breakpoints");
955 if (breakpoints_visible && *breakpoints_visible) {
956 if (breakpoints_card.Begin(breakpoints_visible)) {
958 }
959 breakpoints_card.End();
960 }
961
962 bool* performance_visible =
963 window_manager_->GetWindowVisibilityFlag("emulator.performance");
964 if (performance_visible && *performance_visible) {
965 if (performance_card.Begin(performance_visible)) {
967 }
968 performance_card.End();
969 }
970
971 bool* ai_agent_visible =
972 window_manager_->GetWindowVisibilityFlag("emulator.ai_agent");
973 if (ai_agent_visible && *ai_agent_visible) {
974 if (ai_card.Begin(ai_agent_visible)) {
976 }
977 ai_card.End();
978 }
979
980 bool* save_states_visible =
981 window_manager_->GetWindowVisibilityFlag("emulator.save_states");
982 if (save_states_visible && *save_states_visible) {
983 if (save_states_card.Begin(save_states_visible)) {
985 }
986 save_states_card.End();
987 }
988
989 bool* keyboard_config_visible =
990 window_manager_->GetWindowVisibilityFlag("emulator.keyboard_config");
991 if (keyboard_config_visible && *keyboard_config_visible) {
992 if (keyboard_card.Begin(keyboard_config_visible)) {
994 }
995 keyboard_card.End();
996 }
997
998 static gui::PanelWindow controller_card("Virtual Controller",
1000 controller_card.SetDefaultSize(250, 450);
1001 bool* virtual_controller_visible =
1002 window_manager_->GetWindowVisibilityFlag("emulator.virtual_controller");
1003 if (virtual_controller_visible && *virtual_controller_visible) {
1004 if (controller_card.Begin(virtual_controller_visible)) {
1006 }
1007 controller_card.End();
1008 }
1009
1010 bool* apu_debugger_visible =
1011 window_manager_->GetWindowVisibilityFlag("emulator.apu_debugger");
1012 if (apu_debugger_visible && *apu_debugger_visible) {
1013 if (apu_card.Begin(apu_debugger_visible)) {
1015 }
1016 apu_card.End();
1017 }
1018
1019 bool* audio_mixer_visible =
1020 window_manager_->GetWindowVisibilityFlag("emulator.audio_mixer");
1021 if (audio_mixer_visible && *audio_mixer_visible) {
1022 if (audio_card.Begin(audio_mixer_visible)) {
1024 }
1025 audio_card.End();
1026 }
1027
1028 } catch (const std::exception& e) {
1029 // Fallback to basic UI if theming fails
1030 ImGui::Text("Error loading emulator UI: %s", e.what());
1031 if (ImGui::Button("Retry")) {
1032 // Force theme manager reinitialization
1033 auto& theme_manager = gui::ThemeManager::Get();
1034 theme_manager.InitializeBuiltInThemes();
1035 }
1036 }
1037}
1038
1040 // Delegate to UI layer
1041 ui::RenderSnesPpu(this);
1042}
1043
1045 // Delegate to UI layer
1046 ui::RenderNavBar(this);
1047}
1048
1049// REMOVED: HandleEvents() - replaced by ui::InputHandler::Poll()
1050// The old ImGui::IsKeyPressed/Released approach was event-based and didn't work
1051// properly for continuous game input. Now using SDL_GetKeyboardState() for
1052// proper polling.
1053
1055 // Delegate to UI layer
1057}
1058
1060 // Delegate to UI layer
1062}
1063
1065 try {
1066 auto& theme_manager = gui::ThemeManager::Get();
1067 const auto& theme = theme_manager.GetCurrentTheme();
1068
1069 // Debugger controls toolbar
1070 if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
1071 running_ = true;
1072 }
1073 ImGui::SameLine();
1074 if (ImGui::Button(ICON_MD_PAUSE)) {
1075 running_ = false;
1076 }
1077 ImGui::SameLine();
1078 if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) {
1079 if (!running_)
1080 snes_.cpu().RunOpcode();
1081 }
1082 ImGui::SameLine();
1083 if (ImGui::Button(ICON_MD_REFRESH)) {
1084 snes_.Reset(true);
1085 }
1086
1087 ImGui::Separator();
1088
1089 // Breakpoint controls
1090 static char bp_addr[16] = "00FFD9";
1091 ImGui::Text(ICON_MD_BUG_REPORT " Breakpoints:");
1092 ImGui::PushItemWidth(100);
1093 ImGui::InputText("##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1094 ImGuiInputTextFlags_CharsHexadecimal |
1095 ImGuiInputTextFlags_CharsUppercase);
1096 ImGui::PopItemWidth();
1097 ImGui::SameLine();
1098 if (ImGui::Button(ICON_MD_ADD " Add")) {
1099 uint32_t addr = std::strtoul(bp_addr, nullptr, 16);
1102 "",
1103 absl::StrFormat("BP at $%06X", addr));
1104 }
1105
1106 // List breakpoints
1107 ImGui::BeginChild("##BPList", ImVec2(0, 100), true);
1108 for (const auto& bp : breakpoint_manager_.GetAllBreakpoints()) {
1110 bool enabled = bp.enabled;
1111 if (ImGui::Checkbox(absl::StrFormat("##en%d", bp.id).c_str(),
1112 &enabled)) {
1113 breakpoint_manager_.SetEnabled(bp.id, enabled);
1114 }
1115 ImGui::SameLine();
1116 ImGui::Text("$%06X", bp.address);
1117 ImGui::SameLine();
1118 ImGui::TextDisabled("(hits: %d)", bp.hit_count);
1119 ImGui::SameLine();
1120 if (ImGui::SmallButton(
1121 absl::StrFormat(ICON_MD_DELETE "##%d", bp.id).c_str())) {
1123 }
1124 }
1125 }
1126 ImGui::EndChild();
1127
1128 ImGui::Separator();
1129
1130 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "CPU Status");
1131 {
1132 gui::StyledChild cpu_status_child(
1133 "##CpuStatus", ImVec2(0, 180),
1134 {.bg = ConvertColorToImVec4(theme.child_bg)}, true);
1135
1136 // Compact register display in a table
1137 if (ImGui::BeginTable(
1138 "Registers", 4,
1139 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1140 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1141 60);
1142 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 80);
1143 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1144 60);
1145 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 80);
1146 ImGui::TableHeadersRow();
1147
1148 ImGui::TableNextRow();
1149 ImGui::TableNextColumn();
1150 ImGui::Text("A");
1151 ImGui::TableNextColumn();
1152 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1153 snes_.cpu().A);
1154 ImGui::TableNextColumn();
1155 ImGui::Text("D");
1156 ImGui::TableNextColumn();
1157 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1158 snes_.cpu().D);
1159
1160 ImGui::TableNextRow();
1161 ImGui::TableNextColumn();
1162 ImGui::Text("X");
1163 ImGui::TableNextColumn();
1164 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1165 snes_.cpu().X);
1166 ImGui::TableNextColumn();
1167 ImGui::Text("DB");
1168 ImGui::TableNextColumn();
1169 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1170 snes_.cpu().DB);
1171
1172 ImGui::TableNextRow();
1173 ImGui::TableNextColumn();
1174 ImGui::Text("Y");
1175 ImGui::TableNextColumn();
1176 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X",
1177 snes_.cpu().Y);
1178 ImGui::TableNextColumn();
1179 ImGui::Text("PB");
1180 ImGui::TableNextColumn();
1181 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1182 snes_.cpu().PB);
1183
1184 ImGui::TableNextRow();
1185 ImGui::TableNextColumn();
1186 ImGui::Text("PC");
1187 ImGui::TableNextColumn();
1188 ImGui::TextColored(ConvertColorToImVec4(theme.success), "0x%04X",
1189 snes_.cpu().PC);
1190 ImGui::TableNextColumn();
1191 ImGui::Text("SP");
1192 ImGui::TableNextColumn();
1193 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1194 snes_.memory().mutable_sp());
1195
1196 ImGui::TableNextRow();
1197 ImGui::TableNextColumn();
1198 ImGui::Text("PS");
1199 ImGui::TableNextColumn();
1200 ImGui::TextColored(ConvertColorToImVec4(theme.warning), "0x%02X",
1201 snes_.cpu().status);
1202 ImGui::TableNextColumn();
1203 ImGui::Text("Cycle");
1204 ImGui::TableNextColumn();
1205 ImGui::TextColored(ConvertColorToImVec4(theme.info), "%llu",
1207
1208 ImGui::EndTable();
1209 }
1210 }
1211
1212 // SPC700 Status Panel
1213 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "SPC700 Status");
1214 {
1215 gui::StyledChild spc_status_child(
1216 "##SpcStatus", ImVec2(0, 150),
1217 {.bg = ConvertColorToImVec4(theme.child_bg)}, true);
1218
1219 if (ImGui::BeginTable(
1220 "SPCRegisters", 4,
1221 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1222 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1223 50);
1224 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 60);
1225 ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
1226 50);
1227 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 60);
1228 ImGui::TableHeadersRow();
1229
1230 ImGui::TableNextRow();
1231 ImGui::TableNextColumn();
1232 ImGui::Text("A");
1233 ImGui::TableNextColumn();
1234 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1235 snes_.apu().spc700().A);
1236 ImGui::TableNextColumn();
1237 ImGui::Text("PC");
1238 ImGui::TableNextColumn();
1239 ImGui::TextColored(ConvertColorToImVec4(theme.success), "0x%04X",
1240 snes_.apu().spc700().PC);
1241
1242 ImGui::TableNextRow();
1243 ImGui::TableNextColumn();
1244 ImGui::Text("X");
1245 ImGui::TableNextColumn();
1246 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1247 snes_.apu().spc700().X);
1248 ImGui::TableNextColumn();
1249 ImGui::Text("SP");
1250 ImGui::TableNextColumn();
1251 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1252 snes_.apu().spc700().SP);
1253
1254 ImGui::TableNextRow();
1255 ImGui::TableNextColumn();
1256 ImGui::Text("Y");
1257 ImGui::TableNextColumn();
1258 ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X",
1259 snes_.apu().spc700().Y);
1260 ImGui::TableNextColumn();
1261 ImGui::Text("PSW");
1262 ImGui::TableNextColumn();
1263 ImGui::TextColored(
1264 ConvertColorToImVec4(theme.warning), "0x%02X",
1265 snes_.apu().spc700().FlagsToByte(snes_.apu().spc700().PSW));
1266
1267 ImGui::EndTable();
1268 }
1269 }
1270
1271 // New Disassembly Viewer
1272 if (ImGui::CollapsingHeader("Disassembly Viewer",
1273 ImGuiTreeNodeFlags_DefaultOpen)) {
1274 uint32_t current_pc =
1275 (static_cast<uint32_t>(snes_.cpu().PB) << 16) | snes_.cpu().PC;
1276 auto& disasm = snes_.cpu().disassembly_viewer();
1277 if (disasm.IsAvailable()) {
1278 disasm.Render(current_pc, snes_.cpu().breakpoints_);
1279 } else {
1280 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1281 "Disassembly viewer unavailable.");
1282 }
1283 }
1284 } catch (const std::exception& e) {
1285 // RAII guards handle style cleanup automatically
1286 ImGui::Text("CPU Debugger Error: %s", e.what());
1287 }
1288}
1289
1291 // Delegate to UI layer
1293}
1294
1296 // Delegate to UI layer
1298}
1299
1301 const std::vector<InstructionEntry>& instruction_log) {
1302 // Delegate to UI layer (legacy log deprecated)
1303 ui::RenderCpuInstructionLog(this, instruction_log.size());
1304}
1305
1307 // TODO: Create ui::RenderSaveStates() when save state system is implemented
1308 auto& theme_manager = gui::ThemeManager::Get();
1309 const auto& theme = theme_manager.GetCurrentTheme();
1310
1311 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1312 ICON_MD_SAVE " Save States - Coming Soon");
1313 ImGui::TextWrapped("Save state functionality will be implemented here.");
1314}
1315
1317 // Delegate to the input manager UI
1319 [this](const input::InputConfig& config) {
1320 input_config_ = config;
1323 }
1324 });
1325}
1326
1328 // Delegate to UI layer
1330}
1331
1333 if (!audio_backend_)
1334 return;
1335
1336 // Master Volume
1337 float volume = audio_backend_->GetVolume();
1338 if (ImGui::SliderFloat("Master Volume", &volume, 0.0f, 1.0f, "%.2f")) {
1339 audio_backend_->SetVolume(volume);
1340 }
1341
1342 ImGui::Separator();
1343 ImGui::Text("Channel Mutes (Debug)");
1344
1345 auto& dsp = snes_.apu().dsp();
1346
1347 if (ImGui::BeginTable("AudioChannels", 4)) {
1348 for (int i = 0; i < 8; ++i) {
1349 ImGui::TableNextColumn();
1350 bool mute = dsp.GetChannelMute(i);
1351 std::string label = "Ch " + std::to_string(i + 1);
1352 if (ImGui::Checkbox(label.c_str(), &mute)) {
1353 dsp.SetChannelMute(i, mute);
1354 }
1355 }
1356 ImGui::EndTable();
1357 }
1358
1359 ImGui::Separator();
1360 if (ImGui::Button("Mute All")) {
1361 for (int i = 0; i < 8; ++i)
1362 dsp.SetChannelMute(i, true);
1363 }
1364 ImGui::SameLine();
1365 if (ImGui::Button("Unmute All")) {
1366 for (int i = 0; i < 8; ++i)
1367 dsp.SetChannelMute(i, false);
1368 }
1369}
1370
1371} // namespace emu
1372} // 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:143
bool is_loaded() const
Definition rom.h:132
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:1064
std::unique_ptr< audio::IAudioBackend > audio_backend_
Definition emulator.h:241
std::vector< float > FrameTimeHistory() const
Definition emulator.cc:844
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:879
std::array< float, kMetricHistorySize > audio_rms_left_history_
Definition emulator.h:229
std::vector< float > AudioRmsRightHistory() const
Definition emulator.cc:874
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:864
std::array< float, kMetricHistorySize > fps_history_
Definition emulator.h:225
std::vector< float > AudioQueueHistory() const
Definition emulator.cc:854
void RenderKeyboardConfig()
Definition emulator.cc:1316
std::vector< float > DmaBytesHistory() const
Definition emulator.cc:859
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:812
std::vector< uint8_t > rom_data_
Definition emulator.h:262
void RenderPerformanceMonitor()
Definition emulator.cc:1290
bool EnsureInitialized(Rom *rom)
Definition emulator.cc:179
input::InputConfig input_config_
Definition emulator.h:266
void RenderBreakpointList()
Definition emulator.cc:1054
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:849
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:1300
std::vector< float > AudioRmsLeftHistory() const
Definition emulator.cc:869
void Run(Rom *rom)
Definition emulator.cc:462
audio::IAudioBackend * audio_backend()
Definition emulator.h:77
std::array< float, kMetricHistorySize > dma_bytes_history_
Definition emulator.h:227
void RenderEmulatorInterface()
Definition emulator.cc:900
int16_t * audio_buffer_
Definition emulator.h:237
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
Definition emulator.cc:258
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:829
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)