Files
yaze/src/app/emu/emulator.cc
scawful fdb0f18d6a feat: Enhance emulator functionality with card registration and UI improvements
- Registered multiple emulator cards (CPU Debugger, Memory Viewer, PPU Viewer, Audio Mixer) with the EditorCardManager for improved access and organization.
- Updated EditorManager to support the new Emulator editor type in context-sensitive card controls.
- Improved GraphicsEditor loading process with enhanced logging and palette management for graphics sheets.
- Refactored MessageEditor to streamline font bitmap creation and texture queuing, ensuring better performance and clarity in the rendering loop.
2025-10-09 10:54:11 -04:00

817 lines
28 KiB
C++

#include "app/emu/emulator.h"
#include <cstdint>
#include <fstream>
#include <vector>
#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<uint8_t>& 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<uint8_t>& 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<uint8_t*>(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<uint32_t>(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<InstructionEntry>& 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