#include "app/emu/emulator.h" #include #include #include #include "app/core/window.h" #include "util/log.h" namespace yaze::core { extern bool g_window_is_resizing; } #include "app/emu/debug/disassembly_viewer.h" #include "app/emu/ui/debugger_ui.h" #include "app/emu/ui/emulator_ui.h" #include "app/emu/ui/input_handler.h" #include "app/gui/color.h" #include "app/gui/editor_layout.h" #include "app/gui/icons.h" #include "app/gui/theme_manager.h" #include "imgui/imgui.h" namespace yaze { namespace emu { Emulator::~Emulator() { // Don't call Cleanup() in destructor - renderer is already destroyed // Just stop emulation running_ = false; } void Emulator::Cleanup() { // Stop emulation running_ = false; // Don't try to destroy PPU texture during shutdown // The renderer is destroyed before the emulator, so attempting to // call renderer_->DestroyTexture() will crash // The texture will be cleaned up automatically when SDL quits ppu_texture_ = nullptr; // Reset state snes_initialized_ = false; } void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector& rom_data) { // This method is now optional - emulator can be initialized lazily in Run() renderer_ = renderer; rom_data_ = rom_data; // Register emulator cards with EditorCardManager auto& card_manager = gui::EditorCardManager::Get(); card_manager.RegisterCard({ .card_id = "emulator.cpu_debugger", .display_name = "CPU Debugger", .icon = ICON_MD_BUG_REPORT, .category = "Emulator", .visibility_flag = &show_cpu_debugger_, .priority = 10 }); card_manager.RegisterCard({ .card_id = "emulator.memory_viewer", .display_name = "Memory Viewer", .icon = ICON_MD_MEMORY, .category = "Emulator", .visibility_flag = &show_memory_viewer_, .priority = 20 }); card_manager.RegisterCard({ .card_id = "emulator.ppu_viewer", .display_name = "PPU Viewer", .icon = ICON_MD_GRID_VIEW, .category = "Emulator", .visibility_flag = &show_ppu_viewer_, .priority = 30 }); card_manager.RegisterCard({ .card_id = "emulator.audio_mixer", .display_name = "Audio Mixer", .icon = ICON_MD_AUDIO_FILE, .category = "Emulator", .visibility_flag = &show_audio_mixer_, .priority = 40 }); printf("[Emulator] Registered 4 cards with EditorCardManager\n"); // Reset state for new ROM running_ = false; snes_initialized_ = false; // Initialize audio backend if not already done if (!audio_backend_) { audio_backend_ = audio::AudioBackendFactory::Create( audio::AudioBackendFactory::BackendType::SDL2); audio::AudioConfig config; config.sample_rate = 48000; config.channels = 2; config.buffer_frames = 1024; config.format = audio::SampleFormat::INT16; if (!audio_backend_->Initialize(config)) { LOG_ERROR("Emulator", "Failed to initialize audio backend"); } else { LOG_INFO("Emulator", "Audio backend initialized: %s", audio_backend_->GetBackendName().c_str()); } } // Set up CPU breakpoint callback snes_.cpu().on_breakpoint_hit_ = [this](uint32_t pc) -> bool { return breakpoint_manager_.ShouldBreakOnExecute(pc, BreakpointManager::CpuType::CPU_65816); }; // Set up instruction recording callback for DisassemblyViewer snes_.cpu().on_instruction_executed_ = [this](uint32_t address, uint8_t opcode, const std::vector& operands, const std::string& mnemonic, const std::string& operand_str) { disassembly_viewer_.RecordInstruction(address, opcode, operands, mnemonic, operand_str); }; initialized_ = true; } void Emulator::Run(Rom* rom) { // Lazy initialization: set renderer from Controller if not set yet if (!renderer_) { ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Emulator renderer not initialized"); return; } // Initialize audio backend if not already done (lazy initialization) if (!audio_backend_) { audio_backend_ = audio::AudioBackendFactory::Create( audio::AudioBackendFactory::BackendType::SDL2); audio::AudioConfig config; config.sample_rate = 48000; config.channels = 2; config.buffer_frames = 1024; config.format = audio::SampleFormat::INT16; if (!audio_backend_->Initialize(config)) { LOG_ERROR("Emulator", "Failed to initialize audio backend"); } else { LOG_INFO("Emulator", "Audio backend initialized (lazy): %s", audio_backend_->GetBackendName().c_str()); } } // Initialize input manager if not already done if (!input_manager_.IsInitialized()) { if (!input_manager_.Initialize(input::InputBackendFactory::BackendType::SDL2)) { LOG_ERROR("Emulator", "Failed to initialize input manager"); } else { LOG_INFO("Emulator", "Input manager initialized: %s", input_manager_.backend()->GetBackendName().c_str()); } } // Initialize SNES and create PPU texture on first run // This happens lazily when user opens the emulator window if (!snes_initialized_ && rom->is_loaded()) { // Create PPU texture with correct format for SNES emulator // ARGB8888 matches the XBGR format used by the SNES PPU (pixel format 1) if (!ppu_texture_) { ppu_texture_ = renderer_->CreateTextureWithFormat( 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING); if (ppu_texture_ == NULL) { printf("Failed to create PPU texture: %s\n", SDL_GetError()); return; } } // Initialize SNES with ROM data (either from Initialize() or from rom parameter) if (rom_data_.empty()) { rom_data_ = rom->vector(); } snes_.Init(rom_data_); // Note: DisassemblyViewer recording is always enabled via callback // No explicit setup needed - callback is set in Initialize() // Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888 texture wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0); wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60); snes_initialized_ = true; count_frequency = SDL_GetPerformanceFrequency(); last_count = SDL_GetPerformanceCounter(); time_adder = 0.0; frame_count_ = 0; fps_timer_ = 0.0; current_fps_ = 0.0; } RenderNavBar(); // Auto-pause emulator during window operations to prevent macOS crashes static bool was_running_before_pause = false; bool window_has_focus = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow); // Check if window is being resized (set in HandleEvents) if (yaze::core::g_window_is_resizing && running_) { was_running_before_pause = true; running_ = false; } else if (!yaze::core::g_window_is_resizing && !running_ && was_running_before_pause) { // Auto-resume after resize completes running_ = true; was_running_before_pause = false; } // Also pause when window loses focus to save CPU/battery if (!window_has_focus && running_ && !was_running_before_pause) { was_running_before_pause = true; running_ = false; } else if (window_has_focus && !running_ && was_running_before_pause && !yaze::core::g_window_is_resizing) { // Don't auto-resume - let user manually resume was_running_before_pause = false; } if (running_) { // Poll input and update SNES controller state input_manager_.Poll(&snes_, 1); // Player 1 uint64_t current_count = SDL_GetPerformanceCounter(); uint64_t delta = current_count - last_count; last_count = current_count; double seconds = delta / (double)count_frequency; time_adder += seconds; // Cap time accumulation to prevent spiral of death and improve stability if (time_adder > wanted_frames_ * 3.0) { time_adder = wanted_frames_ * 3.0; } // Track frames to skip for performance int frames_to_process = 0; while (time_adder >= wanted_frames_ - 0.002) { time_adder -= wanted_frames_; frames_to_process++; } // Limit maximum frames to process (prevent spiral of death) if (frames_to_process > 4) { frames_to_process = 4; } if (snes_initialized_ && frames_to_process > 0) { // Process frames (skip rendering for all but last frame if falling behind) for (int i = 0; i < frames_to_process; i++) { bool should_render = (i == frames_to_process - 1); // Run frame if (turbo_mode_) { snes_.RunFrame(); } snes_.RunFrame(); // Track FPS frame_count_++; fps_timer_ += wanted_frames_; if (fps_timer_ >= 1.0) { current_fps_ = frame_count_ / fps_timer_; frame_count_ = 0; fps_timer_ = 0.0; } // Only render and handle audio on the last frame if (should_render) { // Generate and queue audio samples using audio backend snes_.SetSamples(audio_buffer_, wanted_samples_); // AUDIO DEBUG: Comprehensive diagnostics at regular intervals static int audio_debug_counter = 0; audio_debug_counter++; // Log at frames 60 (1sec), 300 (5sec), 600 (10sec), then every 600 frames bool should_debug = (audio_debug_counter == 60 || audio_debug_counter == 300 || audio_debug_counter == 600 || (audio_debug_counter % 600 == 0)); if (should_debug) { // Check if buffer exists if (!audio_buffer_) { printf("[AUDIO ERROR] audio_buffer_ is NULL!\n"); } else { // Check for audio samples bool has_audio = false; int16_t max_sample = 0; int non_zero_count = 0; for (int i = 0; i < wanted_samples_ * 2 && i < 100; i++) { if (audio_buffer_[i] != 0) { has_audio = true; non_zero_count++; if (std::abs(audio_buffer_[i]) > std::abs(max_sample)) { max_sample = audio_buffer_[i]; } } } // Backend status auto audio_status = audio_backend_ ? audio_backend_->GetStatus() : audio::AudioStatus{}; bool backend_playing = audio_status.is_playing; printf("\n[AUDIO DEBUG] Frame=%d (~%.1f sec)\n", audio_debug_counter, audio_debug_counter / 60.0f); printf(" Backend: %s (Playing: %s)\n", audio_backend_ ? audio_backend_->GetBackendName().c_str() : "NULL", backend_playing ? "YES" : "NO"); printf(" Queued: %u frames\n", audio_status.queued_frames); printf(" Buffer: wanted_samples=%d, non_zero=%d/%d, max=%d\n", wanted_samples_, non_zero_count, std::min(wanted_samples_ * 2, 100), max_sample); printf(" Samples: %s\n", has_audio ? "YES" : "SILENCE"); // APU state if (snes_.running()) { uint64_t apu_cycles = snes_.apu().GetCycles(); uint16_t spc_pc = snes_.apu().spc700().PC; bool ipl_rom_active = (spc_pc >= 0xFFC0 && spc_pc <= 0xFFFF); printf(" APU: %llu cycles, PC=$%04X %s\n", apu_cycles, spc_pc, ipl_rom_active ? "(IPL ROM)" : "(Game Code)"); // Handshake status auto& tracker = snes_.apu_handshake_tracker(); printf(" Handshake: %s\n", tracker.GetPhaseString().c_str()); if (ipl_rom_active && audio_debug_counter > 300) { printf(" ⚠️ SPC700 STUCK IN IPL ROM - Handshake not completing!\n"); } } else { printf(" ⚠️ SNES not running!\n"); } printf("\n"); } } // Smart buffer management using audio backend if (audio_backend_) { auto status = audio_backend_->GetStatus(); int num_samples = wanted_samples_ * 2; // Stereo if (status.queued_frames < 2) { // Buffer is low, queue more audio if (!audio_backend_->QueueSamples(audio_buffer_, num_samples)) { if (frame_count_ % 300 == 0) { LOG_WARN("Emulator", "Failed to queue audio samples"); } } } else if (status.queued_frames > 6) { // Buffer is too full, clear it to prevent lag audio_backend_->Clear(); audio_backend_->QueueSamples(audio_buffer_, num_samples); } else { // Normal operation - queue samples audio_backend_->QueueSamples(audio_buffer_, num_samples); } } // Update PPU texture only on rendered frames void* ppu_pixels_; int ppu_pitch_; if (renderer_->LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_)) { snes_.SetPixels(static_cast(ppu_pixels_)); renderer_->UnlockTexture(ppu_texture_); // WORKAROUND: Tiny delay after texture unlock to prevent macOS Metal crash // macOS CoreAnimation/Metal driver bug in layer_presented() callback // Without this, rapid texture updates corrupt Metal's frame tracking SDL_Delay(1); } } } } } RenderEmulatorInterface(); } void Emulator::RenderEmulatorInterface() { // Apply modern theming with safety checks try { auto& theme_manager = gui::ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); // Modern EditorCard-based layout - modular and flexible static bool show_cpu_debugger_ = true; static bool show_ppu_display_ = true; static bool show_memory_viewer_ = false; static bool show_breakpoints_ = false; static bool show_performance_ = true; static bool show_ai_agent_ = false; static bool show_save_states_ = false; static bool show_keyboard_config_ = false; static bool show_apu_debugger_ = true; // Create session-aware cards static gui::EditorCard cpu_card(ICON_MD_MEMORY " CPU Debugger", ICON_MD_MEMORY); static gui::EditorCard ppu_card(ICON_MD_VIDEOGAME_ASSET " PPU Display", ICON_MD_VIDEOGAME_ASSET); static gui::EditorCard memory_card(ICON_MD_DATA_ARRAY " Memory Viewer", ICON_MD_DATA_ARRAY); static gui::EditorCard breakpoints_card(ICON_MD_BUG_REPORT " Breakpoints", ICON_MD_BUG_REPORT); static gui::EditorCard performance_card(ICON_MD_SPEED " Performance", ICON_MD_SPEED); static gui::EditorCard ai_card(ICON_MD_SMART_TOY " AI Agent", ICON_MD_SMART_TOY); static gui::EditorCard save_states_card(ICON_MD_SAVE " Save States", ICON_MD_SAVE); static gui::EditorCard keyboard_card(ICON_MD_KEYBOARD " Keyboard Config", ICON_MD_KEYBOARD); static gui::EditorCard apu_debug_card(ICON_MD_MUSIC_NOTE " APU Debugger", ICON_MD_MUSIC_NOTE); // Configure default positions static bool cards_configured = false; if (!cards_configured) { cpu_card.SetDefaultSize(400, 500); cpu_card.SetPosition(gui::EditorCard::Position::Right); ppu_card.SetDefaultSize(550, 520); ppu_card.SetPosition(gui::EditorCard::Position::Floating); memory_card.SetDefaultSize(800, 600); memory_card.SetPosition(gui::EditorCard::Position::Floating); breakpoints_card.SetDefaultSize(400, 350); breakpoints_card.SetPosition(gui::EditorCard::Position::Right); performance_card.SetDefaultSize(350, 300); performance_card.SetPosition(gui::EditorCard::Position::Bottom); ai_card.SetDefaultSize(500, 450); ai_card.SetPosition(gui::EditorCard::Position::Floating); save_states_card.SetDefaultSize(400, 300); save_states_card.SetPosition(gui::EditorCard::Position::Floating); keyboard_card.SetDefaultSize(450, 400); keyboard_card.SetPosition(gui::EditorCard::Position::Floating); apu_debug_card.SetDefaultSize(500, 400); apu_debug_card.SetPosition(gui::EditorCard::Position::Floating); cards_configured = true; } // CPU Debugger Card if (show_cpu_debugger_) { if (cpu_card.Begin(&show_cpu_debugger_)) { RenderModernCpuDebugger(); } cpu_card.End(); } // PPU Display Card if (show_ppu_display_) { if (ppu_card.Begin(&show_ppu_display_)) { RenderSnesPpu(); } ppu_card.End(); } // Memory Viewer Card if (show_memory_viewer_) { if (memory_card.Begin(&show_memory_viewer_)) { RenderMemoryViewer(); } memory_card.End(); } // Breakpoints Card if (show_breakpoints_) { if (breakpoints_card.Begin(&show_breakpoints_)) { RenderBreakpointList(); } breakpoints_card.End(); } // Performance Monitor Card if (show_performance_) { if (performance_card.Begin(&show_performance_)) { RenderPerformanceMonitor(); } performance_card.End(); } // AI Agent Card if (show_ai_agent_) { if (ai_card.Begin(&show_ai_agent_)) { RenderAIAgentPanel(); } ai_card.End(); } // Save States Card if (show_save_states_) { if (save_states_card.Begin(&show_save_states_)) { RenderSaveStates(); } save_states_card.End(); } // Keyboard Configuration Card if (show_keyboard_config_) { if (keyboard_card.Begin(&show_keyboard_config_)) { RenderKeyboardConfig(); } keyboard_card.End(); } // APU Debugger Card if (show_apu_debugger_) { if (apu_debug_card.Begin(&show_apu_debugger_)) { RenderApuDebugger(); } apu_debug_card.End(); } } catch (const std::exception& e) { // Fallback to basic UI if theming fails ImGui::Text("Error loading emulator UI: %s", e.what()); if (ImGui::Button("Retry")) { // Force theme manager reinitialization auto& theme_manager = gui::ThemeManager::Get(); theme_manager.InitializeBuiltInThemes(); } } } void Emulator::RenderSnesPpu() { // Delegate to UI layer ui::RenderSnesPpu(this); } void Emulator::RenderNavBar() { // Delegate to UI layer ui::RenderNavBar(this); } // REMOVED: HandleEvents() - replaced by ui::InputHandler::Poll() // The old ImGui::IsKeyPressed/Released approach was event-based and didn't work properly // for continuous game input. Now using SDL_GetKeyboardState() for proper polling. void Emulator::RenderBreakpointList() { // Delegate to UI layer ui::RenderBreakpointList(this); } void Emulator::RenderMemoryViewer() { // Delegate to UI layer ui::RenderMemoryViewer(this); } void Emulator::RenderModernCpuDebugger() { try { auto& theme_manager = gui::ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); // Debugger controls toolbar if (ImGui::Button(ICON_MD_PLAY_ARROW)) { running_ = true; } ImGui::SameLine(); if (ImGui::Button(ICON_MD_PAUSE)) { running_ = false; } ImGui::SameLine(); if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) { if (!running_) snes_.cpu().RunOpcode(); } ImGui::SameLine(); if (ImGui::Button(ICON_MD_REFRESH)) { snes_.Reset(true); } ImGui::Separator(); // Breakpoint controls static char bp_addr[16] = "00FFD9"; ImGui::Text(ICON_MD_BUG_REPORT " Breakpoints:"); ImGui::PushItemWidth(100); ImGui::InputText("##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); ImGui::PopItemWidth(); ImGui::SameLine(); if (ImGui::Button(ICON_MD_ADD " Add")) { uint32_t addr = std::strtoul(bp_addr, nullptr, 16); breakpoint_manager_.AddBreakpoint(addr, BreakpointManager::Type::EXECUTE, BreakpointManager::CpuType::CPU_65816, "", absl::StrFormat("BP at $%06X", addr)); } // List breakpoints ImGui::BeginChild("##BPList", ImVec2(0, 100), true); for (const auto& bp : breakpoint_manager_.GetAllBreakpoints()) { if (bp.cpu == BreakpointManager::CpuType::CPU_65816) { bool enabled = bp.enabled; if (ImGui::Checkbox(absl::StrFormat("##en%d", bp.id).c_str(), &enabled)) { breakpoint_manager_.SetEnabled(bp.id, enabled); } ImGui::SameLine(); ImGui::Text("$%06X", bp.address); ImGui::SameLine(); ImGui::TextDisabled("(hits: %d)", bp.hit_count); ImGui::SameLine(); if (ImGui::SmallButton(absl::StrFormat(ICON_MD_DELETE "##%d", bp.id).c_str())) { breakpoint_manager_.RemoveBreakpoint(bp.id); } } } ImGui::EndChild(); ImGui::Separator(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "CPU Status"); ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##CpuStatus", ImVec2(0, 200), true); // Compact register display in a table if (ImGui::BeginTable( "Registers", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("A"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X", snes_.cpu().A); ImGui::TableNextColumn(); ImGui::Text("D"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X", snes_.cpu().D); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("X"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X", snes_.cpu().X); ImGui::TableNextColumn(); ImGui::Text("DB"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.cpu().DB); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Y"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X", snes_.cpu().Y); ImGui::TableNextColumn(); ImGui::Text("PB"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.cpu().PB); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("PC"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.success), "0x%04X", snes_.cpu().PC); ImGui::TableNextColumn(); ImGui::Text("SP"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.memory().mutable_sp()); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("PS"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.warning), "0x%02X", snes_.cpu().status); ImGui::TableNextColumn(); ImGui::Text("Cycle"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.info), "%llu", snes_.mutable_cycles()); ImGui::EndTable(); } ImGui::EndChild(); ImGui::PopStyleColor(); // SPC700 Status Panel ImGui::TextColored(ConvertColorToImVec4(theme.accent), "SPC700 Status"); ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##SpcStatus", ImVec2(0, 160), true); if (ImGui::BeginTable( "SPCRegisters", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 50); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 50); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("A"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.apu().spc700().A); ImGui::TableNextColumn(); ImGui::Text("PC"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.success), "0x%04X", snes_.apu().spc700().PC); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("X"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.apu().spc700().X); ImGui::TableNextColumn(); ImGui::Text("SP"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.apu().spc700().SP); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Y"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%02X", snes_.apu().spc700().Y); ImGui::TableNextColumn(); ImGui::Text("PSW"); ImGui::TableNextColumn(); ImGui::TextColored( ConvertColorToImVec4(theme.warning), "0x%02X", snes_.apu().spc700().FlagsToByte(snes_.apu().spc700().PSW)); ImGui::EndTable(); } ImGui::EndChild(); ImGui::PopStyleColor(); // New Disassembly Viewer if (ImGui::CollapsingHeader("Disassembly Viewer", ImGuiTreeNodeFlags_DefaultOpen)) { uint32_t current_pc = (static_cast(snes_.cpu().PB) << 16) | snes_.cpu().PC; auto& disasm = snes_.cpu().disassembly_viewer(); if (disasm.IsAvailable()) { disasm.Render(current_pc, snes_.cpu().breakpoints_); } else { ImGui::TextColored(ConvertColorToImVec4(theme.error), "Disassembly viewer unavailable."); } } } catch (const std::exception& e) { // Ensure any pushed styles are popped on error try { ImGui::PopStyleColor(); } catch (...) { // Ignore PopStyleColor errors } ImGui::Text("CPU Debugger Error: %s", e.what()); } } void Emulator::RenderPerformanceMonitor() { // Delegate to UI layer ui::RenderPerformanceMonitor(this); } void Emulator::RenderAIAgentPanel() { // Delegate to UI layer ui::RenderAIAgentPanel(this); } void Emulator::RenderCpuInstructionLog( const std::vector& instruction_log) { // Delegate to UI layer (legacy log deprecated) ui::RenderCpuInstructionLog(this, instruction_log.size()); } void Emulator::RenderSaveStates() { // TODO: Create ui::RenderSaveStates() when save state system is implemented auto& theme_manager = gui::ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); ImGui::TextColored(ConvertColorToImVec4(theme.warning), ICON_MD_SAVE " Save States - Coming Soon"); ImGui::TextWrapped("Save state functionality will be implemented here."); } void Emulator::RenderKeyboardConfig() { // Delegate to the input manager UI ui::RenderKeyboardConfig(&input_manager_); } void Emulator::RenderApuDebugger() { // Delegate to UI layer ui::RenderApuDebugger(this); } } // namespace emu } // namespace yaze