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:
@@ -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",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
// CPU Register Values
|
// Create session-aware cards
|
||||||
if (ImGui::CollapsingHeader("Register Values", ImGuiTreeNodeFlags_DefaultOpen)) {
|
gui::EditorCard cpu_card(ICON_MD_MEMORY " CPU Debugger", ICON_MD_MEMORY);
|
||||||
ImGui::BeginChild("##CpuState", ImVec2(0, 100), ImGuiChildFlags_None,
|
gui::EditorCard ppu_card(ICON_MD_VIDEOGAME_ASSET " PPU Display", ICON_MD_VIDEOGAME_ASSET);
|
||||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
|
gui::EditorCard memory_card(ICON_MD_DATA_ARRAY " Memory Viewer", ICON_MD_DATA_ARRAY);
|
||||||
ImGui::Columns(2, "registersColumns");
|
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);
|
||||||
|
|
||||||
ImGui::Text("A: 0x%04X", snes_.cpu().A);
|
// Configure default positions
|
||||||
ImGui::Text("X: 0x%04X", snes_.cpu().X);
|
static bool cards_configured = false;
|
||||||
ImGui::Text("Y: 0x%04X", snes_.cpu().Y);
|
if (!cards_configured) {
|
||||||
ImGui::Text("PC: 0x%04X", snes_.cpu().PC);
|
cpu_card.SetDefaultSize(400, 500);
|
||||||
ImGui::Text("SP: 0x%02X", snes_.memory().mutable_sp());
|
cpu_card.SetPosition(gui::EditorCard::Position::Right);
|
||||||
|
|
||||||
ImGui::NextColumn();
|
ppu_card.SetDefaultSize(550, 520);
|
||||||
|
ppu_card.SetPosition(gui::EditorCard::Position::Floating);
|
||||||
|
|
||||||
ImGui::Text("D: 0x%04X", snes_.cpu().D);
|
memory_card.SetDefaultSize(800, 600);
|
||||||
ImGui::Text("DB: 0x%02X", snes_.cpu().DB);
|
memory_card.SetPosition(gui::EditorCard::Position::Floating);
|
||||||
ImGui::Text("PB: 0x%02X", snes_.cpu().PB);
|
|
||||||
ImGui::Text("PS: 0x%02X", snes_.cpu().status);
|
|
||||||
ImGui::Text("Cycle: %llu", snes_.mutable_cycles());
|
|
||||||
|
|
||||||
ImGui::Columns(1);
|
breakpoints_card.SetDefaultSize(400, 350);
|
||||||
ImGui::EndChild();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
// SPC Registers
|
// PPU Display Card
|
||||||
if (ImGui::CollapsingHeader("SPC Registers", ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (show_ppu_display_) {
|
||||||
ImGui::BeginChild("##SpcState", ImVec2(0, 100), ImGuiChildFlags_None,
|
if (ppu_card.Begin(&show_ppu_display_)) {
|
||||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
|
RenderSnesPpu();
|
||||||
ImGui::Columns(2, "spcRegistersColumns");
|
|
||||||
|
|
||||||
ImGui::Text("A: 0x%02X", snes_.apu().spc700().A);
|
|
||||||
ImGui::Text("X: 0x%02X", snes_.apu().spc700().X);
|
|
||||||
ImGui::Text("Y: 0x%02X", snes_.apu().spc700().Y);
|
|
||||||
|
|
||||||
ImGui::NextColumn();
|
|
||||||
|
|
||||||
ImGui::Text("PC: 0x%04X", snes_.apu().spc700().PC);
|
|
||||||
ImGui::Text("SP: 0x%02X", snes_.apu().spc700().SP);
|
|
||||||
ImGui::Text("PSW: 0x%02X", snes_.apu().spc700().FlagsToByte(snes_.apu().spc700().PSW));
|
|
||||||
|
|
||||||
ImGui::Columns(1);
|
|
||||||
ImGui::EndChild();
|
|
||||||
}
|
}
|
||||||
|
ppu_card.End();
|
||||||
|
}
|
||||||
|
|
||||||
// CPU Instruction Log
|
// Memory Viewer Card
|
||||||
RenderCpuInstructionLog(snes_.cpu().instruction_log_);
|
if (show_memory_viewer_) {
|
||||||
|
if (memory_card.Begin(&show_memory_viewer_)) {
|
||||||
|
RenderMemoryViewer();
|
||||||
|
}
|
||||||
|
memory_card.End();
|
||||||
|
}
|
||||||
|
|
||||||
// PPU Column
|
// Breakpoints Card
|
||||||
ImGui::TableNextColumn();
|
if (show_breakpoints_) {
|
||||||
|
if (breakpoints_card.Begin(&show_breakpoints_)) {
|
||||||
|
RenderBreakpointList();
|
||||||
|
}
|
||||||
|
breakpoints_card.End();
|
||||||
|
}
|
||||||
|
|
||||||
// SNES PPU
|
// Performance Monitor Card
|
||||||
RenderSnesPpu();
|
if (show_performance_) {
|
||||||
|
if (performance_card.Begin(&show_performance_)) {
|
||||||
|
RenderPerformanceMonitor();
|
||||||
|
}
|
||||||
|
performance_card.End();
|
||||||
|
}
|
||||||
|
|
||||||
// Breakpoint List
|
// AI Agent Card
|
||||||
RenderBreakpointList();
|
if (show_ai_agent_) {
|
||||||
|
if (ai_card.Begin(&show_ai_agent_)) {
|
||||||
|
RenderAIAgentPanel();
|
||||||
|
}
|
||||||
|
ai_card.End();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndTable();
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,37 @@ class Emulator {
|
|||||||
}
|
}
|
||||||
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();
|
||||||
void HandleEvents();
|
void HandleEvents();
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user