Files
yaze/src/app/emu/emulator.cc
scawful 97c75e074d feat: Add Known Issues and Next Steps for G3 Renderer Migration
- Documented known issues specific to macOS, including crashes during window resizing and occasional loading indicators during texture processing.
- Outlined high, medium, and low priority stability improvements for future sessions, focusing on texture processing, event handling, and resource management.
- Updated the document version and added testing recommendations to ensure thorough validation before the next major change.
2025-10-07 18:35:14 -04:00

1349 lines
42 KiB
C++

#include "app/emu/emulator.h"
#include <cstdint>
#include <fstream>
#include <vector>
#include "app/core/window.h"
namespace yaze::core {
extern bool g_window_is_resizing;
}
#include "app/emu/cpu/internal/opcodes.h"
#include "app/emu/debug/disassembly_viewer.h"
#include "app/gui/color.h"
#include "app/gui/editor_layout.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/theme_manager.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include "util/file_util.h"
namespace yaze {
namespace emu {
namespace {
bool ShouldDisplay(const InstructionEntry& entry, const char* filter) {
if (filter[0] == '\0') {
return true;
}
// Supported fields: address, opcode, operands
if (entry.operands.find(filter) != std::string::npos) {
return true;
}
if (absl::StrFormat("%06X", entry.address).find(filter) !=
std::string::npos) {
return true;
}
if (opcode_to_mnemonic.at(entry.opcode).find(filter) != std::string::npos) {
return true;
}
return false;
}
} // namespace
using ImGui::SameLine;
using ImGui::Separator;
using ImGui::TableNextColumn;
using ImGui::Text;
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;
// Reset state for new ROM
running_ = false;
snes_initialized_ = false;
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 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: 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_) {
HandleEvents();
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 with improved buffering
snes_.SetSamples(audio_buffer_, wanted_samples_);
uint32_t queued = SDL_GetQueuedAudioSize(audio_device_);
uint32_t target_buffer = wanted_samples_ * 4 * 2; // Target 2 frames buffered
uint32_t max_buffer = wanted_samples_ * 4 * 6; // Max 6 frames
if (queued < target_buffer) {
// Buffer is low, queue more audio
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
} else if (queued > max_buffer) {
// Buffer is too full, clear it to prevent lag
SDL_ClearQueuedAudio(audio_device_);
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
}
// 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_);
}
}
}
}
}
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;
// Create session-aware cards
gui::EditorCard cpu_card(ICON_MD_MEMORY " CPU Debugger", ICON_MD_MEMORY);
gui::EditorCard ppu_card(ICON_MD_VIDEOGAME_ASSET " PPU Display",
ICON_MD_VIDEOGAME_ASSET);
gui::EditorCard memory_card(ICON_MD_DATA_ARRAY " Memory Viewer",
ICON_MD_DATA_ARRAY);
gui::EditorCard breakpoints_card(ICON_MD_BUG_REPORT " Breakpoints",
ICON_MD_BUG_REPORT);
gui::EditorCard performance_card(ICON_MD_SPEED " Performance",
ICON_MD_SPEED);
gui::EditorCard ai_card(ICON_MD_SMART_TOY " AI Agent", ICON_MD_SMART_TOY);
gui::EditorCard save_states_card(ICON_MD_SAVE " Save States", ICON_MD_SAVE);
gui::EditorCard keyboard_card(ICON_MD_KEYBOARD " Keyboard Config",
ICON_MD_KEYBOARD);
// 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);
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();
}
} 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() {
ImVec2 size = ImVec2(512, 480);
if (snes_.running()) {
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 480), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
ImGui::Image((ImTextureID)(intptr_t)ppu_texture_, size, ImVec2(0, 0),
ImVec2(1, 1));
ImGui::EndChild();
} else {
ImGui::Text("Emulator output not available.");
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 480), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f);
ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f);
ImGui::Dummy(size);
ImGui::EndChild();
}
ImGui::Separator();
}
void Emulator::RenderNavBar() {
if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
running_ = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Start Emulation");
}
SameLine();
if (ImGui::Button(ICON_MD_PAUSE)) {
running_ = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Pause Emulation");
}
SameLine();
if (ImGui::Button(ICON_MD_SKIP_NEXT)) {
// Step through Code logic
snes_.cpu().RunOpcode();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Step Through Code");
}
SameLine();
if (ImGui::Button(ICON_MD_REFRESH)) {
// Reset Emulator logic
snes_.Reset(true);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reset Emulator");
}
SameLine();
if (ImGui::Button(ICON_MD_STOP)) {
// Stop Emulation logic
running_ = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Stop Emulation");
}
SameLine();
if (ImGui::Button(ICON_MD_SAVE)) {
// Save State logic
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Save State");
}
SameLine();
if (ImGui::Button(ICON_MD_SYSTEM_UPDATE_ALT)) {}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Load State");
}
// Additional elements
SameLine();
if (ImGui::Button(ICON_MD_SETTINGS)) {
// Settings logic
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Settings");
}
static bool open_file = false;
SameLine();
if (ImGui::Button(ICON_MD_INFO)) {
open_file = true;
// About Debugger logic
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("About Debugger");
}
SameLine();
ImGui::Checkbox("Logging", snes_.cpu().mutable_log_instructions());
SameLine();
ImGui::Checkbox("Turbo", &turbo_mode_);
// Display FPS and Audio Status
SameLine();
ImGui::Text("|");
SameLine();
if (current_fps_ > 0) {
ImGui::Text("FPS: %.1f", current_fps_);
} else {
ImGui::Text("FPS: --");
}
SameLine();
uint32_t audio_queued = SDL_GetQueuedAudioSize(audio_device_);
uint32_t audio_frames = audio_queued / (wanted_samples_ * 4);
ImGui::Text("| Audio: %u frames", audio_frames);
static bool show_memory_viewer = false;
SameLine();
if (ImGui::Button(ICON_MD_MEMORY)) {
show_memory_viewer = !show_memory_viewer;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Memory Viewer");
}
if (show_memory_viewer) {
ImGui::Begin("Memory Viewer", &show_memory_viewer);
RenderMemoryViewer();
ImGui::End();
}
if (open_file) {
auto file_name = util::FileDialogWrapper::ShowOpenFileDialog();
if (!file_name.empty()) {
std::ifstream file(file_name, std::ios::binary);
// Load the data directly into rom_data
rom_data_.assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
snes_.Init(rom_data_);
open_file = false;
}
}
}
void Emulator::HandleEvents() {
// Handle user input events
if (ImGui::IsKeyPressed(keybindings_.a_button)) {
snes_.SetButtonState(1, 0, true);
}
if (ImGui::IsKeyPressed(keybindings_.b_button)) {
snes_.SetButtonState(1, 1, true);
}
if (ImGui::IsKeyPressed(keybindings_.select_button)) {
snes_.SetButtonState(1, 2, true);
}
if (ImGui::IsKeyPressed(keybindings_.start_button)) {
snes_.SetButtonState(1, 3, true);
}
if (ImGui::IsKeyPressed(keybindings_.up_button)) {
snes_.SetButtonState(1, 4, true);
}
if (ImGui::IsKeyPressed(keybindings_.down_button)) {
snes_.SetButtonState(1, 5, true);
}
if (ImGui::IsKeyPressed(keybindings_.left_button)) {
snes_.SetButtonState(1, 6, true);
}
if (ImGui::IsKeyPressed(keybindings_.right_button)) {
snes_.SetButtonState(1, 7, true);
}
if (ImGui::IsKeyPressed(keybindings_.x_button)) {
snes_.SetButtonState(1, 8, true);
}
if (ImGui::IsKeyPressed(keybindings_.y_button)) {
snes_.SetButtonState(1, 9, true);
}
if (ImGui::IsKeyPressed(keybindings_.l_button)) {
snes_.SetButtonState(1, 10, true);
}
if (ImGui::IsKeyPressed(keybindings_.r_button)) {
snes_.SetButtonState(1, 11, true);
}
if (ImGui::IsKeyReleased(keybindings_.a_button)) {
snes_.SetButtonState(1, 0, false);
}
if (ImGui::IsKeyReleased(keybindings_.b_button)) {
snes_.SetButtonState(1, 1, false);
}
if (ImGui::IsKeyReleased(keybindings_.select_button)) {
snes_.SetButtonState(1, 2, false);
}
if (ImGui::IsKeyReleased(keybindings_.start_button)) {
snes_.SetButtonState(1, 3, false);
}
if (ImGui::IsKeyReleased(keybindings_.up_button)) {
snes_.SetButtonState(1, 4, false);
}
if (ImGui::IsKeyReleased(keybindings_.down_button)) {
snes_.SetButtonState(1, 5, false);
}
if (ImGui::IsKeyReleased(keybindings_.left_button)) {
snes_.SetButtonState(1, 6, false);
}
if (ImGui::IsKeyReleased(keybindings_.right_button)) {
snes_.SetButtonState(1, 7, false);
}
if (ImGui::IsKeyReleased(keybindings_.x_button)) {
snes_.SetButtonState(1, 8, false);
}
if (ImGui::IsKeyReleased(keybindings_.y_button)) {
snes_.SetButtonState(1, 9, false);
}
if (ImGui::IsKeyReleased(keybindings_.l_button)) {
snes_.SetButtonState(1, 10, false);
}
if (ImGui::IsKeyReleased(keybindings_.r_button)) {
snes_.SetButtonState(1, 11, false);
}
}
void Emulator::RenderBreakpointList() {
if (ImGui::Button("Set SPC PC")) {
snes_.apu().spc700().PC = 0xFFEF;
}
Separator();
Text("Breakpoints");
Separator();
static char breakpoint_input[10] = "";
static int current_memory_mode = 0;
static bool read_mode = false;
static bool write_mode = false;
static bool execute_mode = false;
if (ImGui::Combo("##TypeOfMemory", &current_memory_mode, "PRG\0RAM\0")) {}
ImGui::Checkbox("Read", &read_mode);
SameLine();
ImGui::Checkbox("Write", &write_mode);
SameLine();
ImGui::Checkbox("Execute", &execute_mode);
// Breakpoint input fields and buttons
if (ImGui::InputText("##BreakpointInput", breakpoint_input, 10,
ImGuiInputTextFlags_EnterReturnsTrue)) {
int breakpoint = std::stoi(breakpoint_input, nullptr, 16);
snes_.cpu().SetBreakpoint(breakpoint);
memset(breakpoint_input, 0, sizeof(breakpoint_input));
}
SameLine();
if (ImGui::Button("Add")) {
int breakpoint = std::stoi(breakpoint_input, nullptr, 16);
snes_.cpu().SetBreakpoint(breakpoint);
memset(breakpoint_input, 0, sizeof(breakpoint_input));
}
SameLine();
if (ImGui::Button("Clear")) {
snes_.cpu().ClearBreakpoints();
}
Separator();
auto breakpoints = snes_.cpu().GetBreakpoints();
if (!breakpoints.empty()) {
Text("Breakpoints:");
ImGui::BeginChild("BreakpointsList", ImVec2(0, 100), true);
for (auto breakpoint : breakpoints) {
if (ImGui::Selectable(absl::StrFormat("0x%04X", breakpoint).c_str())) {
// Jump to breakpoint
// snes_.cpu().JumpToBreakpoint(breakpoint);
}
}
ImGui::EndChild();
}
Separator();
gui::InputHexByte("PB", &manual_pb_, 50.f);
gui::InputHexWord("PC", &manual_pc_, 75.f);
if (ImGui::Button("Set Current Address")) {
snes_.cpu().PC = manual_pc_;
snes_.cpu().PB = manual_pb_;
}
}
void Emulator::RenderMemoryViewer() {
static MemoryEditor ram_edit;
static MemoryEditor aram_edit;
static MemoryEditor mem_edit;
if (ImGui::BeginTable("MemoryViewerTable", 4,
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
ImGui::TableSetupColumn("Bookmarks");
ImGui::TableSetupColumn("RAM");
ImGui::TableSetupColumn("ARAM");
ImGui::TableSetupColumn("ROM");
ImGui::TableHeadersRow();
TableNextColumn();
if (ImGui::CollapsingHeader("Bookmarks", ImGuiTreeNodeFlags_DefaultOpen)) {
// Input for adding a new bookmark
static char nameBuf[256];
static uint64_t uint64StringBuf;
ImGui::InputText("Name", nameBuf, IM_ARRAYSIZE(nameBuf));
gui::InputHex("Address", &uint64StringBuf);
if (ImGui::Button("Add Bookmark")) {
bookmarks.push_back({nameBuf, uint64StringBuf});
memset(nameBuf, 0, sizeof(nameBuf));
uint64StringBuf = 0;
}
// Tree view of bookmarks
for (const auto& bookmark : bookmarks) {
if (ImGui::TreeNode(bookmark.name.c_str(), ICON_MD_STAR)) {
auto bookmark_string = absl::StrFormat(
"%s: 0x%08X", bookmark.name.c_str(), bookmark.value);
if (ImGui::Selectable(bookmark_string.c_str())) {
mem_edit.GotoAddrAndHighlight(static_cast<ImU64>(bookmark.value),
1);
}
SameLine();
if (ImGui::Button("Delete")) {
// Logic to delete the bookmark
bookmarks.erase(std::remove_if(bookmarks.begin(), bookmarks.end(),
[&](const Bookmark& b) {
return b.name == bookmark.name &&
b.value == bookmark.value;
}),
bookmarks.end());
}
ImGui::TreePop();
}
}
}
TableNextColumn();
if (ImGui::BeginChild("RAM", ImVec2(0, 0), true,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse)) {
ram_edit.DrawContents((void*)snes_.get_ram(), 0x20000);
ImGui::EndChild();
}
TableNextColumn();
if (ImGui::BeginChild("ARAM", ImVec2(0, 0), true,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse)) {
aram_edit.DrawContents((void*)snes_.apu().ram.data(),
snes_.apu().ram.size());
ImGui::EndChild();
}
TableNextColumn();
if (ImGui::BeginChild("ROM", ImVec2(0, 0), true,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse)) {
mem_edit.DrawContents((void*)snes_.memory().rom_.data(),
snes_.memory().rom_.size());
ImGui::EndChild();
}
ImGui::EndTable();
}
}
void Emulator::RenderModernCpuDebugger() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "CPU Status");
ImGui::PushStyleColor(ImGuiCol_ChildBg,
ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##CpuStatus", ImVec2(0, 120), 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, 80), 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() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg,
ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##PerformanceMonitor", ImVec2(0, 0), true);
// Performance Metrics
if (ImGui::CollapsingHeader("Real-time Metrics",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Columns(2, "PerfColumns");
// Frame Rate
ImGui::Text("Frame Rate:");
ImGui::SameLine();
if (current_fps_ > 0) {
ImVec4 fps_color = (current_fps_ >= 59.0 && current_fps_ <= 61.0)
? ConvertColorToImVec4(theme.success)
: ConvertColorToImVec4(theme.error);
ImGui::TextColored(fps_color, "%.1f FPS", current_fps_);
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.warning), "-- FPS");
}
// Audio Status
uint32_t audio_queued = SDL_GetQueuedAudioSize(audio_device_);
uint32_t audio_frames = audio_queued / (wanted_samples_ * 4);
ImGui::Text("Audio Queue:");
ImGui::SameLine();
ImVec4 audio_color = (audio_frames >= 2 && audio_frames <= 6)
? ConvertColorToImVec4(theme.success)
: ConvertColorToImVec4(theme.warning);
ImGui::TextColored(audio_color, "%u frames", audio_frames);
ImGui::NextColumn();
// Timing
double frame_time = (current_fps_ > 0) ? (1000.0 / current_fps_) : 0.0;
ImGui::Text("Frame Time:");
ImGui::SameLine();
ImGui::TextColored(ConvertColorToImVec4(theme.info), "%.2f ms",
frame_time);
// Emulation State
ImGui::Text("State:");
ImGui::SameLine();
ImVec4 state_color = running_ ? ConvertColorToImVec4(theme.success)
: ConvertColorToImVec4(theme.warning);
ImGui::TextColored(state_color, "%s", running_ ? "Running" : "Paused");
ImGui::Columns(1);
}
// Memory Usage
if (ImGui::CollapsingHeader("Memory Usage")) {
ImGui::Text("ROM Size: %zu bytes", rom_data_.size());
ImGui::Text("RAM Usage: %d KB", 128); // SNES RAM is 128KB
ImGui::Text("VRAM Usage: %d KB", 64); // SNES VRAM is 64KB
}
ImGui::EndChild();
ImGui::PopStyleColor();
} catch (const std::exception& e) {
// Ensure any pushed styles are popped on error
try {
ImGui::PopStyleColor();
} catch (...) {
// Ignore PopStyleColor errors
}
ImGui::Text("Performance Monitor Error: %s", e.what());
}
}
void Emulator::RenderAIAgentPanel() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg,
ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##AIAgentPanel", ImVec2(0, 0), true);
// AI Agent Status
if (ImGui::CollapsingHeader("Agent Status",
ImGuiTreeNodeFlags_DefaultOpen)) {
auto metrics = GetMetrics();
ImGui::Columns(2, "AgentColumns");
// Emulator Readiness
ImGui::Text("Emulator Ready:");
ImGui::SameLine();
ImVec4 ready_color = IsEmulatorReady()
? ConvertColorToImVec4(theme.success)
: ConvertColorToImVec4(theme.error);
ImGui::TextColored(ready_color, "%s", IsEmulatorReady() ? "Yes" : "No");
// Current State
ImGui::Text("Current PC:");
ImGui::SameLine();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "0x%04X:%02X",
metrics.cpu_pc, metrics.cpu_pb);
ImGui::NextColumn();
// Performance
ImGui::Text("FPS:");
ImGui::SameLine();
ImVec4 fps_color = (metrics.fps >= 59.0)
? ConvertColorToImVec4(theme.success)
: ConvertColorToImVec4(theme.warning);
ImGui::TextColored(fps_color, "%.1f", metrics.fps);
// Cycles
ImGui::Text("Cycles:");
ImGui::SameLine();
ImGui::TextColored(ConvertColorToImVec4(theme.info), "%llu",
metrics.cycles);
ImGui::Columns(1);
}
// AI Agent Controls
if (ImGui::CollapsingHeader("Agent Controls",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Columns(2, "ControlColumns");
// Single Step Control
if (ImGui::Button("Step Instruction", ImVec2(-1, 30))) {
StepSingleInstruction();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Execute a single CPU instruction");
}
// Breakpoint Controls
static char bp_input[10] = "";
ImGui::InputText("Breakpoint Address", bp_input, IM_ARRAYSIZE(bp_input));
if (ImGui::Button("Add Breakpoint", ImVec2(-1, 25))) {
if (strlen(bp_input) > 0) {
uint32_t addr = std::stoi(bp_input, nullptr, 16);
SetBreakpoint(addr);
memset(bp_input, 0, sizeof(bp_input));
}
}
ImGui::NextColumn();
// Clear All Breakpoints
if (ImGui::Button("Clear All Breakpoints", ImVec2(-1, 30))) {
ClearAllBreakpoints();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Remove all active breakpoints");
}
// Toggle Emulation
if (ImGui::Button(running_ ? "Pause Emulation" : "Resume Emulation",
ImVec2(-1, 30))) {
running_ = !running_;
}
ImGui::Columns(1);
}
// Current Breakpoints
if (ImGui::CollapsingHeader("Active Breakpoints")) {
auto breakpoints = GetBreakpoints();
if (breakpoints.empty()) {
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
"No breakpoints set");
} else {
ImGui::BeginChild("BreakpointsList", ImVec2(0, 150), true);
for (auto bp : breakpoints) {
if (ImGui::Selectable(absl::StrFormat("0x%04X", bp).c_str())) {
// Jump to breakpoint or remove it
}
ImGui::SameLine();
if (ImGui::SmallButton(absl::StrFormat("Remove##%04X", bp).c_str())) {
// TODO: Implement individual breakpoint removal
}
}
ImGui::EndChild();
}
}
// AI Agent API Information
if (ImGui::CollapsingHeader("API Reference")) {
ImGui::TextWrapped("Available API functions for AI agents:");
ImGui::BulletText("IsEmulatorReady() - Check if emulator is ready");
ImGui::BulletText("GetMetrics() - Get current performance metrics");
ImGui::BulletText(
"StepSingleInstruction() - Execute one CPU instruction");
ImGui::BulletText("SetBreakpoint(address) - Set breakpoint at address");
ImGui::BulletText("ClearAllBreakpoints() - Remove all breakpoints");
ImGui::BulletText("GetBreakpoints() - Get list of active breakpoints");
}
ImGui::EndChild();
ImGui::PopStyleColor();
} catch (const std::exception& e) {
// Ensure any pushed styles are popped on error
try {
ImGui::PopStyleColor();
} catch (...) {
// Ignore PopStyleColor errors
}
ImGui::Text("AI Agent Panel Error: %s", e.what());
}
}
void Emulator::RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instruction_log) {
// Filtering options
static char filter[256];
ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter));
// Instruction list
ImGui::BeginChild("InstructionList", ImVec2(0, 0), ImGuiChildFlags_None);
for (const auto& entry : instruction_log) {
if (ShouldDisplay(entry, filter)) {
if (ImGui::Selectable(absl::StrFormat("%06X:", entry.address).c_str())) {
// Logic to handle click (e.g., jump to address, set breakpoint)
}
ImGui::SameLine();
ImVec4 color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
ImGui::TextColored(color, "%s",
opcode_to_mnemonic.at(entry.opcode).c_str());
ImVec4 operand_color = ImVec4(0.7f, 0.5f, 0.3f, 1.0f);
ImGui::SameLine();
ImGui::TextColored(operand_color, "%s", entry.operands.c_str());
}
}
// Jump to the bottom of the child scrollbar
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
ImGui::SetScrollHereY(1.0f);
}
ImGui::EndChild();
}
void Emulator::RenderSaveStates() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg,
ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##SaveStates", ImVec2(0, 0), true);
// Save State Management
if (ImGui::CollapsingHeader("Quick Save/Load",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Columns(2, "SaveStateColumns");
// Save slots
for (int i = 1; i <= 4; ++i) {
if (ImGui::Button(absl::StrFormat("Save Slot %d", i).c_str(),
ImVec2(-1, 30))) {
// TODO: Implement save state to slot
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Save current state to slot %d (F%d)", i, i);
}
}
ImGui::NextColumn();
// Load slots
for (int i = 1; i <= 4; ++i) {
if (ImGui::Button(absl::StrFormat("Load Slot %d", i).c_str(),
ImVec2(-1, 30))) {
// TODO: Implement load state from slot
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Load state from slot %d (Shift+F%d)", i, i);
}
}
ImGui::Columns(1);
}
// File-based save states
if (ImGui::CollapsingHeader("File-based Saves")) {
static char save_name[256] = "";
ImGui::InputText("Save Name", save_name, IM_ARRAYSIZE(save_name));
if (ImGui::Button("Save to File", ImVec2(-1, 30))) {
// TODO: Implement save to file
}
if (ImGui::Button("Load from File", ImVec2(-1, 30))) {
// TODO: Implement load from file
}
}
// Rewind functionality
if (ImGui::CollapsingHeader("Rewind")) {
ImGui::TextWrapped(
"Rewind functionality allows you to step back through recent "
"gameplay.");
static bool rewind_enabled = false;
ImGui::Checkbox("Enable Rewind (uses more memory)", &rewind_enabled);
if (rewind_enabled) {
if (ImGui::Button("Rewind 1 Second", ImVec2(-1, 30))) {
// TODO: Implement rewind
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Rewind gameplay by 1 second (Backquote key)");
}
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
"Enable rewind to use this feature");
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
} catch (const std::exception& e) {
try {
ImGui::PopStyleColor();
} catch (...) {}
ImGui::Text("Save States Error: %s", e.what());
}
}
void Emulator::RenderKeyboardConfig() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg,
ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##KeyboardConfig", ImVec2(0, 0), true);
// Keyboard Configuration
if (ImGui::CollapsingHeader("SNES Controller Mapping",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::TextWrapped("Click on a button and press a key to remap it.");
ImGui::Separator();
if (ImGui::BeginTable("KeyboardTable", 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Button", ImGuiTableColumnFlags_WidthFixed,
120);
ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
auto DrawKeyBinding = [&](const char* label, ImGuiKey& key) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "%s", label);
ImGui::TableNextColumn();
std::string button_label =
absl::StrFormat("%s##%s", ImGui::GetKeyName(key), label);
if (ImGui::Button(button_label.c_str(), ImVec2(-1, 0))) {
// TODO: Implement key remapping
ImGui::OpenPopup(absl::StrFormat("Remap%s", label).c_str());
}
if (ImGui::BeginPopup(absl::StrFormat("Remap%s", label).c_str())) {
ImGui::Text("Press any key...");
// TODO: Detect key press and update binding
ImGui::EndPopup();
}
};
DrawKeyBinding("A Button", keybindings_.a_button);
DrawKeyBinding("B Button", keybindings_.b_button);
DrawKeyBinding("X Button", keybindings_.x_button);
DrawKeyBinding("Y Button", keybindings_.y_button);
DrawKeyBinding("L Button", keybindings_.l_button);
DrawKeyBinding("R Button", keybindings_.r_button);
DrawKeyBinding("Start", keybindings_.start_button);
DrawKeyBinding("Select", keybindings_.select_button);
DrawKeyBinding("Up", keybindings_.up_button);
DrawKeyBinding("Down", keybindings_.down_button);
DrawKeyBinding("Left", keybindings_.left_button);
DrawKeyBinding("Right", keybindings_.right_button);
ImGui::EndTable();
}
}
// Emulator Hotkeys
if (ImGui::CollapsingHeader("Emulator Hotkeys")) {
ImGui::TextWrapped("System-level keyboard shortcuts:");
ImGui::Separator();
ImGui::BulletText("F1-F4: Quick save to slot 1-4");
ImGui::BulletText("Shift+F1-F4: Quick load from slot 1-4");
ImGui::BulletText("Backquote (`): Rewind gameplay");
ImGui::BulletText("Tab: Fast forward (turbo mode)");
ImGui::BulletText("Pause/Break: Pause/Resume emulation");
ImGui::BulletText("F12: Take screenshot");
}
// Reset to defaults
if (ImGui::Button("Reset to Defaults", ImVec2(-1, 35))) {
keybindings_ = EmulatorKeybindings();
}
ImGui::EndChild();
ImGui::PopStyleColor();
} catch (const std::exception& e) {
try {
ImGui::PopStyleColor();
} catch (...) {}
ImGui::Text("Keyboard Config Error: %s", e.what());
}
}
} // namespace emu
} // namespace yaze