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