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:
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ¤t_font_gfx16_bitmap_);
|
||||
|
||||
@@ -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", ¤t_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
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
594
src/app/emu/ui/debugger_ui.cc
Normal file
594
src/app/emu/ui/debugger_ui.cc
Normal 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", ®ion, 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
|
||||
|
||||
282
src/app/emu/ui/emulator_ui.cc
Normal file
282
src/app/emu/ui/emulator_ui.cc
Normal 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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user