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