feat: Add Emulator Editor Type and UI Enhancements

- Introduced a new editor type for the emulator, allowing users to access emulator functionalities directly from the editor manager.
- Updated the editor selection dialog to include the emulator option with appropriate descriptions and shortcuts.
- Enhanced the emulator interface with modern theming and improved layout for better user experience.
- Implemented various UI components for performance monitoring, AI agent integration, and save state management, enriching the emulator's capabilities.
This commit is contained in:
scawful
2025-10-06 21:18:56 -04:00
parent fdb817987f
commit 14eba4a765
6 changed files with 656 additions and 96 deletions

View File

@@ -53,6 +53,7 @@ struct EditorContext {
enum class EditorType { enum class EditorType {
kAssembly, kAssembly,
kDungeon, kDungeon,
kEmulator,
kGraphics, kGraphics,
kMusic, kMusic,
kOverworld, kOverworld,
@@ -65,9 +66,9 @@ enum class EditorType {
kSettings, kSettings,
}; };
constexpr std::array<const char*, 12> kEditorNames = { constexpr std::array<const char*, 13> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music", "Overworld", "Assembly", "Dungeon", "Emulator", "Graphics", "Music", "Overworld",
"Palette", "Screen", "Sprite", "Message", "Hex", "Agent", "Settings", "Palette", "Screen", "Sprite", "Message", "Hex", "Agent", "Settings",
}; };
/** /**

View File

@@ -468,6 +468,9 @@ void EditorManager::Initialize(const std::string& filename) {
case EditorType::kAssembly: case EditorType::kAssembly:
show_asm_editor_ = true; show_asm_editor_ = true;
break; break;
case EditorType::kEmulator:
show_emulator_ = true;
break;
case EditorType::kSettings: case EditorType::kSettings:
current_editor_set_->settings_editor_.set_active(true); current_editor_set_->settings_editor_.set_active(true);
break; break;

View File

@@ -56,6 +56,10 @@ EditorSelectionDialog::EditorSelectionDialog() {
"Direct ROM memory editing and comparison", "Ctrl+0", false, true, "Direct ROM memory editing and comparison", "Ctrl+0", false, true,
ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green
{EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET,
"Test and debug your ROM in real-time with live debugging", "Ctrl+Shift+E", false, true,
ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue
{EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY, {EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY,
"Configure AI agent, collaboration, and automation", "Ctrl+Shift+A", false, false, "Configure AI agent, collaboration, and automation", "Ctrl+Shift+A", false, false,
ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta
@@ -279,9 +283,7 @@ void EditorSelectionDialog::DrawEditorCard(const EditorInfo& info, int index) {
// Draw shortcut hint if available // Draw shortcut hint if available
if (info.shortcut && info.shortcut[0]) { if (info.shortcut && info.shortcut[0]) {
ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut);
ImGui::Text("%s", info.shortcut);
ImGui::PopStyleColor();
} }
// Hover glow effect // Hover glow effect

View File

@@ -9,6 +9,9 @@
#include "app/emu/cpu/internal/opcodes.h" #include "app/emu/cpu/internal/opcodes.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/theme_manager.h"
#include "app/gui/color.h"
#include "app/gui/editor_layout.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "imgui_memory_editor.h" #include "imgui_memory_editor.h"
@@ -40,7 +43,6 @@ bool ShouldDisplay(const InstructionEntry& entry, const char* filter) {
} // namespace } // namespace
using ImGui::NextColumn;
using ImGui::SameLine; using ImGui::SameLine;
using ImGui::Separator; using ImGui::Separator;
using ImGui::TableNextColumn; using ImGui::TableNextColumn;
@@ -50,6 +52,7 @@ void Emulator::Run(Rom* rom) {
static bool loaded = false; static bool loaded = false;
if (!snes_.running() && rom->is_loaded()) { if (!snes_.running() && rom->is_loaded()) {
// Use ARGB8888 format to match PPU output (XBGR layout with format=1) // Use ARGB8888 format to match PPU output (XBGR layout with format=1)
// Add better error handling and texture optimization
ppu_texture_ = SDL_CreateTexture(core::Renderer::Get().renderer(), ppu_texture_ = SDL_CreateTexture(core::Renderer::Get().renderer(),
SDL_PIXELFORMAT_ARGB8888, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, 512, 480); SDL_TEXTUREACCESS_STREAMING, 512, 480);
@@ -57,6 +60,9 @@ void Emulator::Run(Rom* rom) {
printf("Failed to create texture: %s\n", SDL_GetError()); printf("Failed to create texture: %s\n", SDL_GetError());
return; return;
} }
// Optimize texture for better performance
SDL_SetTextureBlendMode(ppu_texture_, SDL_BLENDMODE_NONE);
rom_data_ = rom->vector(); rom_data_ = rom->vector();
snes_.Init(rom_data_); snes_.Init(rom_data_);
@@ -85,9 +91,9 @@ void Emulator::Run(Rom* rom) {
double seconds = delta / (double)count_frequency; double seconds = delta / (double)count_frequency;
time_adder += seconds; time_adder += seconds;
// Cap time accumulation to prevent spiral of death // Cap time accumulation to prevent spiral of death and improve stability
if (time_adder > wanted_frames_ * 5.0) { if (time_adder > wanted_frames_ * 3.0) {
time_adder = wanted_frames_ * 5.0; time_adder = wanted_frames_ * 3.0;
} }
// allow 2 ms earlier, to prevent skipping due to being just below wanted // allow 2 ms earlier, to prevent skipping due to being just below wanted
@@ -110,21 +116,27 @@ void Emulator::Run(Rom* rom) {
fps_timer_ = 0.0; fps_timer_ = 0.0;
} }
// Generate and queue audio samples // Generate and queue audio samples with better buffering
snes_.SetSamples(audio_buffer_, wanted_samples_); snes_.SetSamples(audio_buffer_, wanted_samples_);
uint32_t queued = SDL_GetQueuedAudioSize(audio_device_); uint32_t queued = SDL_GetQueuedAudioSize(audio_device_);
// Keep audio buffer filled but not overflowing (max 6 frames worth) uint32_t max_buffer_size = wanted_samples_ * 4 * 4; // Reduced from 6 to 4 frames
if (queued <= wanted_samples_ * 4 * 6) {
// Only queue audio if buffer is not too full to prevent audio lag
if (queued <= max_buffer_size) {
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4); SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
} else {
// Clear excess audio to prevent audio lag buildup
SDL_ClearQueuedAudio(audio_device_);
} }
// Update PPU texture // Update PPU texture with better error handling
void* ppu_pixels_; void* ppu_pixels_;
int ppu_pitch_; int ppu_pitch_;
if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) != if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) !=
0) { 0) {
printf("Failed to lock texture: %s\n", SDL_GetError()); printf("Failed to lock texture: %s\n", SDL_GetError());
return; // Don't return here, continue with next frame to prevent deadlock
continue;
} }
snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels_)); snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels_));
SDL_UnlockTexture(ppu_texture_); SDL_UnlockTexture(ppu_texture_);
@@ -141,71 +153,133 @@ void Emulator::Run(Rom* rom) {
} }
void Emulator::RenderEmulatorInterface() { void Emulator::RenderEmulatorInterface() {
if (ImGui::BeginTable("Emulator", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { // Apply modern theming with safety checks
ImGui::TableSetupColumn("CPU"); try {
ImGui::TableSetupColumn("PPU"); auto& theme_manager = gui::ThemeManager::Get();
ImGui::TableHeadersRow(); const auto& theme = theme_manager.GetCurrentTheme();
// CPU Column // Modern EditorCard-based layout - modular and flexible
ImGui::TableNextColumn(); 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);
// CPU Register Values ppu_card.SetDefaultSize(550, 520);
if (ImGui::CollapsingHeader("Register Values", ImGuiTreeNodeFlags_DefaultOpen)) { ppu_card.SetPosition(gui::EditorCard::Position::Floating);
ImGui::BeginChild("##CpuState", ImVec2(0, 100), ImGuiChildFlags_None,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); memory_card.SetDefaultSize(800, 600);
ImGui::Columns(2, "registersColumns"); memory_card.SetPosition(gui::EditorCard::Position::Floating);
ImGui::Text("A: 0x%04X", snes_.cpu().A); breakpoints_card.SetDefaultSize(400, 350);
ImGui::Text("X: 0x%04X", snes_.cpu().X); breakpoints_card.SetPosition(gui::EditorCard::Position::Right);
ImGui::Text("Y: 0x%04X", snes_.cpu().Y);
ImGui::Text("PC: 0x%04X", snes_.cpu().PC); performance_card.SetDefaultSize(350, 300);
ImGui::Text("SP: 0x%02X", snes_.memory().mutable_sp()); performance_card.SetPosition(gui::EditorCard::Position::Bottom);
ImGui::NextColumn(); ai_card.SetDefaultSize(500, 450);
ai_card.SetPosition(gui::EditorCard::Position::Floating);
ImGui::Text("D: 0x%04X", snes_.cpu().D);
ImGui::Text("DB: 0x%02X", snes_.cpu().DB); save_states_card.SetDefaultSize(400, 300);
ImGui::Text("PB: 0x%02X", snes_.cpu().PB); save_states_card.SetPosition(gui::EditorCard::Position::Floating);
ImGui::Text("PS: 0x%02X", snes_.cpu().status);
ImGui::Text("Cycle: %llu", snes_.mutable_cycles()); keyboard_card.SetDefaultSize(450, 400);
keyboard_card.SetPosition(gui::EditorCard::Position::Floating);
ImGui::Columns(1);
ImGui::EndChild(); cards_configured = true;
}
// CPU Debugger Card
if (show_cpu_debugger_) {
if (cpu_card.Begin(&show_cpu_debugger_)) {
RenderModernCpuDebugger();
} }
cpu_card.End();
// SPC Registers }
if (ImGui::CollapsingHeader("SPC Registers", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::BeginChild("##SpcState", ImVec2(0, 100), ImGuiChildFlags_None, // PPU Display Card
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); if (show_ppu_display_) {
ImGui::Columns(2, "spcRegistersColumns"); if (ppu_card.Begin(&show_ppu_display_)) {
RenderSnesPpu();
ImGui::Text("A: 0x%02X", snes_.apu().spc700().A); }
ImGui::Text("X: 0x%02X", snes_.apu().spc700().X); ppu_card.End();
ImGui::Text("Y: 0x%02X", snes_.apu().spc700().Y); }
ImGui::NextColumn(); // Memory Viewer Card
if (show_memory_viewer_) {
ImGui::Text("PC: 0x%04X", snes_.apu().spc700().PC); if (memory_card.Begin(&show_memory_viewer_)) {
ImGui::Text("SP: 0x%02X", snes_.apu().spc700().SP); RenderMemoryViewer();
ImGui::Text("PSW: 0x%02X", snes_.apu().spc700().FlagsToByte(snes_.apu().spc700().PSW)); }
memory_card.End();
ImGui::Columns(1); }
ImGui::EndChild();
// 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();
} }
// CPU Instruction Log
RenderCpuInstructionLog(snes_.cpu().instruction_log_);
// PPU Column
ImGui::TableNextColumn();
// SNES PPU
RenderSnesPpu();
// Breakpoint List
RenderBreakpointList();
ImGui::EndTable();
} }
} }
@@ -233,22 +307,6 @@ void Emulator::RenderSnesPpu() {
} }
void Emulator::RenderNavBar() { void Emulator::RenderNavBar() {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("Options")) {
if (ImGui::MenuItem("Input")) {
// Input options logic
}
if (ImGui::MenuItem("Audio")) {
// Audio options logic
}
if (ImGui::MenuItem("Video")) {
// Video options logic
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
if (ImGui::Button(ICON_MD_PLAY_ARROW)) { if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
running_ = true; running_ = true;
} }
@@ -619,10 +677,308 @@ void Emulator::RenderMemoryViewer() {
} }
} }
void Emulator::RenderModernCpuDebugger() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
// CPU Status Panel
if (ImGui::CollapsingHeader("CPU Status", ImGuiTreeNodeFlags_DefaultOpen)) {
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
if (ImGui::CollapsingHeader("SPC700 Status", ImGuiTreeNodeFlags_DefaultOpen)) {
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();
}
// CPU Instruction Log
RenderCpuInstructionLog(snes_.cpu().instruction_log_);
} 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( void Emulator::RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instruction_log) { const std::vector<InstructionEntry>& instruction_log) {
if (ImGui::CollapsingHeader("Instruction Log",
ImGuiTreeNodeFlags_DefaultOpen)) {
// Filtering options // Filtering options
static char filter[256]; static char filter[256];
ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter)); ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter));
@@ -652,6 +1008,168 @@ void Emulator::RenderCpuInstructionLog(
} }
ImGui::EndChild(); 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());
} }
} }

View File

@@ -48,6 +48,37 @@ class Emulator {
audio_device_ = audio_device; audio_device_ = audio_device;
} }
auto wanted_samples() const -> int { return wanted_samples_; } auto wanted_samples() const -> int { return wanted_samples_; }
// AI Agent Integration API
bool IsEmulatorReady() const { return snes_.running() && !rom_data_.empty(); }
double GetCurrentFPS() const { return current_fps_; }
uint64_t GetCurrentCycle() { return snes_.mutable_cycles(); }
uint16_t GetCPUPC() { return snes_.cpu().PC; }
uint8_t GetCPUB() { return snes_.cpu().DB; }
void StepSingleInstruction() { snes_.cpu().RunOpcode(); }
void SetBreakpoint(uint32_t address) { snes_.cpu().SetBreakpoint(address); }
void ClearAllBreakpoints() { snes_.cpu().ClearBreakpoints(); }
std::vector<uint32_t> GetBreakpoints() { return snes_.cpu().GetBreakpoints(); }
// Performance monitoring for AI agents
struct EmulatorMetrics {
double fps;
uint64_t cycles;
uint32_t audio_frames_queued;
bool is_running;
uint16_t cpu_pc;
uint8_t cpu_pb;
};
EmulatorMetrics GetMetrics() {
return {
.fps = current_fps_,
.cycles = snes_.mutable_cycles(),
.audio_frames_queued = SDL_GetQueuedAudioSize(audio_device_) / (wanted_samples_ * 4),
.is_running = running_,
.cpu_pc = snes_.cpu().PC,
.cpu_pb = snes_.cpu().PB
};
}
private: private:
void RenderNavBar(); void RenderNavBar();
@@ -57,6 +88,11 @@ class Emulator {
void RenderSnesPpu(); void RenderSnesPpu();
void RenderBreakpointList(); void RenderBreakpointList();
void RenderMemoryViewer(); void RenderMemoryViewer();
void RenderModernCpuDebugger();
void RenderPerformanceMonitor();
void RenderAIAgentPanel();
void RenderSaveStates();
void RenderKeyboardConfig();
struct Bookmark { struct Bookmark {
std::string name; std::string name;

View File

@@ -114,7 +114,7 @@ void Ppu::Reset() {
fixed_color_g_ = 0; fixed_color_g_ = 0;
fixed_color_b_ = 0; fixed_color_b_ = 0;
forced_blank_ = true; forced_blank_ = true;
brightness = 0; brightness = 15; // SNES hardware default is maximum brightness
mode = 0; mode = 0;
bg3priority = false; bg3priority = false;
even_frame = false; even_frame = false;