feat: Enhance emulator UI and performance features

- Added new UI components for the emulator, including dedicated panels for CPU and APU debugging, improving user interaction and debugging capabilities.
- Implemented a caching mechanism for rendered objects in the DungeonCanvasViewer to optimize performance and reduce rendering time.
- Updated the CMake configuration to include new UI source files, ensuring proper organization and build management.
- Enhanced the theme manager with improved color definitions for better visibility and consistency across the UI.
- Refactored the emulator interface to delegate rendering tasks to the UI layer, streamlining the codebase and improving maintainability.
This commit is contained in:
scawful
2025-10-08 23:38:20 -04:00
parent d2dab43358
commit b5ec0cb637
10 changed files with 1025 additions and 900 deletions

View File

@@ -15,6 +15,8 @@ set(
app/emu/input/input_backend.cc
app/emu/input/input_manager.cc
app/emu/ui/input_handler.cc
app/emu/ui/emulator_ui.cc
app/emu/ui/debugger_ui.cc
app/emu/cpu/cpu.cc
app/emu/video/ppu.cc
app/emu/memory/dma.cc
@@ -939,7 +941,9 @@ source_group("Application\\Emulator\\Input" FILES
source_group("Application\\Emulator\\UI" FILES
app/emu/ui/input_handler.cc
app/emu/ui/input_handler.h
app/emu/ui/emulator_ui.cc
app/emu/ui/emulator_ui.h
app/emu/ui/debugger_ui.cc
app/emu/ui/debugger_ui.h
)

View File

@@ -179,6 +179,23 @@ void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
return; // Skip objects outside visible area
}
// Calculate palette hash for caching
uint64_t palette_hash = 0;
for (size_t i = 0; i < palette.size() && i < 16; ++i) {
palette_hash ^= std::hash<uint16_t>{}(palette[i].snes()) + 0x9e3779b9 +
(palette_hash << 6) + (palette_hash >> 2);
}
// Check cache first
for (auto& cached : object_render_cache_) {
if (cached.object_id == object.id_ && cached.object_x == object.x_ &&
cached.object_y == object.y_ && cached.object_size == object.size_ &&
cached.palette_hash == palette_hash && cached.is_valid) {
canvas_.DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Create a mutable copy of the object to ensure tiles are loaded
auto mutable_object = object;
mutable_object.set_rom(rom_);
@@ -192,10 +209,27 @@ void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
// Ensure the bitmap is valid and has content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0) {
object_bitmap.SetPalette(palette);
// Queue texture creation for the object bitmap via Arena's deferred system
// Add to cache
ObjectRenderCache cache_entry;
cache_entry.object_id = object.id_;
cache_entry.object_x = object.x_;
cache_entry.object_y = object.y_;
cache_entry.object_size = object.size_;
cache_entry.palette_hash = palette_hash;
cache_entry.rendered_bitmap = std::move(object_bitmap); // Move bitmap into cache
cache_entry.is_valid = true;
if (object_render_cache_.size() >= 200) { // Limit cache size
object_render_cache_.erase(object_render_cache_.begin());
}
object_render_cache_.push_back(std::move(cache_entry));
// Get pointer to cached bitmap and queue texture creation
gfx::Bitmap* cached_bitmap = &object_render_cache_.back().rendered_bitmap;
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &object_bitmap);
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
gfx::Arena::TextureCommandType::CREATE, cached_bitmap);
canvas_.DrawBitmap(*cached_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}

View File

@@ -336,7 +336,7 @@ void MessageEditor::DrawMessagePreview() {
} else {
// Create bitmap and queue texture creation
current_font_gfx16_bitmap_.Create(kCurrentMessageWidth, kCurrentMessageHeight,
172, message_preview_.current_preview_data_);
64, message_preview_.current_preview_data_);
current_font_gfx16_bitmap_.SetPalette(font_preview_colors_);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &current_font_gfx16_bitmap_);

View File

@@ -11,51 +11,19 @@ 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/emu/ui/debugger_ui.h"
#include "app/emu/ui/emulator_ui.h"
#include "app/emu/ui/input_handler.h"
#include "app/gui/color.h"
#include "app/gui/editor_layout.h"
#include "app/gui/icons.h"
#include "app/gui/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
@@ -395,20 +363,20 @@ void Emulator::RenderEmulatorInterface() {
static bool show_apu_debugger_ = true;
// 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",
static gui::EditorCard cpu_card(ICON_MD_MEMORY " CPU Debugger", ICON_MD_MEMORY);
static gui::EditorCard ppu_card(ICON_MD_VIDEOGAME_ASSET " PPU Display",
ICON_MD_VIDEOGAME_ASSET);
gui::EditorCard memory_card(ICON_MD_DATA_ARRAY " Memory Viewer",
static gui::EditorCard memory_card(ICON_MD_DATA_ARRAY " Memory Viewer",
ICON_MD_DATA_ARRAY);
gui::EditorCard breakpoints_card(ICON_MD_BUG_REPORT " Breakpoints",
static gui::EditorCard breakpoints_card(ICON_MD_BUG_REPORT " Breakpoints",
ICON_MD_BUG_REPORT);
gui::EditorCard performance_card(ICON_MD_SPEED " Performance",
static 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",
static gui::EditorCard ai_card(ICON_MD_SMART_TOY " AI Agent", ICON_MD_SMART_TOY);
static gui::EditorCard save_states_card(ICON_MD_SAVE " Save States", ICON_MD_SAVE);
static gui::EditorCard keyboard_card(ICON_MD_KEYBOARD " Keyboard Config",
ICON_MD_KEYBOARD);
gui::EditorCard apu_debug_card(ICON_MD_MUSIC_NOTE " APU Debugger",
static gui::EditorCard apu_debug_card(ICON_MD_MUSIC_NOTE " APU Debugger",
ICON_MD_MUSIC_NOTE);
// Configure default positions
@@ -528,162 +496,13 @@ void Emulator::RenderEmulatorInterface() {
}
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();
// Delegate to UI layer
ui::RenderSnesPpu(this);
}
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();
// Recording control moved to DisassemblyViewer UI
bool recording = disassembly_viewer_.IsRecording();
if (ImGui::Checkbox("Recording", &recording)) {
disassembly_viewer_.SetRecording(recording);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Toggle instruction recording to DisassemblyViewer\n(Always lightweight - uses sparse address map)");
}
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();
if (audio_backend_) {
auto audio_status = audio_backend_->GetStatus();
ImGui::Text("| Audio: %u frames", audio_status.queued_frames);
} else {
ImGui::Text("| Audio: N/A");
}
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;
}
}
// Delegate to UI layer
ui::RenderNavBar(this);
}
// REMOVED: HandleEvents() - replaced by ui::InputHandler::Poll()
@@ -691,147 +510,13 @@ void Emulator::RenderNavBar() {
// for continuous game input. Now using SDL_GetKeyboardState() for proper polling.
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_;
}
// Delegate to UI layer
ui::RenderBreakpointList(this);
}
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();
}
// Delegate to UI layer
ui::RenderMemoryViewer(this);
}
void Emulator::RenderModernCpuDebugger() {
@@ -1051,338 +736,29 @@ void Emulator::RenderModernCpuDebugger() {
}
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
ImGui::Text("Audio Queue:");
ImGui::SameLine();
if (audio_backend_) {
auto audio_status = audio_backend_->GetStatus();
ImVec4 audio_color = (audio_status.queued_frames >= 2 && audio_status.queued_frames <= 6)
? ConvertColorToImVec4(theme.success)
: ConvertColorToImVec4(theme.warning);
ImGui::TextColored(audio_color, "%u frames", audio_status.queued_frames);
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.error), "No backend");
}
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());
}
// Delegate to UI layer
ui::RenderPerformanceMonitor(this);
}
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());
}
// Delegate to UI layer
ui::RenderAIAgentPanel(this);
}
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();
// Delegate to UI layer (legacy log deprecated)
ui::RenderCpuInstructionLog(this, instruction_log.size());
}
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());
}
// TODO: Create ui::RenderSaveStates() when save state system is implemented
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::TextColored(ConvertColorToImVec4(theme.warning),
ICON_MD_SAVE " Save States - Coming Soon");
ImGui::TextWrapped("Save state functionality will be implemented here.");
}
void Emulator::RenderKeyboardConfig() {
@@ -1391,211 +767,8 @@ void Emulator::RenderKeyboardConfig() {
}
void Emulator::RenderApuDebugger() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##ApuDebugger", ImVec2(0, 0), true);
// Handshake Status
if (ImGui::CollapsingHeader("APU Handshake Status", ImGuiTreeNodeFlags_DefaultOpen)) {
auto& tracker = snes_.apu_handshake_tracker();
// Phase indicator with color
ImGui::Text("Phase:");
ImGui::SameLine();
auto phase_str = tracker.GetPhaseString();
ImVec4 phase_color;
if (phase_str == "RUNNING") {
phase_color = ConvertColorToImVec4(theme.success);
} else if (phase_str == "TRANSFER_ACTIVE") {
phase_color = ConvertColorToImVec4(theme.info);
} else if (phase_str == "WAITING_BBAA" || phase_str == "IPL_BOOT") {
phase_color = ConvertColorToImVec4(theme.warning);
} else {
phase_color = ConvertColorToImVec4(theme.text_primary);
}
ImGui::TextColored(phase_color, "%s", phase_str.c_str());
// Handshake complete indicator
ImGui::Text("Handshake:");
ImGui::SameLine();
if (tracker.IsHandshakeComplete()) {
ImGui::TextColored(ConvertColorToImVec4(theme.success), "✓ Complete");
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.error), "✗ Waiting");
}
// Transfer progress
if (tracker.IsTransferActive() || tracker.GetBytesTransferred() > 0) {
ImGui::Text("Bytes Transferred: %d", tracker.GetBytesTransferred());
ImGui::Text("Blocks: %d", tracker.GetBlockCount());
auto progress = tracker.GetTransferProgress();
if (!progress.empty()) {
ImGui::TextColored(ConvertColorToImVec4(theme.info), "%s", progress.c_str());
}
}
// Status summary
ImGui::Separator();
ImGui::TextWrapped("%s", tracker.GetStatusSummary().c_str());
}
// Port Activity Log
if (ImGui::CollapsingHeader("Port Activity Log")) {
ImGui::BeginChild("##PortLog", ImVec2(0, 200), true);
auto& tracker = snes_.apu_handshake_tracker();
const auto& history = tracker.GetPortHistory();
if (history.empty()) {
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
"No port activity yet");
} else {
// Show last 50 entries (most recent at bottom)
int start_idx = std::max(0, static_cast<int>(history.size()) - 50);
for (size_t i = start_idx; i < history.size(); ++i) {
const auto& entry = history[i];
ImVec4 color = entry.is_cpu ? ConvertColorToImVec4(theme.accent)
: ConvertColorToImVec4(theme.info);
ImGui::TextColored(color, "[%04llu] %s F%d = $%02X @ PC=$%04X %s",
entry.timestamp,
entry.is_cpu ? "CPU→" : "SPC→",
entry.port + 4,
entry.value,
entry.pc,
entry.description.c_str());
}
// Auto-scroll to bottom
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
ImGui::SetScrollHereY(1.0f);
}
}
ImGui::EndChild();
}
// Current Port Values
if (ImGui::CollapsingHeader("Current Port Values", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("APU_Ports", 4, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Port");
ImGui::TableSetupColumn("CPU → SPC");
ImGui::TableSetupColumn("SPC → CPU");
ImGui::TableSetupColumn("Addr");
ImGui::TableHeadersRow();
for (int i = 0; i < 4; ++i) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("F%d", i + 4);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%02X",
snes_.apu().in_ports_[i]);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.info), "$%02X",
snes_.apu().out_ports_[i]);
ImGui::TableNextColumn();
ImGui::TextDisabled("$214%d / $F%d", i, i + 4);
}
ImGui::EndTable();
}
}
// Quick Actions
if (ImGui::CollapsingHeader("Quick Actions", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::TextColored(ConvertColorToImVec4(theme.warning),
"⚠️ Manual Testing Tools");
ImGui::Separator();
// Full handshake sequence test
if (ImGui::Button("🎯 Full Handshake Test", ImVec2(-1, 35))) {
LOG_INFO("APU_DEBUG", "=== MANUAL HANDSHAKE TEST SEQUENCE ===");
// Step 1: Write $CC to F4 (initiate handshake)
snes_.Write(0x002140, 0xCC);
LOG_INFO("APU_DEBUG", "Step 1: Wrote $CC to F4 (port $2140)");
// Step 2: Write $01 to F5 (data port)
snes_.Write(0x002141, 0x01);
LOG_INFO("APU_DEBUG", "Step 2: Wrote $01 to F5 (port $2141)");
// Step 3: Write $00 to F6 (address low)
snes_.Write(0x002142, 0x00);
LOG_INFO("APU_DEBUG", "Step 3: Wrote $00 to F6 (port $2142)");
// Step 4: Write $02 to F7 (address high)
snes_.Write(0x002143, 0x02);
LOG_INFO("APU_DEBUG", "Step 4: Wrote $02 to F7 (port $2143)");
LOG_INFO("APU_DEBUG", "Handshake initiated - check Port Activity Log");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Manually execute full handshake sequence:\n"
"$CC → F4 (init), $01 → F5, $00 → F6, $02 → F7");
}
ImGui::Spacing();
// Individual port writes for debugging
if (ImGui::CollapsingHeader("Manual Port Writes")) {
static uint8_t port_values[4] = {0xCC, 0x01, 0x00, 0x02};
for (int i = 0; i < 4; ++i) {
ImGui::PushID(i);
ImGui::Text("F%d ($214%d):", i + 4, i);
ImGui::SameLine();
ImGui::SetNextItemWidth(80);
ImGui::InputScalar("##val", ImGuiDataType_U8, &port_values[i], NULL, NULL, "%02X",
ImGuiInputTextFlags_CharsHexadecimal);
ImGui::SameLine();
if (ImGui::Button("Write")) {
snes_.Write(0x002140 + i, port_values[i]);
LOG_INFO("APU_DEBUG", "Wrote $%02X to F%d (port $214%d)",
port_values[i], i + 4, i);
}
ImGui::PopID();
}
}
ImGui::Spacing();
ImGui::Separator();
// System controls
if (ImGui::Button("Reset APU", ImVec2(-1, 30))) {
snes_.apu().Reset();
LOG_INFO("APU_DEBUG", "APU manually reset");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Full APU reset - clears all state");
}
if (ImGui::Button("Clear Port History", ImVec2(-1, 30))) {
snes_.apu_handshake_tracker().Reset();
LOG_INFO("APU_DEBUG", "Port history cleared");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Clear the port activity log");
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
} catch (const std::exception& e) {
try {
ImGui::PopStyleColor();
} catch (...) {}
ImGui::Text("APU Debugger Error: %s", e.what());
}
// Delegate to UI layer
ui::RenderApuDebugger(this);
}
} // namespace emu

View File

@@ -39,6 +39,7 @@ class Emulator {
auto snes() -> Snes& { return snes_; }
auto running() const -> bool { return running_; }
void set_running(bool running) { running_ = running; }
// Audio backend access
audio::IAudioBackend* audio_backend() { return audio_backend_.get(); }
@@ -49,10 +50,22 @@ class Emulator {
auto wanted_samples() const -> int { return wanted_samples_; }
void set_renderer(gfx::IRenderer* renderer) { renderer_ = renderer; }
// Render access
gfx::IRenderer* renderer() { return renderer_; }
void* ppu_texture() { return ppu_texture_; }
// Turbo mode
bool turbo_mode() const { return turbo_mode_; }
void set_turbo_mode(bool turbo) { turbo_mode_ = turbo; }
// Debugger access
BreakpointManager& breakpoint_manager() { return breakpoint_manager_; }
debug::DisassemblyViewer& disassembly_viewer() { return disassembly_viewer_; }
input::InputManager& input_manager() { return input_manager_; }
bool is_debugging() const { return debugging_; }
void set_debugging(bool debugging) { debugging_ = debugging; }
bool is_initialized() const { return initialized_; }
bool is_snes_initialized() const { return snes_initialized_; }
// AI Agent Integration API
bool IsEmulatorReady() const { return snes_.running() && !rom_data_.empty(); }

View File

@@ -0,0 +1,594 @@
#include "app/emu/ui/debugger_ui.h"
#include "absl/strings/str_format.h"
#include "app/emu/emulator.h"
#include "app/emu/cpu/cpu.h"
#include "app/gui/color.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/log.h"
namespace yaze {
namespace emu {
namespace ui {
using namespace yaze::gui;
namespace {
// UI Constants
constexpr float kStandardSpacing = 8.0f;
constexpr float kButtonHeight = 30.0f;
constexpr float kLargeButtonHeight = 35.0f;
void AddSpacing() { ImGui::Spacing(); ImGui::Spacing(); }
void AddSectionSpacing() { ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); }
} // namespace
void RenderModernCpuDebugger(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##CPUDebugger", ImVec2(0, 0), true);
// Title with icon
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_DEVELOPER_BOARD " 65816 CPU Debugger");
AddSectionSpacing();
auto& cpu = emu->snes().cpu();
// Debugger Controls
if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " Controls", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6));
if (ImGui::Button(ICON_MD_SKIP_NEXT " Step", ImVec2(100, kButtonHeight))) {
cpu.RunOpcode();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Execute single instruction (F10)");
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_FAST_FORWARD " Run to BP", ImVec2(120, kButtonHeight))) {
// Run until breakpoint
emu->set_running(true);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Run until next breakpoint (F5)");
}
ImGui::PopStyleVar();
}
AddSpacing();
// CPU Registers
if (ImGui::CollapsingHeader(ICON_MD_MEMORY " Registers", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("CPU_Registers", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed, 40.0f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 70.0f);
ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed, 40.0f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 70.0f);
ImGui::TableHeadersRow();
// Row 1: A, X
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "A:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.A);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "X:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.X);
// Row 2: Y, D
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "Y:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.Y);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "D:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.D);
// Row 3: DB, PB
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "DB:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%02X", cpu.DB);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "PB:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%02X", cpu.PB);
// Row 4: PC, SP
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "PC:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.warning), "$%04X", cpu.PC);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "SP:");
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.SP());
ImGui::EndTable();
}
AddSpacing();
// Status Flags (visual checkboxes)
ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), ICON_MD_FLAG " Flags:");
ImGui::Indent();
auto RenderFlag = [&](const char* name, bool value) {
ImVec4 color = value ? ConvertColorToImVec4(theme.success) :
ConvertColorToImVec4(theme.text_disabled);
ImGui::TextColored(color, "%s %s", value ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK, name);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s: %s", name, value ? "Set" : "Clear");
}
ImGui::SameLine();
};
RenderFlag("N", cpu.GetNegativeFlag());
RenderFlag("V", cpu.GetOverflowFlag());
RenderFlag("D", cpu.GetDecimalFlag());
RenderFlag("I", cpu.GetInterruptFlag());
RenderFlag("Z", cpu.GetZeroFlag());
RenderFlag("C", cpu.GetCarryFlag());
ImGui::NewLine();
ImGui::Unindent();
}
AddSpacing();
// Breakpoint Management
if (ImGui::CollapsingHeader(ICON_MD_STOP_CIRCLE " Breakpoints")) {
static char bp_input[10] = "";
ImGui::SetNextItemWidth(150);
if (ImGui::InputTextWithHint("##BP", "Address (hex)", bp_input, sizeof(bp_input),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) {
if (strlen(bp_input) > 0) {
uint32_t addr = std::stoi(bp_input, nullptr, 16);
emu->SetBreakpoint(addr);
memset(bp_input, 0, sizeof(bp_input));
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_ADD " Add", ImVec2(80, 0))) {
if (strlen(bp_input) > 0) {
uint32_t addr = std::stoi(bp_input, nullptr, 16);
emu->SetBreakpoint(addr);
memset(bp_input, 0, sizeof(bp_input));
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear All", ImVec2(100, 0))) {
emu->ClearAllBreakpoints();
}
AddSpacing();
// List breakpoints
auto breakpoints = emu->GetBreakpoints();
if (!breakpoints.empty()) {
ImGui::BeginChild("##BPList", ImVec2(0, 150), true);
for (size_t i = 0; i < breakpoints.size(); ++i) {
uint32_t bp = breakpoints[i];
ImGui::PushID(i);
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_STOP " $%06X", bp);
ImGui::SameLine(200);
if (ImGui::SmallButton(ICON_MD_DELETE " Remove")) {
cpu.ClearBreakpoint(bp);
}
ImGui::PopID();
}
ImGui::EndChild();
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
"No breakpoints set");
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderBreakpointList(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##BreakpointList", ImVec2(0, 0), true);
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_STOP_CIRCLE " Breakpoint Manager");
AddSectionSpacing();
// Same content as in RenderModernCpuDebugger but with more detail
auto breakpoints = emu->GetBreakpoints();
ImGui::Text("Active Breakpoints: %zu", breakpoints.size());
AddSpacing();
if (!breakpoints.empty()) {
if (ImGui::BeginTable("BreakpointTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn(ICON_MD_TAG, ImGuiTableColumnFlags_WidthFixed, 40);
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (size_t i = 0; i < breakpoints.size(); ++i) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.error), ICON_MD_STOP);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%06X", breakpoints[i]);
ImGui::TableNextColumn();
ImGui::PushID(i);
if (ImGui::SmallButton(ICON_MD_DELETE " Remove")) {
emu->snes().cpu().ClearBreakpoint(breakpoints[i]);
}
ImGui::PopID();
}
ImGui::EndTable();
}
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
ICON_MD_INFO " No breakpoints set");
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderMemoryViewer(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
static MemoryEditor mem_edit;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##MemoryViewer", ImVec2(0, 0), true);
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_STORAGE " Memory Viewer");
AddSectionSpacing();
// Memory region selector
static int region = 0;
const char* regions[] = {"RAM ($0000-$1FFF)", "ROM Bank 0", "WRAM ($7E0000-$7FFFFF)", "SRAM"};
ImGui::SetNextItemWidth(250);
if (ImGui::Combo(ICON_MD_MAP " Region", &region, regions, IM_ARRAYSIZE(regions))) {
// Region changed
}
AddSpacing();
// Render memory editor
uint8_t* memory_base = emu->snes().get_ram();
size_t memory_size = 0x20000;
mem_edit.DrawContents(memory_base, memory_size, 0x0000);
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderCpuInstructionLog(Emulator* emu, uint32_t log_size) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##InstructionLog", ImVec2(0, 0), true);
ImGui::TextColored(ConvertColorToImVec4(theme.warning),
ICON_MD_WARNING " Legacy Instruction Log");
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
"Deprecated - Use Disassembly Viewer instead");
AddSectionSpacing();
// Show DisassemblyViewer stats instead
ImGui::Text(ICON_MD_INFO " DisassemblyViewer Active:");
ImGui::BulletText("Unique addresses: %zu", emu->disassembly_viewer().GetInstructionCount());
ImGui::BulletText("Recording: %s", emu->disassembly_viewer().IsRecording() ? "ON" : "OFF");
ImGui::BulletText("Auto-scroll: Available in viewer");
AddSpacing();
if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open Disassembly Viewer", ImVec2(-1, kLargeButtonHeight))) {
// TODO: Open disassembly viewer window
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderApuDebugger(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##ApuDebugger", ImVec2(0, 0), true);
// Title
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_MUSIC_NOTE " APU / SPC700 Debugger");
AddSectionSpacing();
auto& tracker = emu->snes().apu_handshake_tracker();
// Handshake Status with enhanced visuals
if (ImGui::CollapsingHeader(ICON_MD_HANDSHAKE " Handshake Status", ImGuiTreeNodeFlags_DefaultOpen)) {
// Phase with icon and color
auto phase_str = tracker.GetPhaseString();
ImVec4 phase_color;
const char* phase_icon;
if (phase_str == "RUNNING") {
phase_color = ConvertColorToImVec4(theme.success);
phase_icon = ICON_MD_CHECK_CIRCLE;
} else if (phase_str == "TRANSFER_ACTIVE") {
phase_color = ConvertColorToImVec4(theme.info);
phase_icon = ICON_MD_SYNC;
} else if (phase_str == "WAITING_BBAA" || phase_str == "IPL_BOOT") {
phase_color = ConvertColorToImVec4(theme.warning);
phase_icon = ICON_MD_PENDING;
} else {
phase_color = ConvertColorToImVec4(theme.error);
phase_icon = ICON_MD_ERROR;
}
ImGui::Text(ICON_MD_SETTINGS " Phase:");
ImGui::SameLine();
ImGui::TextColored(phase_color, "%s %s", phase_icon, phase_str.c_str());
// Handshake complete indicator
ImGui::Text(ICON_MD_LINK " Handshake:");
ImGui::SameLine();
if (tracker.IsHandshakeComplete()) {
ImGui::TextColored(ConvertColorToImVec4(theme.success),
ICON_MD_CHECK_CIRCLE " Complete");
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.warning),
ICON_MD_HOURGLASS_EMPTY " Waiting");
}
// Transfer progress
if (tracker.IsTransferActive() || tracker.GetBytesTransferred() > 0) {
AddSpacing();
ImGui::Text(ICON_MD_CLOUD_UPLOAD " Transfer Progress:");
ImGui::Indent();
ImGui::BulletText("Bytes: %d", tracker.GetBytesTransferred());
ImGui::BulletText("Blocks: %d", tracker.GetBlockCount());
auto progress = tracker.GetTransferProgress();
if (!progress.empty()) {
ImGui::TextColored(ConvertColorToImVec4(theme.info), "%s", progress.c_str());
}
ImGui::Unindent();
}
// Status summary
AddSectionSpacing();
ImGui::TextWrapped("%s", tracker.GetStatusSummary().c_str());
}
// Port Activity Log
if (ImGui::CollapsingHeader(ICON_MD_LIST " Port Activity Log", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::BeginChild("##PortLog", ImVec2(0, 200), true);
const auto& history = tracker.GetPortHistory();
if (history.empty()) {
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
ICON_MD_INFO " No port activity yet");
} else {
// Show last 50 entries
int start_idx = std::max(0, static_cast<int>(history.size()) - 50);
for (size_t i = start_idx; i < history.size(); ++i) {
const auto& entry = history[i];
ImVec4 color = entry.is_cpu ? ConvertColorToImVec4(theme.accent)
: ConvertColorToImVec4(theme.info);
const char* icon = entry.is_cpu ? ICON_MD_ARROW_FORWARD : ICON_MD_ARROW_BACK;
ImGui::TextColored(color, "[%04llu] %s %s F%d = $%02X @ PC=$%04X %s",
entry.timestamp,
entry.is_cpu ? "CPU" : "SPC",
icon,
entry.port + 4,
entry.value,
entry.pc,
entry.description.c_str());
}
// Auto-scroll
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
ImGui::SetScrollHereY(1.0f);
}
}
ImGui::EndChild();
}
// Current Port Values
if (ImGui::CollapsingHeader(ICON_MD_SETTINGS_INPUT_COMPONENT " Current Port Values",
ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("APU_Ports", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Port", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableSetupColumn("CPU → SPC", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableSetupColumn("SPC → CPU", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (int i = 0; i < 4; ++i) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text(ICON_MD_SETTINGS " F%d", i + 4);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"$%02X", emu->snes().apu().in_ports_[i]);
ImGui::TableNextColumn();
ImGui::TextColored(ConvertColorToImVec4(theme.info),
"$%02X", emu->snes().apu().out_ports_[i]);
ImGui::TableNextColumn();
ImGui::TextDisabled("$214%d / $F%d", i, i + 4);
}
ImGui::EndTable();
}
}
// Quick Actions
if (ImGui::CollapsingHeader(ICON_MD_BUILD " Quick Actions", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::TextColored(ConvertColorToImVec4(theme.warning),
ICON_MD_WARNING " Manual Testing Tools");
AddSpacing();
// Full handshake test
if (ImGui::Button(ICON_MD_PLAY_CIRCLE " Full Handshake Test", ImVec2(-1, kLargeButtonHeight))) {
LOG_INFO("APU_DEBUG", "=== MANUAL HANDSHAKE TEST ===");
emu->snes().Write(0x002140, 0xCC);
emu->snes().Write(0x002141, 0x01);
emu->snes().Write(0x002142, 0x00);
emu->snes().Write(0x002143, 0x02);
LOG_INFO("APU_DEBUG", "Handshake sequence executed");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Execute full handshake sequence:\n"
"$CC → F4, $01 → F5, $00 → F6, $02 → F7");
}
AddSpacing();
// Manual port writes
if (ImGui::TreeNode(ICON_MD_EDIT " Manual Port Writes")) {
static uint8_t port_values[4] = {0xCC, 0x01, 0x00, 0x02};
for (int i = 0; i < 4; ++i) {
ImGui::PushID(i);
ImGui::Text("F%d ($214%d):", i + 4, i);
ImGui::SameLine();
ImGui::SetNextItemWidth(80);
ImGui::InputScalar("##val", ImGuiDataType_U8, &port_values[i], NULL, NULL, "%02X",
ImGuiInputTextFlags_CharsHexadecimal);
ImGui::SameLine();
if (ImGui::Button(ICON_MD_SEND " Write", ImVec2(100, 0))) {
emu->snes().Write(0x002140 + i, port_values[i]);
LOG_INFO("APU_DEBUG", "Wrote $%02X to F%d", port_values[i], i + 4);
}
ImGui::PopID();
}
ImGui::TreePop();
}
AddSectionSpacing();
// System controls
if (ImGui::Button(ICON_MD_RESTART_ALT " Reset APU", ImVec2(-1, kButtonHeight))) {
emu->snes().apu().Reset();
LOG_INFO("APU_DEBUG", "APU reset");
}
if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear Port History", ImVec2(-1, kButtonHeight))) {
tracker.Reset();
LOG_INFO("APU_DEBUG", "Port history cleared");
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderAIAgentPanel(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##AIAgent", ImVec2(0, 0), true);
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_SMART_TOY " AI Agent Integration");
AddSectionSpacing();
// Agent status
bool agent_ready = emu->IsEmulatorReady();
ImVec4 status_color = agent_ready ? ConvertColorToImVec4(theme.success) :
ConvertColorToImVec4(theme.error);
ImGui::Text("Status:");
ImGui::SameLine();
ImGui::TextColored(status_color, "%s %s",
agent_ready ? ICON_MD_CHECK_CIRCLE : ICON_MD_ERROR,
agent_ready ? "Ready" : "Not Ready");
AddSpacing();
// Emulator metrics for agents
if (ImGui::CollapsingHeader(ICON_MD_DATA_OBJECT " Metrics", ImGuiTreeNodeFlags_DefaultOpen)) {
auto metrics = emu->GetMetrics();
ImGui::BulletText("FPS: %.2f", metrics.fps);
ImGui::BulletText("Cycles: %llu", metrics.cycles);
ImGui::BulletText("CPU PC: $%02X:%04X", metrics.cpu_pb, metrics.cpu_pc);
ImGui::BulletText("Audio Queued: %u frames", metrics.audio_frames_queued);
ImGui::BulletText("Running: %s", metrics.is_running ? "YES" : "NO");
}
// Agent controls
if (ImGui::CollapsingHeader(ICON_MD_PLAY_CIRCLE " Agent Controls")) {
if (ImGui::Button(ICON_MD_PLAY_ARROW " Start Agent Session", ImVec2(-1, kLargeButtonHeight))) {
// TODO: Start agent
}
if (ImGui::Button(ICON_MD_STOP " Stop Agent", ImVec2(-1, kButtonHeight))) {
// TODO: Stop agent
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
} // namespace ui
} // namespace emu
} // namespace yaze

View File

@@ -0,0 +1,282 @@
#include "app/emu/ui/emulator_ui.h"
#include "absl/strings/str_format.h"
#include "app/emu/emulator.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/theme_manager.h"
#include "imgui/imgui.h"
#include "util/log.h"
namespace yaze {
namespace emu {
namespace ui {
using namespace yaze::gui;
namespace {
// UI Constants for consistent spacing
constexpr float kStandardSpacing = 8.0f;
constexpr float kSectionSpacing = 16.0f;
constexpr float kButtonHeight = 30.0f;
constexpr float kIconSize = 24.0f;
// Helper to add consistent spacing
void AddSpacing() {
ImGui::Spacing();
ImGui::Spacing();
}
void AddSectionSpacing() {
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
}
} // namespace
void RenderNavBar(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
// Navbar with theme colors
ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active));
// Play/Pause button with icon
bool is_running = emu->running();
if (is_running) {
if (ImGui::Button(ICON_MD_PAUSE " Pause", ImVec2(100, kButtonHeight))) {
emu->set_running(false);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Pause emulation (Space)");
}
} else {
if (ImGui::Button(ICON_MD_PLAY_ARROW " Play", ImVec2(100, kButtonHeight))) {
emu->set_running(true);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Start emulation (Space)");
}
}
ImGui::SameLine();
// Step button
if (ImGui::Button(ICON_MD_SKIP_NEXT " Step", ImVec2(80, kButtonHeight))) {
if (!is_running) {
emu->snes().RunFrame();
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Step one frame (F10)");
}
ImGui::SameLine();
// Reset button
if (ImGui::Button(ICON_MD_RESTART_ALT " Reset", ImVec2(80, kButtonHeight))) {
emu->snes().Reset();
LOG_INFO("Emulator", "System reset");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reset SNES (Ctrl+R)");
}
ImGui::SameLine();
ImGui::Separator();
ImGui::SameLine();
// Debugger toggle
bool is_debugging = emu->is_debugging();
if (ImGui::Checkbox(ICON_MD_BUG_REPORT " Debug", &is_debugging)) {
emu->set_debugging(is_debugging);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enable debugger features");
}
ImGui::SameLine();
// Recording toggle (for DisassemblyViewer)
// Access through emulator's disassembly viewer
// bool recording = emu->disassembly_viewer().IsRecording();
// if (ImGui::Checkbox(ICON_MD_FIBER_MANUAL_RECORD " Rec", &recording)) {
// emu->disassembly_viewer().SetRecording(recording);
// }
// if (ImGui::IsItemHovered()) {
// ImGui::SetTooltip("Record instructions to Disassembly Viewer\n(Lightweight - uses sparse address map)");
// }
ImGui::SameLine();
// Turbo mode
bool turbo = false; // Need to expose this from Emulator
if (ImGui::Checkbox(ICON_MD_FAST_FORWARD " Turbo", &turbo)) {
// emu->set_turbo_mode(turbo);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Fast forward (hold Tab)");
}
ImGui::SameLine();
ImGui::Separator();
ImGui::SameLine();
// FPS Counter with color coding
double fps = emu->GetCurrentFPS();
ImVec4 fps_color;
if (fps >= 58.0) {
fps_color = ConvertColorToImVec4(theme.success); // Green for good FPS
} else if (fps >= 45.0) {
fps_color = ConvertColorToImVec4(theme.warning); // Yellow for okay FPS
} else {
fps_color = ConvertColorToImVec4(theme.error); // Red for bad FPS
}
ImGui::TextColored(fps_color, ICON_MD_SPEED " %.1f FPS", fps);
ImGui::SameLine();
// Audio backend status
if (emu->audio_backend()) {
auto audio_status = emu->audio_backend()->GetStatus();
ImVec4 audio_color = audio_status.is_playing ?
ConvertColorToImVec4(theme.success) : ConvertColorToImVec4(theme.text_disabled);
ImGui::TextColored(audio_color, ICON_MD_VOLUME_UP " %s | %u frames",
emu->audio_backend()->GetBackendName().c_str(),
audio_status.queued_frames);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Audio Backend: %s\nQueued: %u frames\nPlaying: %s",
emu->audio_backend()->GetBackendName().c_str(),
audio_status.queued_frames,
audio_status.is_playing ? "YES" : "NO");
}
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.error),
ICON_MD_VOLUME_OFF " No Backend");
}
ImGui::PopStyleColor(3);
}
void RenderSnesPpu(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background));
ImGui::BeginChild("##SNES_PPU", ImVec2(0, 0), true,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImVec2 canvas_size = ImGui::GetContentRegionAvail();
ImVec2 snes_size = ImVec2(512, 480);
if (emu->is_snes_initialized() && emu->ppu_texture()) {
// Center the SNES display
float aspect = snes_size.x / snes_size.y;
float display_w = canvas_size.x;
float display_h = display_w / aspect;
if (display_h > canvas_size.y) {
display_h = canvas_size.y;
display_w = display_h * aspect;
}
float pos_x = (canvas_size.x - display_w) * 0.5f;
float pos_y = (canvas_size.y - display_h) * 0.5f;
ImGui::SetCursorPos(ImVec2(pos_x, pos_y));
// Render PPU texture
ImGui::Image((ImTextureID)(intptr_t)emu->ppu_texture(),
ImVec2(display_w, display_h),
ImVec2(0, 0), ImVec2(1, 1));
} else {
// Not initialized - show placeholder
ImVec2 text_size = ImGui::CalcTextSize("SNES PPU Output\n512x480");
ImGui::SetCursorPos(ImVec2((canvas_size.x - text_size.x) * 0.5f,
(canvas_size.y - text_size.y) * 0.5f));
ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
ICON_MD_VIDEOGAME_ASSET "\nSNES PPU Output\n512x480");
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderPerformanceMonitor(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
ImGui::BeginChild("##Performance", ImVec2(0, 0), true);
ImGui::TextColored(ConvertColorToImVec4(theme.accent),
ICON_MD_SPEED " Performance Monitor");
AddSectionSpacing();
auto metrics = emu->GetMetrics();
// FPS Graph
if (ImGui::CollapsingHeader(ICON_MD_SHOW_CHART " Frame Rate", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Current: %.2f FPS", metrics.fps);
ImGui::Text("Target: %.2f FPS", emu->snes().memory().pal_timing() ? 50.0 : 60.0);
// TODO: Add FPS graph with ImPlot
}
// CPU Stats
if (ImGui::CollapsingHeader(ICON_MD_MEMORY " CPU Status", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("PC: $%02X:%04X", metrics.cpu_pb, metrics.cpu_pc);
ImGui::Text("Cycles: %llu", metrics.cycles);
}
// Audio Stats
if (ImGui::CollapsingHeader(ICON_MD_AUDIOTRACK " Audio Status", ImGuiTreeNodeFlags_DefaultOpen)) {
if (emu->audio_backend()) {
auto audio_status = emu->audio_backend()->GetStatus();
ImGui::Text("Backend: %s", emu->audio_backend()->GetBackendName().c_str());
ImGui::Text("Queued: %u frames", audio_status.queued_frames);
ImGui::Text("Playing: %s", audio_status.is_playing ? "YES" : "NO");
} else {
ImGui::TextColored(ConvertColorToImVec4(theme.error), "No audio backend");
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
void RenderEmulatorInterface(Emulator* emu) {
if (!emu) return;
auto& theme_manager = ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
// Main layout
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.window_bg));
RenderNavBar(emu);
ImGui::Separator();
// Main content area
RenderSnesPpu(emu);
ImGui::PopStyleColor();
}
} // namespace ui
} // namespace emu
} // namespace yaze

View File

@@ -214,22 +214,30 @@ void Ppu::HandlePixel(int x, int y) {
// Two pixels per X position for hi-res support:
// pixel1 at x*8 + 0..3, pixel2 at x*8 + 4..7
// Apply brightness
r = r * brightness / 15;
g = g * brightness / 15;
b = b * brightness / 15;
r2 = r2 * brightness / 15;
g2 = g2 * brightness / 15;
b2 = b2 * brightness / 15;
// First pixel (hi-res/main screen)
pixelBuffer[row * 2048 + x * 8 + 0 + pixelOutputFormat] =
((b2 << 3) | (b2 >> 2)) * brightness / 15; // Blue channel
((b2 << 3) | (b2 >> 2)); // Blue channel
pixelBuffer[row * 2048 + x * 8 + 1 + pixelOutputFormat] =
((g2 << 3) | (g2 >> 2)) * brightness / 15; // Green channel
((g2 << 3) | (g2 >> 2)); // Green channel
pixelBuffer[row * 2048 + x * 8 + 2 + pixelOutputFormat] =
((r2 << 3) | (r2 >> 2)) * brightness / 15; // Red channel
((r2 << 3) | (r2 >> 2)); // Red channel
pixelBuffer[row * 2048 + x * 8 + 3 + pixelOutputFormat] = 0xFF; // Alpha (opaque)
// Second pixel (lo-res/subscreen)
pixelBuffer[row * 2048 + x * 8 + 4 + pixelOutputFormat] =
((b << 3) | (b >> 2)) * brightness / 15; // Blue channel
((b << 3) | (b >> 2)); // Blue channel
pixelBuffer[row * 2048 + x * 8 + 5 + pixelOutputFormat] =
((g << 3) | (g >> 2)) * brightness / 15; // Green channel
((g << 3) | (g >> 2)); // Green channel
pixelBuffer[row * 2048 + x * 8 + 6 + pixelOutputFormat] =
((r << 3) | (r >> 2)) * brightness / 15; // Red channel
((r << 3) | (r >> 2)); // Red channel
pixelBuffer[row * 2048 + x * 8 + 7 + pixelOutputFormat] = 0xFF; // Alpha (opaque)
}

View File

@@ -203,17 +203,18 @@ void ThemeManager::CreateFallbackYazeClassic() {
theme.resize_grip_hovered = RGBA(199, 209, 255, 153); // 0.78f, 0.82f, 1.00f, 0.60f
theme.resize_grip_active = RGBA(199, 209, 255, 230); // 0.78f, 0.82f, 1.00f, 0.90f
// Complete ImGui colors with smart defaults using accent colors
theme.check_mark = RGBA(66, 150, 250, 255); // Solid blue checkmark (visible!)
theme.slider_grab = RGBA(66, 150, 250, 200); // Blue slider grab
theme.slider_grab_active = RGBA(92, 115, 92, 255); // Solid green when active
theme.input_text_cursor = theme.text_primary; // Use primary text color
theme.nav_cursor = theme.accent; // Use accent color for navigation
theme.nav_windowing_highlight = theme.accent; // Accent for window switching
theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 128); // Semi-transparent overlay
theme.modal_window_dim_bg = RGBA(0, 0, 0, 89); // 0.35f alpha
theme.text_selected_bg = RGBA(89, 119, 89, 89); // Accent color with transparency
theme.drag_drop_target = theme.accent; // Use accent for drag targets
// ENHANCED: Complete ImGui colors with theme-aware smart defaults
// Use theme colors instead of hardcoded values for consistency
theme.check_mark = RGBA(125, 255, 125, 255); // Bright green checkmark (highly visible!)
theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid)
theme.slider_grab_active = RGBA(125, 146, 125, 255); // Lighter green when active
theme.input_text_cursor = RGBA(255, 255, 255, 255); // White cursor (always visible)
theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green for navigation
theme.nav_windowing_highlight = RGBA(89, 119, 89, 200); // Accent with high visibility
theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay for better contrast
theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha for modals
theme.text_selected_bg = RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible selection!)
theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green for drop zones
// Table colors (from original)
theme.table_header_bg = RGBA(46, 66, 46); // alttpDarkGreen
@@ -893,23 +894,39 @@ void ThemeManager::ApplyClassicYazeTheme() {
classic_theme.scrollbar_grab_hovered = RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f
classic_theme.scrollbar_grab_active = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
// Add all the missing colors that CreateFallbackYazeClassic has
classic_theme.frame_bg = classic_theme.window_bg;
classic_theme.frame_bg_hovered = classic_theme.button_hovered;
classic_theme.frame_bg_active = classic_theme.button_active;
classic_theme.resize_grip = RGBA(255, 255, 255, 26);
classic_theme.resize_grip_hovered = RGBA(199, 209, 255, 153);
classic_theme.resize_grip_active = RGBA(199, 209, 255, 230);
classic_theme.check_mark = RGBA(230, 230, 230, 128);
classic_theme.slider_grab = RGBA(255, 255, 255, 77);
classic_theme.slider_grab_active = RGBA(92, 115, 92, 153);
classic_theme.input_text_cursor = classic_theme.text_primary;
classic_theme.nav_cursor = classic_theme.accent;
classic_theme.nav_windowing_highlight = classic_theme.accent;
classic_theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 128);
classic_theme.modal_window_dim_bg = RGBA(0, 0, 0, 89);
classic_theme.text_selected_bg = RGBA(89, 119, 89, 89);
classic_theme.drag_drop_target = classic_theme.accent;
// ENHANCED: Frame colors for inputs/widgets
classic_theme.frame_bg = RGBA(46, 66, 46, 140); // Darker green with some transparency
classic_theme.frame_bg_hovered = RGBA(71, 92, 71, 170); // Mid green when hovered
classic_theme.frame_bg_active = RGBA(92, 115, 92, 200); // Light green when active
// FIXED: Resize grips with better visibility
classic_theme.resize_grip = RGBA(92, 115, 92, 80); // Theme green, subtle
classic_theme.resize_grip_hovered = RGBA(125, 146, 125, 180); // Brighter when hovered
classic_theme.resize_grip_active = RGBA(125, 146, 125, 255); // Solid when active
// FIXED: Checkmark - bright green for high visibility!
classic_theme.check_mark = RGBA(125, 255, 125, 255); // Bright green (clearly visible)
// FIXED: Sliders with theme colors
classic_theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid)
classic_theme.slider_grab_active = RGBA(125, 146, 125, 255); // Lighter when grabbed
// FIXED: Input cursor - white for maximum visibility
classic_theme.input_text_cursor = RGBA(255, 255, 255, 255); // White cursor (always visible)
// FIXED: Navigation with theme colors
classic_theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green navigation
classic_theme.nav_windowing_highlight = RGBA(92, 115, 92, 200); // Theme green highlight
classic_theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay
// FIXED: Modals with better dimming
classic_theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha
// FIXED: Text selection - visible and theme-appropriate!
classic_theme.text_selected_bg = RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible!)
// FIXED: Drag/drop target with high visibility
classic_theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green
classic_theme.table_header_bg = RGBA(46, 66, 46);
classic_theme.table_border_strong = RGBA(71, 92, 71);
classic_theme.table_border_light = RGBA(66, 66, 71);

View File

@@ -37,7 +37,7 @@ class DungeonTestHarness {
auto& ppu = snes.ppu();
// Run emulator until the main game loop is reached
int max_cycles = 5000000; // 5 million cycles should be plenty
int max_cycles = 15000000; // 15 million cycles should be plenty
int cycles = 0;
while (cycles < max_cycles) {
snes.RunCycle();