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