diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e622238 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# Data-Oriented Design Fundamentals + +This document outlines the core principles and guidelines for contributing to our project, which has been rewritten +using a data-oriented design (DoD) approach. + +## Why Data-Oriented Design? + +The source code is now written in a data-oriented style instead of an object-oriented one. This change was made because +traditional OOP can be CPU cache-unfriendly, leading to slower performance compared to DoD principles. + +While Yuzu's entire codebase is object-oriented, we believe that applying DoD from the very beginning could +significantly improve its speed and efficiency. + +## Key Resources + +To learn more about data-oriented design fundamentals, please refer to this invaluable resource: + +https://github.com/dbartolini/data-oriented-design + +This guide should be treated as a fundamental reference when working on our codebase. + +## Core Principles + +### 1. Performance First + +Performance is the top priority in all aspects of development. Always ask yourself: "Is the CPU wasting cycles running +this code? If so, how do I fix it?" The data-oriented design resource above contains answers to these questions. + +### 2. Memory Management + +- **Heap Allocation Ban**: Using memory allocation functions like malloc(), free(), new, and delete is prohibited. +- **Stack Preference**: Keep everything on the stack whenever possible. +- **Last Resort**: If you must allocate memory, use our custom memory allocator in `core/memory/arena.h`. This should be + your last resort only. + +The reason for these strict rules is that heap allocations can introduce undefined behavior issues into our codebase. + +### 3. Safety First + +1. **Error Handling**: Every return code from a function must be checked and handled appropriately. +2. **Assertions**: Use assertions to guarantee behavior whenever possible. Watch this video for an explanation: + https://youtube.com/shorts/M-VU0fLjIUU +3. **Static Analysis**: Use the SonarQube static analyzer to catch potential bugs early in development. + +### 4. Documentation + +Document every struct and function throughout our codebase. This is a tedious task, but it will be greatly appreciated +by current and future programmers working on this project. + +## Style Conventions + +Refer to `main.cpp` and the GUI folder for examples of proper code styling. Here are some specific rules: + +1. **Constant First in Equality Tests**: + ```c + // Non-compliant + if (var == constant) + if (pointer == NULL) + + // Compliant + if (constant == var) + if (NULL == pointer) + ``` +2. **Todo Comments**: Use the following format: + ```c + // Todo(:
): ... + + Where
is one of: + + - cpu + - gpu + - gui + - memory + - filesystem + + For example: + // Todo(GloriousTaco:memory): Create a custom allocator. + ``` + +## Contributing Suggestions + +For those who want to contribute now, we suggest rewriting: + +- Base::Log::Initialize() +- Base::Log::Start() +- Config::load() + +in main() using the principles outlined in this document. + +If you're familiar with memory allocators, please consider implementing std::allocator for our custom memory allocator +in core/memory/arena.h. This would allow us to manage the memory of C++ standard types like std::vector. diff --git a/README.md b/README.md index f6b46dc..30dc9a4 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,13 @@ Join the [**Pound Discord Server**](https://discord.gg/aMmTmKsVC7)! -**Pound** is an early-stage emulator for the **Nintendo Switch 2**, targeting **Windows**, **Linux** and **macOS** (Intel). +**Pound** is an early-stage emulator for the **Nintendo Switch 2**, targeting **Windows**, **Linux** and **macOS** ( +Intel). Future Supports: **Android**, **macOS ARM** - -Initial focus is on implementing the architectural similarities to the original Nintendo Switch. Later stages of development will address differences in hardware between the two console generations. +Initial focus is on implementing the architectural similarities to the original Nintendo Switch. Later stages of +development will address differences in hardware between the two console generations. ## Disclaimer @@ -36,28 +37,23 @@ legally purchased devices and games and information made public on the internet (you'd be surprised what's indexed on Google...). We are not any way affiliated with Nintendo or NVidia. - ## How to Compile Pound See the [**compilation guide**](/resources/docs/compguide.md) for detailed instructions on how to compile Pound. - ## Codebase Pound reuses selected components from existing Nintendo Switch 1 emulators, primarily **Yuzu**. All third-party code is clearly documented and properly attributed in the relevant parts of the repository. - ## Contributing -(coming soon) - +See [**here**](/CONTRIBUTING.md) before submitting a pull request. ## License - Distributed under the [**GNU GPL-2.0 license**](https://github.com/pound-emu/pound/blob/main/LICENSE) - ## Credits - Parts of the emulator are based on code from the Yuzu project. diff --git a/core/ARM/cpu.cpp b/core/ARM/cpu.cpp new file mode 100755 index 0000000..f415eda --- /dev/null +++ b/core/ARM/cpu.cpp @@ -0,0 +1,20 @@ +#include "cpu.h" +#include "JIT/jit.h" +void cpuTest() +{ + CPU cpu; + cpu.pc = 0; + + // Simple ARMv8 program in memory (MOVZ X0, #5; ADD X0, X0, #3; RET) + // These are placeholders; real encoding will be parsed later + cpu.write_byte(0, 0x05); // MOVZ placeholder + cpu.write_byte(4, 0x03); // ADD placeholder + cpu.write_byte(8, 0xFF); // RET placeholder + + LOG_INFO(ARM, "{}", cpu.read_byte(0)); + + JIT jit; + //jit.translate_and_run(cpu); + + cpu.print_debug_information(); +} diff --git a/core/ARM/cpu.h b/core/ARM/cpu.h index d94754e..f962b50 100644 --- a/core/ARM/cpu.h +++ b/core/ARM/cpu.h @@ -6,38 +6,44 @@ #include "Base/Logging/Log.h" -struct CPU { - u64 regs[31] = {0}; // X0–X30 +struct CPU +{ + u64 regs[31] = {0}; // X0–X30 u64 pc = 0; static constexpr size_t MEM_SIZE = 64 * 1024; u8 memory[MEM_SIZE]; - CPU() { - std::memset(memory, 0, MEM_SIZE); - } + CPU() { std::memset(memory, 0, MEM_SIZE); } - u64& x(int i) { - return regs[i]; - } + u64& x(int i) { return regs[i]; } - u8 read_byte(u64 addr) { - if (addr >= MEM_SIZE) { + u8 read_byte(u64 addr) + { + if (addr >= MEM_SIZE) + { LOG_INFO(ARM, "{} out of bounds", addr); } return memory[addr]; } - void write_byte(u64 addr, u8 byte) { - if (addr >= MEM_SIZE) { + void write_byte(u64 addr, u8 byte) + { + if (addr >= MEM_SIZE) + { LOG_INFO(ARM, "{} out of bounds", addr); } memory[addr] = byte; } - void print_debug_information() { + void print_debug_information() + { LOG_INFO(ARM, "PC = {}", pc); - for (int reg = 0; reg < 32; reg++) { - LOG_INFO(ARM, "X{} = {}", reg, x(reg)); // X0 = 0... + for (int reg = 0; reg < 31; reg++) + { + uint64_t regis = x(reg); + LOG_INFO(ARM, "X{} = {}", reg, regis); // X0 = 0.. } } }; + +void cpuTest(); diff --git a/core/main.cpp b/core/main.cpp index 335c331..afc3dce 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -1,89 +1,128 @@ // Copyright 2025 Pound Emulator Project. All rights reserved. -#include -#include #include +#include +#include -#include "Base/Logging/Backend.h" -#include "Base/Config.h" #include "ARM/cpu.h" +#include "Base/Config.h" +#include "Base/Logging/Backend.h" #include "JIT/jit.h" +#include "gui/gui.h" +#include "memory/arena.h" -#include "gui/GUIManager.h" -#include "gui/panels/ConsolePanel.h" -#include "gui/panels/CPUPanel.h" -#include "gui/panels/PerformancePanel.h" +#include +#include "gui/color.h" +#include "gui/panels.h" +#include "imgui_impl_opengl3.h" +#include "imgui_impl_sdl3.h" -// CPU test function -void cpuTest() { - CPU cpu; - cpu.pc = 0; +int main() +{ + // This is meant to replace malloc() and its related functions. + // TODO(GloriousTaco:memory): Implement std::allocator for this custom allocator which allows it to manage the memory of C++ standard types like std::vector. + memory::arena_t arena = memory::arena_init(1024); - // Simple ARMv8 program in memory (MOVZ X0, #5; ADD X0, X0, #3; RET) - // These are placeholders; real encoding will be parsed later - cpu.write_byte(0, 0x05); // MOVZ placeholder - cpu.write_byte(4, 0x03); // ADD placeholder - cpu.write_byte(8, 0xFF); // RET placeholder - - LOG_INFO(ARM, "{}", cpu.read_byte(0)); - - JIT jit; - jit.translate_and_run(cpu); - - cpu.print_debug_information(); - - LOG_INFO(ARM, "X0 = {}", cpu.x(0)); -} - -int main() { Base::Log::Initialize(); Base::Log::Start(); - const auto config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir); + const auto& config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir); Config::Load(config_dir / "config.toml"); // Create GUI manager - auto gui_manager = std::make_unique(); - - // Initialize GUI - if (!gui_manager->Initialize("Pound Emulator", Config::windowWidth(), Config::windowHeight())) { + gui::window_t window = {.data = nullptr, .gl_context = nullptr}; + (void)gui::window_init(&window, "Pound Emulator", Config::windowWidth(), Config::windowHeight()); + + if (bool return_code = gui::init_imgui(&window); false == return_code) + { LOG_ERROR(Render, "Failed to initialize GUI"); - return -1; + return EXIT_FAILURE; } - // Create and add panels - auto console_panel = std::make_shared(); - auto cpu_panel = std::make_shared(); - auto performance_panel = std::make_shared(); - - gui_manager->AddPanel(console_panel); - gui_manager->AddPanel(cpu_panel); - gui_manager->AddPanel(performance_panel); - - // Set up callbacks - auto cpu_test_callback = [console_panel]() { - console_panel->AddLog("[INFO] Running CPU test..."); - cpuTest(); - console_panel->AddLog("[INFO] CPU test completed. Check terminal for details."); + const size_t panels_capacity = 2; + const char* panel_names[panels_capacity] = {PANEL_NAME_CPU, PANEL_NAME_PERFORMANCE}; + bool panels_visibility[panels_capacity] = {false}; + bool imgui_demo_visible = false; + + gui::gui_t gui = { + .window = window, + .custom_panels = panel_names, + .custom_panels_visibility = panels_visibility, + .custom_panels_capacity = panels_capacity, }; - - gui_manager->SetCPUTestCallback(cpu_test_callback); - cpu_panel->SetCPUTestCallback(cpu_test_callback); - - // Add initial console message - console_panel->AddLog("[INFO] Pound Emulator started"); - console_panel->AddLog("[INFO] Version: Pre-Alpha"); - + + gui::panel::performance_panel_t performance_panel = {}; + gui::panel::performance_data_t performance_data = {.frame_count = 1}; + std::chrono::steady_clock::time_point performance_panel_last_render = std::chrono::steady_clock::now(); + // Main loop - while (gui_manager->IsRunning()) { - gui_manager->RunFrame(); - + bool is_running = true; + bool show_cpu_result_popup = false; + while (true == is_running) + { + SDL_Event event; + while (::SDL_PollEvent(&event)) + { + (void)::ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT) + { + is_running = false; + } + } + + ::ImGui_ImplOpenGL3_NewFrame(); + ::ImGui_ImplSDL3_NewFrame(); + ::ImGui::NewFrame(); + if (int8_t return_code = gui::render_memu_bar(gui.custom_panels, gui.custom_panels_capacity, + gui.custom_panels_visibility, &imgui_demo_visible); + WINDOW_SHOULD_CLOSE == return_code) + { + is_running = false; + } + + for (size_t i = 0; i < panels_capacity; ++i) + { + if (false == gui.custom_panels_visibility[i]) + { + continue; + } + + if (0 == ::strcmp(gui.custom_panels[i], PANEL_NAME_PERFORMANCE)) + { + int8_t return_code = gui::panel::render_performance_panel(&performance_panel, &performance_data, + &performance_panel_last_render); + if (ERROR_PANEL_IS_CLOSED == return_code) + { + gui.custom_panels_visibility[i] = false; + } + } + if (0 == ::strcmp(gui.custom_panels[i], PANEL_NAME_CPU)) + { + int8_t return_code = gui::panel::render_cpu_panel(&show_cpu_result_popup); + if (ERROR_PANEL_IS_CLOSED == return_code) + { + gui.custom_panels_visibility[i] = false; + } + } + } + + // End Frame. + ImGui::Render(); + const ImGuiIO& io = ImGui::GetIO(); + ::glViewport(0, 0, static_cast(io.DisplaySize.x), static_cast(io.DisplaySize.y)); + ::glClearColor(0.08f, 0.08f, 0.10f, 1.0f); + ::glClear(GL_COLOR_BUFFER_BIT); + + ::ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + if (bool sdl_ret_code = ::SDL_GL_SwapWindow(gui.window.data); false == sdl_ret_code) + { + LOG_ERROR(Render, "Failed to update window with OpenGL rendering: {}", SDL_GetError()); + is_running = false; + } + // Small delay to prevent excessive CPU usage std::this_thread::sleep_for(std::chrono::milliseconds(5)); } - // Cleanup - gui_manager->Shutdown(); - - return 0; -} \ No newline at end of file + gui::destroy(); +} diff --git a/core/memory/arena.cpp b/core/memory/arena.cpp index cdc8757..484f667 100644 --- a/core/memory/arena.cpp +++ b/core/memory/arena.cpp @@ -4,43 +4,50 @@ #include #endif -Memory::Arena Memory::arena_init() { - // TODO(GloriousEggroll): Replace malloc with a windows memory mapping API. +memory::arena_t memory::arena_init(size_t capacity) +{ + + // TODO(GloriousTaco:memory): Replace malloc with a windows memory mapping API. #ifdef WIN32 - auto data = - static_cast(malloc(sizeof(uint8_t) * MEMORY_CAPACITY)); + auto data = static_cast(malloc(sizeof(uint8_t) * MEMORY_CAPACITY)); #else - void* data = mmap(nullptr, MEMORY_CAPACITY, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (data == MAP_FAILED) { + void* data = ::mmap(nullptr, capacity, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (data == MAP_FAILED) + { return {0, 0, nullptr}; // Return invalid arena on failure } #endif - Memory::Arena arena = { - .capacity = MEMORY_CAPACITY, + + (void)std::memset(data, POISON_PATTERN, capacity); + memory::arena_t arena = { + .capacity = capacity, .size = 0, - .data = static_cast(data), + .data = data, }; return arena; } // new more memsafe code (ownedbywuigi) (i give up on windows compatibility for now, will stick to the old unsafe code) -const uint8_t* Memory::arena_allocate(Memory::Arena* arena, - const std::size_t size) { +const void* memory::arena_allocate(memory::arena_t* arena, const std::size_t size) +{ ASSERT(arena != nullptr); ASSERT(arena->size + size < arena->capacity); - const uint8_t* const data = &(arena->data[arena->size]); + const void* const data = &arena->data + arena->size; arena->size += size; return data; } -void Memory::arena_reset(Memory::Arena* arena) { - ASSERT(arena != nullptr); +void memory::arena_reset(memory::arena_t* arena) +{ + ASSERT(nullptr != arena); + ASSERT(nullptr != arena->data); arena->size = 0; + std::memset(arena->data, POISON_PATTERN, arena->capacity); } -void Memory::arena_free(Memory::Arena* arena) { +void memory::arena_free(memory::arena_t* arena) +{ ASSERT(arena != nullptr); arena->capacity = 0; arena->size = 0; - // TODO(GloriousTaco): Replace free with a memory safe alternative. + // TODO(GloriousTaco:memory): Replace free with a memory safe alternative. free(arena->data); } diff --git a/core/memory/arena.h b/core/memory/arena.h index a92957a..22dfd1e 100644 --- a/core/memory/arena.h +++ b/core/memory/arena.h @@ -5,21 +5,24 @@ #include #include -namespace Memory { +namespace memory +{ /* Defines the default size (in bytes) for memory arenas created via arena_init() */ #define MEMORY_CAPACITY 20480 // 20 KB +#define POISON_PATTERN 0xAA + /* * NAME - * Arena - Memory management structure for efficient allocation and de-allocation. + * arena_t - memory management structure for efficient allocation and de-allocation. * * SYNOPSIS * typedef struct { * std::size_t capacity; Total number of bytes allocated. * std::size_t size; The current number of bytes consumed. - * uint8_t* data; A pointer to the base address of the allocated memory buffer. - * } Arena; + * void* data; A pointer to the base address of the allocated memory buffer. + * } arena_t; * * DESCRIPTION * The arena struct handles allocating and managing contiguous memory blocks. @@ -29,18 +32,19 @@ namespace Memory { * maintaining a single contiguous block eliminates heap fragmentation * that occurs with frequent small allocations. */ -typedef struct { +typedef struct +{ std::size_t capacity; std::size_t size; - uint8_t* data; -} Arena; + void* data; +} arena_t; /* * NAME * arena_init - Initialize a memory arena with default capacity. * * SYNOPSIS - * Arena Memory::arena_init(); + * arema_t memory::arena_init(); * * DESCRIPTION * The function creates and returns a new memory arena instance with a @@ -48,16 +52,16 @@ typedef struct { * default capacity. * * RETURN VALUE - * Returns a valid Arena object on success. + * Returns a valid arema_t object on success. */ -extern Arena arena_init(); +extern memory::arena_t arena_init(size_t capacity); /* * NAME * arena_allocate - Allocate memory from a pre-initialized arena. * * SYNOPSIS - * const uint8_t Memory::arena_allocate(Memory::Arena* arena, std::size_t size); + * const void* memory::arena_allocate(memory::arena_t* arena, std::size_t size); * * DESCRIPTION * The function allocates size bytes from the specified arena. It assumes @@ -69,19 +73,19 @@ extern Arena arena_init(); * pointer is valid until the arena is reset or destroyed. * * NOTES - * Requires Arena to be initialized with arena_init() or similar. + * Requires arema_t to be initialized with arena_init() or similar. */ -const uint8_t* arena_allocate(Arena* arena, std::size_t size); +const void* arena_allocate(arena_t* arena, const std::size_t size); /* * NAME * arena_reset - Reset a memory arena's allocation size to zero. * * SYNOPSIS - * void Memory::arena_reset(Memory::Arena* arena); + * void memory::arena_reset(memory::arena_t* arena); * * DESCRIPTION - * The function resets the allocation size of a pre-initialized Arena to zero. + * The function resets the allocation size of a pre-initialized arema_t to zero. * This effectively "frees" all memory allocated from the arena without * deallocating the underlying buffer, allowing reuse of the capacity for * future allocations. @@ -91,21 +95,21 @@ const uint8_t* arena_allocate(Arena* arena, std::size_t size); * Does not free the underlying memory buffer. * Useful for reusing arenas without reallocation. */ -void arena_reset(Arena* arena); +void arena_reset(arena_t* arena); /** * NAME * arena_free - Free the memory allocated by an arena * * SYNOPSIS - * void Memory::arena_free(Memory::Arena* arena); + * void memory::arena_free(memory::arena_t* arena); * * DESCRIPTION - * The function releases the memory buffer associated with a Arena and + * The function releases the memory buffer associated with a arema_t and * resets its capacity and size to zero. This marks the arena as invalid for * future allocation unless reinitialized. */ -void arena_free(Memory::Arena* arena); +void arena_free(memory::arena_t* arena); -} // namespace Memory +} // namespace memory #endif //POUND_ARENA_H diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index ffbae12..c5b3a61 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -2,38 +2,17 @@ # GUI sources set(GUI_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/Window.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/GUIManager.cpp -) - -set(GUI_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/Window.h - ${CMAKE_CURRENT_SOURCE_DIR}/GUIManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/Panel.h -) - -# Panel sources -set(PANEL_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/panels/ConsolePanel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/CPUPanel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/PerformancePanel.cpp -) - -set(PANEL_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/panels/ConsolePanel.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/CPUPanel.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/PerformancePanel.h + ${CMAKE_CURRENT_SOURCE_DIR}/gui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/color.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels.cpp ) # Add all GUI sources to the main target target_sources(Pound PRIVATE - ${GUI_SOURCES} - ${GUI_HEADERS} - ${PANEL_SOURCES} - ${PANEL_HEADERS} + ${GUI_SOURCES} ) # Include directories for GUI target_include_directories(Pound PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/.. ) \ No newline at end of file diff --git a/gui/Colors.h b/gui/Colors.h deleted file mode 100644 index c34abac..0000000 --- a/gui/Colors.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include - -namespace Pound::GUI { - -class Colors { -public: - // Primary colors - static constexpr ImVec4 Primary = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); // #00c3e3 - static constexpr ImVec4 PrimaryHover = ImVec4(0.0f, 0.865f, 0.990f, 1.0f); // Lighter - static constexpr ImVec4 PrimaryActive = ImVec4(0.0f, 0.665f, 0.790f, 1.0f); // Darker - - // Secondary colors - static constexpr ImVec4 Secondary = ImVec4(1.0f, 0.271f, 0.329f, 1.0f); // #ff4554 - static constexpr ImVec4 SecondaryHover = ImVec4(1.0f, 0.371f, 0.429f, 1.0f); // Lighter - static constexpr ImVec4 SecondaryActive = ImVec4(0.9f, 0.171f, 0.229f, 1.0f); // Darker - - // Background colors - static constexpr ImVec4 Background = ImVec4(0.255f, 0.271f, 0.282f, 1.0f); // #414548 - static constexpr ImVec4 BackgroundDark = ImVec4(0.155f, 0.171f, 0.182f, 1.0f); - static constexpr ImVec4 BackgroundLight = ImVec4(0.355f, 0.371f, 0.382f, 1.0f); - - // Text colors - static constexpr ImVec4 Text = ImVec4(0.95f, 0.96f, 0.98f, 1.0f); - static constexpr ImVec4 TextDisabled = ImVec4(0.60f, 0.60f, 0.60f, 1.0f); - - // UI element colors - static constexpr ImVec4 Border = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); - static constexpr ImVec4 Frame = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); - static constexpr ImVec4 FrameHover = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); - static constexpr ImVec4 FrameActive = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - - // Special colors - static constexpr ImVec4 Success = ImVec4(0.0f, 0.8f, 0.0f, 1.0f); - static constexpr ImVec4 Warning = ImVec4(1.0f, 0.8f, 0.0f, 1.0f); - static constexpr ImVec4 Error = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); - static constexpr ImVec4 Info = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); - - // Utility functions - static ImVec4 WithAlpha(const ImVec4& color, float alpha) { - return ImVec4(color.x, color.y, color.z, alpha); - } - - static ImVec4 Lighten(const ImVec4& color, float amount = 0.1f) { - return ImVec4( - std::min(1.0f, color.x + amount), - std::min(1.0f, color.y + amount), - std::min(1.0f, color.z + amount), - color.w - ); - } - - static ImVec4 Darken(const ImVec4& color, float amount = 0.1f) { - return ImVec4( - std::max(0.0f, color.x - amount), - std::max(0.0f, color.y - amount), - std::max(0.0f, color.z - amount), - color.w - ); - } - - // Convert hex to ImVec4 (utility for future use) - static ImVec4 FromHex(uint32_t hex, float alpha = 1.0f) { - float r = ((hex >> 16) & 0xFF) / 255.0f; - float g = ((hex >> 8) & 0xFF) / 255.0f; - float b = (hex & 0xFF) / 255.0f; - return ImVec4(r, g, b, alpha); - } -}; - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/GUIManager.cpp b/gui/GUIManager.cpp deleted file mode 100644 index 39f50f7..0000000 --- a/gui/GUIManager.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. - -#include "GUIManager.h" -#include "Colors.h" -#include "Base/Logging/Log.h" -#include -#include -#include -#include -#include - -namespace Pound::GUI -{ - - GUIManager::GUIManager() = default; - - GUIManager::~GUIManager() - { - Shutdown(); - } - - bool GUIManager::Initialize(const std::string &title, int width, int height) - { - window = std::make_unique(); - - if (!window->Initialize(title, width, height)) - { - LOG_ERROR(Render, "Failed to initialize window"); - return false; - } - - // Initialize ImGui - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO &io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - // Setup style - ApplyTheme(); - - // Setup platform/renderer backends - ImGui_ImplSDL3_InitForOpenGL(window->GetSDLWindow(), window->GetGLContext()); - ImGui_ImplOpenGL3_Init("#version 330"); - - running = true; - return true; - } - - void GUIManager::Shutdown() - { - if (!running) - return; - - panels.clear(); - - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL3_Shutdown(); - ImGui::DestroyContext(); - - window.reset(); - running = false; - } - - void GUIManager::RunFrame() - { - if (!running) - return; - - window->ProcessEvents(); - - BeginFrame(); - - // Render menu bar - RenderMainMenuBar(); - - // Render all panels - for (auto &panel : panels) - { - if (panel->IsVisible()) - { - panel->Render(); - } - } - - // Demo window for debugging - if (show_demo_window) - { - ImGui::ShowDemoWindow(&show_demo_window); - } - - EndFrame(); - } - - void GUIManager::BeginFrame() - { - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL3_NewFrame(); - ImGui::NewFrame(); - } - - void GUIManager::EndFrame() - { - ImGui::Render(); - - ImGuiIO &io = ImGui::GetIO(); - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(0.08f, 0.08f, 0.10f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - window->SwapBuffers(); - } - - void GUIManager::RenderMainMenuBar() - { - if (ImGui::BeginMainMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Load ROM...", "Ctrl+O")) - { - // TODO: Implement ROM loading - } - ImGui::Separator(); - if (ImGui::MenuItem("Exit", "Alt+F4")) - { - window->SetShouldClose(true); - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Emulation")) - { - if (ImGui::MenuItem("Run CPU Test")) - { - if (cpu_test_callback) - { - cpu_test_callback(); - } - } - ImGui::Separator(); - if (ImGui::MenuItem("Pause", "F5")) - { - // TODO: Implement pause - } - if (ImGui::MenuItem("Reset", "F6")) - { - // TODO: Implement reset - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("View")) - { - for (auto &panel : panels) - { - bool visible = panel->IsVisible(); - if (ImGui::MenuItem(panel->GetName().c_str(), nullptr, &visible)) - { - panel->SetVisible(visible); - } - } - ImGui::Separator(); - if (ImGui::MenuItem("ImGui Demo", nullptr, &show_demo_window)) - { - // Toggle handled by flag - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Tools")) - { - if (ImGui::MenuItem("Settings", "Ctrl+,")) - { - // TODO: Open settings panel - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Help")) - { - if (ImGui::MenuItem("About")) - { - // TODO: Show about dialog - } - ImGui::EndMenu(); - } - - ImGui::EndMainMenuBar(); - } - } - - void GUIManager::ApplyTheme() - { - ImGuiStyle &style = ImGui::GetStyle(); - - // Modern theme with custom colors - style.WindowRounding = 8.0f; - style.FrameRounding = 4.0f; - style.PopupRounding = 4.0f; - style.ScrollbarRounding = 6.0f; - style.GrabRounding = 4.0f; - style.TabRounding = 4.0f; - - style.WindowTitleAlign = ImVec2(0.5f, 0.5f); - style.WindowMenuButtonPosition = ImGuiDir_Right; - - // Apply custom color scheme - style.Colors[ImGuiCol_Text] = Colors::Text; - style.Colors[ImGuiCol_TextDisabled] = Colors::TextDisabled; - style.Colors[ImGuiCol_WindowBg] = Colors::WithAlpha(Colors::Background, 0.95f); - style.Colors[ImGuiCol_ChildBg] = Colors::BackgroundDark; - style.Colors[ImGuiCol_PopupBg] = Colors::WithAlpha(Colors::Background, 0.94f); - style.Colors[ImGuiCol_Border] = Colors::Border; - style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - style.Colors[ImGuiCol_FrameBg] = Colors::BackgroundLight; - style.Colors[ImGuiCol_FrameBgHovered] = Colors::Lighten(Colors::BackgroundLight, 0.1f); - style.Colors[ImGuiCol_FrameBgActive] = Colors::Lighten(Colors::BackgroundLight, 0.2f); - style.Colors[ImGuiCol_TitleBg] = Colors::BackgroundDark; - style.Colors[ImGuiCol_TitleBgActive] = Colors::Background; - style.Colors[ImGuiCol_TitleBgCollapsed] = Colors::WithAlpha(Colors::BackgroundDark, 0.51f); - style.Colors[ImGuiCol_MenuBarBg] = Colors::BackgroundDark; - style.Colors[ImGuiCol_ScrollbarBg] = Colors::WithAlpha(Colors::BackgroundDark, 0.53f); - style.Colors[ImGuiCol_ScrollbarGrab] = Colors::BackgroundLight; - style.Colors[ImGuiCol_ScrollbarGrabHovered] = Colors::Lighten(Colors::BackgroundLight, 0.1f); - style.Colors[ImGuiCol_ScrollbarGrabActive] = Colors::Lighten(Colors::BackgroundLight, 0.2f); - style.Colors[ImGuiCol_CheckMark] = Colors::Primary; - style.Colors[ImGuiCol_SliderGrab] = Colors::Primary; - style.Colors[ImGuiCol_SliderGrabActive] = Colors::PrimaryActive; - style.Colors[ImGuiCol_Button] = Colors::WithAlpha(Colors::Primary, 0.40f); - style.Colors[ImGuiCol_ButtonHovered] = Colors::PrimaryHover; - style.Colors[ImGuiCol_ButtonActive] = Colors::PrimaryActive; - style.Colors[ImGuiCol_Header] = Colors::WithAlpha(Colors::Primary, 0.31f); - style.Colors[ImGuiCol_HeaderHovered] = Colors::WithAlpha(Colors::Primary, 0.80f); - style.Colors[ImGuiCol_HeaderActive] = Colors::Primary; - style.Colors[ImGuiCol_Separator] = Colors::Border; - style.Colors[ImGuiCol_SeparatorHovered] = Colors::WithAlpha(Colors::Primary, 0.78f); - style.Colors[ImGuiCol_SeparatorActive] = Colors::Primary; - style.Colors[ImGuiCol_ResizeGrip] = Colors::WithAlpha(Colors::Primary, 0.25f); - style.Colors[ImGuiCol_ResizeGripHovered] = Colors::WithAlpha(Colors::Primary, 0.67f); - style.Colors[ImGuiCol_ResizeGripActive] = Colors::WithAlpha(Colors::Primary, 0.95f); - style.Colors[ImGuiCol_Tab] = Colors::BackgroundLight; - style.Colors[ImGuiCol_TabHovered] = Colors::WithAlpha(Colors::Primary, 0.80f); - style.Colors[ImGuiCol_TabActive] = Colors::Primary; - style.Colors[ImGuiCol_TabUnfocused] = Colors::Background; - style.Colors[ImGuiCol_TabUnfocusedActive] = Colors::Lighten(Colors::Background, 0.1f); - style.Colors[ImGuiCol_PlotLines] = Colors::Primary; - style.Colors[ImGuiCol_PlotLinesHovered] = Colors::PrimaryHover; - style.Colors[ImGuiCol_PlotHistogram] = Colors::Secondary; - style.Colors[ImGuiCol_PlotHistogramHovered] = Colors::SecondaryHover; - style.Colors[ImGuiCol_TextSelectedBg] = Colors::WithAlpha(Colors::Primary, 0.35f); - style.Colors[ImGuiCol_DragDropTarget] = Colors::WithAlpha(Colors::Secondary, 0.90f); - style.Colors[ImGuiCol_NavHighlight] = Colors::Primary; - style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); - style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); - } - - void GUIManager::AddPanel(std::shared_ptr panel) - { - panels.push_back(panel); - } - - void GUIManager::RemovePanel(const std::string &name) - { - panels.erase( - std::remove_if(panels.begin(), panels.end(), - [&name](const std::shared_ptr &panel) - { - return panel->GetName() == name; - }), - panels.end()); - } -} \ No newline at end of file diff --git a/gui/GUIManager.h b/gui/GUIManager.h deleted file mode 100644 index 5a8ec67..0000000 --- a/gui/GUIManager.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include -#include -#include -#include "Window.h" -#include "Panel.h" - -namespace Pound::GUI { - -class GUIManager { -public: - GUIManager(); - ~GUIManager(); - - bool Initialize(const std::string& title, int width, int height); - void Shutdown(); - - void RunFrame(); - bool IsRunning() const { return running && window && !window->ShouldClose(); } - - void AddPanel(std::shared_ptr panel); - void RemovePanel(const std::string& name); - - // Callback for external systems - void SetCPUTestCallback(std::function callback) { cpu_test_callback = callback; } - -private: - void BeginFrame(); - void EndFrame(); - void RenderMainMenuBar(); - void ApplyTheme(); - - std::unique_ptr window; - std::vector> panels; - bool running = false; - bool show_demo_window = false; - - // Callbacks - std::function cpu_test_callback; -}; - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/Panel.h b/gui/Panel.h deleted file mode 100644 index 87c6269..0000000 --- a/gui/Panel.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include -#include - -namespace Pound::GUI -{ - - class Panel - { - public: - Panel(const std::string &name) : name(name) {} - virtual ~Panel() = default; - - virtual void Render() = 0; - - const std::string &GetName() const { return name; } - bool IsVisible() const { return visible; } - void SetVisible(bool vis) { visible = vis; } - - protected: - std::string name; - bool visible = true; - }; - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/Window.cpp b/gui/Window.cpp deleted file mode 100644 index 7c1a232..0000000 --- a/gui/Window.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. - -#include "Window.h" -#include "Base/Logging/Log.h" -#include -#include -#include - -namespace Pound::GUI -{ - - Window::Window() = default; - - Window::~Window() - { - Shutdown(); - } - - bool Window::Initialize(const std::string &title, int width, int height) - { - if (!SDL_Init(SDL_INIT_VIDEO)) - { - LOG_ERROR(Render, "Error while creating SDL3 Context!"); - return false; - } - - SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); - SDL_SetNumberProperty(props, "flags", SDL_WINDOW_OPENGL); - SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); - SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); - - window = SDL_CreateWindowWithProperties(props); - SDL_DestroyProperties(props); - - if (!window) - { - LOG_ERROR(Render, "Failed to create SDL window: {}", SDL_GetError()); - return false; - } - - SDL_SetWindowMinimumSize(window, 640, 480); - - gl_context = SDL_GL_CreateContext(window); - if (!gl_context) - { - LOG_ERROR(Render, "Failed to create OpenGL context: {}", SDL_GetError()); - return false; - } - - SDL_GL_MakeCurrent(window, gl_context); - SDL_GL_SetSwapInterval(1); - - return true; - } - - void Window::Shutdown() - { - if (gl_context) - { - SDL_GL_DestroyContext(gl_context); - gl_context = nullptr; - } - - if (window) - { - SDL_DestroyWindow(window); - window = nullptr; - } - - SDL_Quit(); - } - - void Window::ProcessEvents() - { - SDL_Event event; - while (SDL_PollEvent(&event)) - { - ImGui_ImplSDL3_ProcessEvent(&event); - - if (event.type == SDL_EVENT_QUIT) - { - should_close = true; - } - } - } - - void Window::SwapBuffers() - { - SDL_GL_SwapWindow(window); - } - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/Window.h b/gui/Window.h deleted file mode 100644 index 7752d7f..0000000 --- a/gui/Window.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include -#include -#include -#include - -namespace Pound::GUI -{ - - class Window - { - public: - Window(); - ~Window(); - - bool Initialize(const std::string &title, int width, int height); - void Shutdown(); - - SDL_Window *GetSDLWindow() const { return window; } - SDL_GLContext GetGLContext() const { return gl_context; } - - bool ShouldClose() const { return should_close; } - void SetShouldClose(bool close) { should_close = close; } - - void ProcessEvents(); - void SwapBuffers(); - - private: - SDL_Window *window = nullptr; - SDL_GLContext gl_context = nullptr; - bool should_close = false; - }; - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/color.cpp b/gui/color.cpp new file mode 100755 index 0000000..2cae110 --- /dev/null +++ b/gui/color.cpp @@ -0,0 +1,34 @@ +#include "color.h" + +ImVec4 gui::color::with_alpha(const ImVec4& color, float alpha) +{ + auto vec = ImVec4(color.x, color.y, color.z, alpha); + return vec; +} + +ImVec4 gui::color::lighten(const ImVec4& color, float amount) +{ + float x = std::min(1.0F, color.x + amount); + float y = std::min(1.0F, color.y + amount); + float z = std::min(1.0F, color.z + amount); + auto vec = ImVec4(x, y, z, color.w); + return vec; +} + +ImVec4 gui::color::darken(const ImVec4& color, float amount) +{ + float x = std::max(0.0F, color.x - amount); + float y = std::max(0.0F, color.y - amount); + float z = std::max(0.0F, color.z - amount); + auto vec = ImVec4(x, y, z, color.w); + return vec; +} + +ImVec4 gui::color::from_hex(uint32_t hex, float alpha) +{ + float r = static_cast(((hex >> 16) & 0xFF)) / 255.0f; + float g = static_cast(((hex >> 8) & 0xFF)) / 255.0f; + float b = static_cast((hex & 0xFF)) / 255.0f; + auto vec = ImVec4(r, g, b, alpha); + return vec; +} diff --git a/gui/color.h b/gui/color.h new file mode 100755 index 0000000..5a9d99e --- /dev/null +++ b/gui/color.h @@ -0,0 +1,137 @@ +#ifndef POUND_COLORS_H +#define POUND_COLORS_H + +#include + +namespace gui::color +{ +constexpr ImVec4 primary = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); // #00c3e3 +constexpr ImVec4 primary_hover = ImVec4(0.0f, 0.865f, 0.990f, 1.0f); // Lighter +constexpr ImVec4 primary_active = ImVec4(0.0f, 0.665f, 0.790f, 1.0f); // Darker + +// Secondary colors +constexpr ImVec4 secondary = ImVec4(1.0f, 0.271f, 0.329f, 1.0f); // #ff4554 +constexpr ImVec4 secondary_hover = ImVec4(1.0f, 0.371f, 0.429f, 1.0f); // Lighter +constexpr ImVec4 secondary_active = ImVec4(0.9f, 0.171f, 0.229f, 1.0f); // Darker + +// Background colors +constexpr ImVec4 background = ImVec4(0.255f, 0.271f, 0.282f, 1.0f); +constexpr ImVec4 background_dark = ImVec4(0.155f, 0.171f, 0.182f, 1.0f); +constexpr ImVec4 background_light = ImVec4(0.355f, 0.371f, 0.382f, 1.0f); + +// Text colors +constexpr ImVec4 text = ImVec4(0.95f, 0.96f, 0.98f, 1.0f); +constexpr ImVec4 text_disable = ImVec4(0.60f, 0.60f, 0.60f, 1.0f); + +// UI element colors +constexpr ImVec4 border = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); +constexpr ImVec4 frame = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); +constexpr ImVec4 frame_hover = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); +constexpr ImVec4 frame_active = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + +// Special colors +constexpr ImVec4 sucess = ImVec4(0.0f, 0.8f, 0.0f, 1.0f); +constexpr ImVec4 warning = ImVec4(1.0f, 0.8f, 0.0f, 1.0f); +constexpr ImVec4 error = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); +constexpr ImVec4 info = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); + +/* + * NAME + * with_alpha - Create a new color with adjusted alpha channel. + * + * SYNOPSIS + * ImVec4 gui::color::with_alpha(const ImVec4& color, float alpha channel) + * + * DESCRIPTION + * Returns a copy of the input color with the alpha component replaced by the specified value. The original color's + * RGB values remain unchanged. + * + * This should be used when the transparency level needs to be adjusted without modifying the original color value. + * + * RETURN VALUE + * A new ImVec4 instance with RGB values from `color` amd alpha value from `alpha`. + * + * EXAMPLES + * ImVec4 red = ImVec4(1.0F, 0.0F, 0.0F, 1.0F); + * ImVec4 semi_transparent_red = gui::color::with_alpha(red, 0.5F); + * // semi_transparent_red = (1.0, 0.0, 0.0, 0.5) + */ +ImVec4 with_alpha(const ImVec4& color, float alpha); + +/* + * NAME + * lighten - Create a new color with increased brightness. + * + * SYNOPSIS + * ImVec4 gui::color::lighten(const ImVec& color, float amount); + * + * DESCRIPTION + * Returns a copy of the input color with each RGB channel brightened by the specified amount. + * + * Brightening works by adding the amount to each RGB component and clamping the result between 0.0F and 1.0F. + * This is useful for creating lighter color variations, highlights, or glow effects while maintaining the original + * transparency. + * + * RETURN VALUE + * A new ImVec4 instance with brightened RGB values and unchanged alpha. + * + * NOTES + * - Negative amounts will result in darkening instead of lightening. + * + * EXAMPLES + * // Create a brighter version of a base color. + * ImVec4 blue = ImVec4(0.0F, 0.0F, 1.0F, 1.0F); + * ImVec4 lighter_blue = gui::color::lighten(blue, 0.3F); // (0.3, 0.3 1.0, 1.0) + */ +ImVec4 lighten(const ImVec4& color, float amount); + +/* + * NAME + * darken - Create a new color ith decreased brightness. + * + * SYNOPSIS + * ImVec4 - gui::color::darken(const ImVec4& color, float amount); + * + * DESCRIPTION + * Return a copy of the input color with each RGB channel darkened by the specified amount. + * + * Darkening works by subtracting the amount from each RGB component and ensuring the result does not go below 0.0F. + * This operation is useful for creating darker color variation, shadows, or dimmed effects while maintaining the + * original transparency. + * + * RETURNS + * A new ImVec4 instance with darkened RGB values and unchanged alpha. + * + * NOTES + * - Negative amounts will result in lightening instead of darkening. + * + * EXAMPLES + * // Create a darker version of a base color. + * ImVec4 yellow = ImVec4(1.0F, 1.0F, 0.0F, 1.0F); + * ImVec4 dark_yellow = gui::color::darken(yellow, 0.5F); // (0.5, 0.5, 0.0, 1.0) + */ +ImVec4 darken(const ImVec4& color, float amount); + +/* + * NAME + * from_hex - Convert a hex color value to an ImVec4 color with specified alpha. + * + * SYNOPSIS + * ImVec4 gui::color::from_hex(uint32_t hex, float alpha); + * + * DESCRIPTION + * Converts a 24-bit hexadecimal color value (RRGGBB format) to an ImVec4 color structure. The input hex value is + * interpreted as having three bytes: red, green, and blue components in that order. Each component ranges from + * 0x00 to 0xFF and is normalized to the ranged [0.0F, 1.0F] for the output. + * + * RETURN VALUE + * A new ImVec4 instance with RGB values from hex specified alpha. + * + * EXAMPLES + * // Convert pure red with full opacity. + * ImVec4 red = gui::color::from_hex(0xFF0000, 1.0F); // (1.0, 0.0, 0.0, 1.0) + */ +ImVec4 from_hex(uint32_t hex, float alpha); +} // namespace gui::color + +#endif //POUND_COLORS_H diff --git a/gui/gui.cpp b/gui/gui.cpp new file mode 100755 index 0000000..474c382 --- /dev/null +++ b/gui/gui.cpp @@ -0,0 +1,264 @@ +#include "gui.h" +#include "Base/Assert.h" +#include "Base/Logging/Log.h" +#include "color.h" +#include "imgui_impl_opengl3_loader.h" + +#include +#include +#include + +static void apply_theme(); + +//========================================================= +// PUBLIC FUNCTIONS +//========================================================= + +bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height) +{ + ASSERT(nullptr != window); + ASSERT(nullptr != title); + + bool ret = ::SDL_Init(SDL_INIT_VIDEO); + if (false == ret) + { + LOG_ERROR(Render, "Error creating SDL3 Context: {}", SDL_GetError()); + return false; + } + + SDL_PropertiesID properties = ::SDL_CreateProperties(); + if (0 == properties) + { + LOG_ERROR(Render, "Error creating SDL3 Properties: {}", SDL_GetError()); + return false; + } + + ret = ::SDL_SetStringProperty(properties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title); + if (false == ret) + { + LOG_ERROR(Render, "Error setting window title {}: {}", title, SDL_GetError()); + return false; + } + + (void)::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); + (void)::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); + + ret = ::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); + if (false == ret) + { + LOG_ERROR(Render, "Error setting window {} width {}: ", title, width, SDL_GetError()); + return false; + } + + ret = ::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); + if (false == ret) + { + LOG_ERROR(Render, "Error setting window {} height {}: ", title, height); + return false; + } + + (void)::SDL_SetNumberProperty(properties, "flags", SDL_WINDOW_OPENGL); + (void)::SDL_SetBooleanProperty(properties, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); + (void)::SDL_SetBooleanProperty(properties, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); + + window->data = ::SDL_CreateWindowWithProperties(properties); + ::SDL_DestroyProperties(properties); + if (nullptr == window->data) + { + LOG_ERROR(Render, "Failed to create window {}: {}", title, SDL_GetError()); + return false; + } + + ret = ::SDL_SetWindowMinimumSize(window->data, WINDOW_MINIMUM_SIZE_WIDTH, WINDOW_MINIMUM_SIZE_HEIGHT); + if (false == ret) + { + LOG_ERROR(Render, "Failed to set window {} minimum width and height: {}", title, SDL_GetError()); + return false; + } + + window->gl_context = ::SDL_GL_CreateContext(window->data); + if (nullptr == window->gl_context) + { + LOG_ERROR(Render, "Failed to create OpenGL context: {}", SDL_GetError()); + return false; + } + + ret = ::SDL_GL_MakeCurrent(window->data, window->gl_context); + if (false == ret) + { + LOG_ERROR(Render, "Failed to make set OpenGL context to window {}: {}", title, SDL_GetError()); + return false; + } + + ret = ::SDL_GL_SetSwapInterval(1); + if (false == ret) + { + LOG_ERROR(Render, "Failed to set swap interval for window {}: {}", title, SDL_GetError()); + return false; + } + + return true; +} + +void gui::window_destroy(gui::window_t* window) +{ + bool ret = false; + if (window->gl_context != nullptr) + { + ret = ::SDL_GL_DestroyContext(window->gl_context); + if (false == ret) + { + LOG_ERROR(Render, "Failed to destroy OpenGL context"); + } + } + if (window->data != nullptr) + { + ::SDL_DestroyWindow(window->data); + } +} + +bool gui::init_imgui(gui::window_t* main_window) +{ + ASSERT(nullptr != main_window->data); + ASSERT(nullptr != main_window->gl_context); + + // Initialize ImGui + IMGUI_CHECKVERSION(); + (void)::ImGui::CreateContext(); + ImGuiIO& io = ::ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + ::apply_theme(); + + bool ret = ::ImGui_ImplSDL3_InitForOpenGL(main_window->data, main_window->gl_context); + if (false == ret) + { + LOG_ERROR(Render, "Failed to init SDL3: {}", SDL_GetError()); + return false; + } + + ret = ::ImGui_ImplOpenGL3_Init("#version 330"); + if (false == ret) + { + LOG_ERROR(Render, "Failed to init OpenGL3: {}", SDL_GetError()); + return false; + } + + return true; +} + +int8_t gui::render_memu_bar(const char** panels, const size_t panels_count, bool* panels_visibility, + bool* imgui_demo_visible) +{ + int8_t return_code = GUI_SUCCESS; + if (true == ::ImGui::BeginMainMenuBar()) + { + if (true == ::ImGui::BeginMenu("File")) + { + ::ImGui::Separator(); + if (true == ::ImGui::MenuItem("Exit", "Alt+F4")) + { + return_code = WINDOW_SHOULD_CLOSE; + } + ::ImGui::EndMenu(); + } + if (true == ::ImGui::BeginMenu("View")) + { + for (size_t i = 0; i < panels_count; ++i) + { + (void)::ImGui::MenuItem(panels[i], nullptr, &panels_visibility[i]); + } + + ::ImGui::Separator(); + // The demo window will need to be rendered outside this nested if statement, or else it will close the next frame. + (void)::ImGui::MenuItem("ImGui Demo", nullptr, imgui_demo_visible); + ::ImGui::EndMenu(); + } + + ::ImGui::EndMainMenuBar(); + } + + if (true == *imgui_demo_visible) + { + ::ImGui::ShowDemoWindow(imgui_demo_visible); + } + + return return_code; +} + +void gui::destroy() +{ + ::ImGui_ImplOpenGL3_Shutdown(); + ::ImGui_ImplSDL3_Shutdown(); + ::ImGui::DestroyContext(); +} + +//========================================================= +// Private FUNCTIONS +//========================================================= + +void apply_theme() +{ + ImGuiStyle& style = ::ImGui::GetStyle(); + + // Modern theme with custom colors + style.WindowRounding = 8.0f; + style.FrameRounding = 4.0f; + style.PopupRounding = 4.0f; + style.ScrollbarRounding = 6.0f; + style.GrabRounding = 4.0f; + style.TabRounding = 4.0f; + + style.WindowTitleAlign = ImVec2(0.5f, 0.5f); + style.WindowMenuButtonPosition = ImGuiDir_Right; + + // Apply custom color scheme. + style.Colors[ImGuiCol_Text] = gui::color::text; + style.Colors[ImGuiCol_TextDisabled] = gui::color::text_disable; + style.Colors[ImGuiCol_WindowBg] = gui::color::with_alpha(gui::color::background, 0.95F); + style.Colors[ImGuiCol_ChildBg] = gui::color::background_dark; + style.Colors[ImGuiCol_PopupBg] = gui::color::with_alpha(gui::color::background, 0.94F); + style.Colors[ImGuiCol_Border] = gui::color::border; + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.0F, 0.0F, 0.0F, 0.0F); + style.Colors[ImGuiCol_FrameBg] = gui::color::background_light; + style.Colors[ImGuiCol_FrameBgHovered] = gui::color::lighten(gui::color::background_light, 0.1F); + style.Colors[ImGuiCol_FrameBgActive] = gui::color::lighten(gui::color::background_light, 0.2F); + style.Colors[ImGuiCol_TitleBg] = gui::color::background_dark; + style.Colors[ImGuiCol_TitleBgActive] = gui::color::background; + style.Colors[ImGuiCol_TitleBgCollapsed] = gui::color::with_alpha(gui::color::background_dark, 0.51F); + style.Colors[ImGuiCol_MenuBarBg] = gui::color::background_dark; + style.Colors[ImGuiCol_ScrollbarBg] = gui::color::with_alpha(gui::color::background_dark, 0.53F); + style.Colors[ImGuiCol_ScrollbarGrab] = gui::color::background_light; + style.Colors[ImGuiCol_ScrollbarGrabHovered] = gui::color::lighten(gui::color::background_light, 0.1F); + style.Colors[ImGuiCol_ScrollbarGrabActive] = gui::color::lighten(gui::color::background_light, 0.2F); + style.Colors[ImGuiCol_CheckMark] = gui::color::primary; + style.Colors[ImGuiCol_SliderGrab] = gui::color::primary; + style.Colors[ImGuiCol_SliderGrabActive] = gui::color::primary_active; + style.Colors[ImGuiCol_Button] = gui::color::with_alpha(gui::color::primary, 0.4F); + style.Colors[ImGuiCol_ButtonHovered] = gui::color::primary_hover; + style.Colors[ImGuiCol_ButtonActive] = gui::color::primary_active; + style.Colors[ImGuiCol_Header] = gui::color::with_alpha(gui::color::primary, 0.4F); + style.Colors[ImGuiCol_HeaderHovered] = gui::color::with_alpha(gui::color::primary, 0.8F); + style.Colors[ImGuiCol_HeaderActive] = gui::color::primary; + style.Colors[ImGuiCol_Separator] = gui::color::border; + style.Colors[ImGuiCol_SeparatorHovered] = gui::color::with_alpha(gui::color::primary, 0.78F); + style.Colors[ImGuiCol_SeparatorActive] = gui::color::primary; + style.Colors[ImGuiCol_ResizeGrip] = gui::color::with_alpha(gui::color::primary, 0.25F); + style.Colors[ImGuiCol_ResizeGripHovered] = gui::color::with_alpha(gui::color::primary, 0.67F); + style.Colors[ImGuiCol_ResizeGripActive] = gui::color::with_alpha(gui::color::primary, 0.95F); + style.Colors[ImGuiCol_Tab] = gui::color::background_light; + style.Colors[ImGuiCol_TabHovered] = gui::color::with_alpha(gui::color::primary, 0.8F); + style.Colors[ImGuiCol_TabActive] = gui::color::primary; + style.Colors[ImGuiCol_TabUnfocused] = gui::color::background; + style.Colors[ImGuiCol_TabUnfocusedActive] = gui::color::lighten(gui::color::background, 0.1F); + style.Colors[ImGuiCol_PlotLines] = gui::color::primary; + style.Colors[ImGuiCol_PlotLinesHovered] = gui::color::primary_hover; + style.Colors[ImGuiCol_PlotHistogram] = gui::color::secondary; + style.Colors[ImGuiCol_PlotHistogramHovered] = gui::color::secondary_hover; + style.Colors[ImGuiCol_TextSelectedBg] = gui::color::with_alpha(gui::color::primary, 0.35F); + style.Colors[ImGuiCol_DragDropTarget] = gui::color::with_alpha(gui::color::secondary, 0.9F); + style.Colors[ImGuiCol_NavHighlight] = gui::color::primary; + style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); +} diff --git a/gui/gui.h b/gui/gui.h new file mode 100755 index 0000000..1f1fc6c --- /dev/null +++ b/gui/gui.h @@ -0,0 +1,219 @@ +#ifndef POUND_GUI_H +#define POUND_GUI_H + +#include +#include +#include "memory/arena.h" + +namespace gui +{ + +#define WINDOW_MINIMUM_SIZE_WIDTH 640 +#define WINDOW_MINIMUM_SIZE_HEIGHT 480 + +#define GUI_SUCCESS 0 +#define WINDOW_SHOULD_CLOSE 1 +#define ERROR_OPENGL 2 + +/* + * NAME + * window_t - Structure representing a window with OpenGL context. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * typedef struct { + * SDL_Window* data; // Window created by the SDL library. + * SDL_GLContext gl_context; // OpenGL context associated with the window. + * } window_t; + * + * DESCRIPTION + * The window_t structure is used to represent a window along with its associated OpenGL context. + * + * EXAMPLE + * + * #include "gui/gui.h" + * #include + * + * int main() { + * gui::window_t window; + * + * // Initialize the widow. + * if (!gui::window_init(&window, "Pound Emulator", 800, 600)) { + * fprintf(stderr, "Failed to initialize the window. \n"); + * return -1; + * } + * + * // Clean up when done. + * gui::window_destroy(&window); + * } + */ +typedef struct +{ + SDL_Window* data; + SDL_GLContext gl_context; +} window_t; + +/* + * NAME + * window_init - Initializes a window with specified properties. + * + * SYNOPSIS + * #include "gui/gui" + * + * bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height); + * + * DESCRIPTION + * The function initializes a window with the given parameters. + * + * RETURN VALUE + * The function returns true if the window successfully initialized with all properties set, false otherwise. + * + * NOTES + * - Ensure that the window is not null before calling this function. + * - The window will be created with OpenGL support. + */ +bool window_init(window_t* window, const char* title, int64_t width, int64_t height); + +/* + * NAME + * window_destroy - Destroys a previously initialized window and its associated OpenGL context. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * void gui::window_destroy(gui::window_t* window); + * + * DESCRIPTION + * The function cleans up resources associated with a previous initialized window. + * + * NOTES + * - Ensure that the window parameter is valid and points to a previous initialized window structure. + * - It is essential to call this function for every window created with gui::window_init() to prevent resource leaks. + */ +void window_destroy(window_t* window); + +/* + * NAME + * gui_t - Structure representing the main GUI system with window and panel management. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * typedef struct { + * window_t window; // Main window of the GUI with OpenGL context + * const char** custom_panels; // Array of pointers to custom panel names + * bool* custom_panels_visibility; // Array tracking visibility state of each panel + * size_t custom_panels_capacity; // Maximum number of panels that can be managed + * } gui_t; + * + * DESCRIPTION + * The gui_t structure represents the core GUI system, managing the main window and any + * additional user-defined panels. It provides a container for all resources needed to + * maintain and render the graphical interface. + * + * This structure should be initialized before use and destroyed when no longer needed to + * properly manage memory and system resources. + * + * EXAMPLE + * + * #include "gui/gui.h" + * #include + * + * int main() { + * gui::gui_t gui; + * + * // Initialize the GUI system + * if (!gui::init(&gui, &main_window)) { + * fprintf(stderr, "Failed to initialize the GUI. \n"); + * return -1; + * } + * + * // Use the GUI for rendering... + * ... + * + * // Clean up when done. + * gui::destroy(&gui); + * } + */ +typedef struct +{ + window_t window; + const char** custom_panels; + bool* custom_panels_visibility; + size_t custom_panels_capacity; +} gui_t; + +/* + * NAME + * init_imgui - Initializes ImGui and its integration with SDL3 and OpenGL. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * bool gui::init_imgui(gui::window_t* main_window); + * + * DESCRIPTION + * This function initializes the ImGui library along with its integration with + * SDL3 for input handling and OpenGL for rendering. It sets up all necessary + * configurations and context required for ImGui to work properly. + * + * RETURN VALUE + * Returns true if ImGui initialization was successful, false otherwise. + * + * NOTES + * - The function assumes that the main window has already been created and initialized + * - This should be called before any other ImGui functions are used + */ +bool init_imgui(gui::window_t* main_window); + +/* + * NAME + * render_memu_bar - Renders a main menu bar with standard menus and custom panels. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * int8_t gui::render_memu_bar(const char** panels, const size_t panels_count, + * bool* panels_visibility, bool* imgui_demo_visible); + * + * DESCRIPTION + * This function renders a main menu bar using ImGui that includes standard menus like File and View. + * + * The File menu contains an Exit option that triggers window closure. + * The View menu displays all custom panels registered with their visibility toggles, + * plus an optional ImGui demo window toggle. + * + * PARAMETERS + * panels - Array of panel names to be displayed in the View menu + * panels_count - Number of panels in the array + * panels_visibility - Boolean array indicating current visibility state for each panel + * imgui_demo_visible - Pointer to boolean controlling visibility of ImGui demo window + * + * RETURN VALUE + * Returns one of these codes: + * GUI_SUCCESS - Normal operation + * WINDOW_SHOULD_CLOSE - Exit option was selected + * + * NOTES + * - Panel visibility state is toggled when corresponding menu items are clicked + */ +int8_t render_memu_bar(const char** panels, size_t panels_count, bool* panels_visibility, bool* imgui_demo_visible); + +/* + * NAME + * destroy - Destroys a GUI system and cleans up resources. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * void gui::destroy(); + * + * DESCRIPTION + * The function cleans up and releases all resources associated with the GUI system. + */ +void destroy(); + +} // namespace gui + +#endif //POUND_GUI_H diff --git a/gui/panels.cpp b/gui/panels.cpp new file mode 100755 index 0000000..59803dc --- /dev/null +++ b/gui/panels.cpp @@ -0,0 +1,118 @@ +#include "panels.h" +#include +#include "ARM/cpu.h" +#include "imgui.h" + +int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data, + std::chrono::steady_clock::time_point* last_render) +{ + bool is_visible = true; + (void)::ImGui::Begin(PANEL_NAME_PERFORMANCE, &is_visible); + if (false == is_visible) + { + ::ImGui::End(); + return ERROR_PANEL_IS_CLOSED; + } + + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now - *last_render); + ++data->frame_count; + if (duration.count() >= 100) + { + // Every 100ms + data->fps = data->frame_count * 1000.0f / duration.count(); + data->frame_time = duration.count() / (float)data->frame_count; + + panel->fps_history.push_back(data->fps); + panel->frame_time_history.push_back(data->frame_time); + + // Keep history size limited + while (panel->fps_history.size() > FRAME_TIME_HISTORY_SIZE) + { + panel->fps_history.pop_front(); + } + while (panel->frame_time_history.size() > FRAME_TIME_HISTORY_SIZE) + + { + panel->frame_time_history.pop_front(); + } + + data->frame_count = 0; + *last_render = now; + + // TODO(GloriousTaco:gui): Get actual CPU and memory usage + data->cpu_usage = 0.0f; + data->memory_usage = 0.0f; + } + ::ImGui::Text("FPS: %.1f", data->fps); + ::ImGui::Text("Frame Time: %.2f ms", data->frame_time); + ::ImGui::Separator(); + + // Frame Time Graph + if (false == panel->frame_time_history.empty()) + { + float frame_time_array[FRAME_TIME_HISTORY_SIZE] = {}; + (void)std::copy(panel->frame_time_history.begin(), panel->frame_time_history.end(), frame_time_array); + + ::ImGui::Text("Frame Time History (ms):"); + ::ImGui::PlotLines("##FrameTime", frame_time_array, (int)panel->frame_time_history.size(), 0, nullptr, 0.0f, + 33.33f, ImVec2(0, 80)); + } + + ::ImGui::Separator(); + + // System info (placeholder) + ::ImGui::Text("CPU Usage: %.1f%%", data->cpu_usage); + ::ImGui::Text("Memory Usage: %.1f MB", data->memory_usage); + + // Emulation stats + ::ImGui::Separator(); + ::ImGui::Text("Emulation Statistics:"); + ::ImGui::Text("Instructions/sec: N/A"); + ::ImGui::Text("JIT Cache Usage: N/A"); + + ::ImGui::End(); + return PANEL_SUCCESS; +} + +int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup) +{ + bool is_visible = true; + (void)::ImGui::Begin(PANEL_NAME_CPU, &is_visible, ImGuiWindowFlags_NoCollapse); + if (false == is_visible) + { + ::ImGui::End(); + return ERROR_PANEL_IS_CLOSED; + } + + if (::ImGui::Button("Run CPU Test", ImVec2(120, 0))) + { + ::cpuTest(); + *show_cpu_result_popup = true; + } + if (true == *show_cpu_result_popup) + { + ::ImGui::OpenPopup("CPU Test Result"); + } + + if (::ImGui::BeginPopupModal("CPU Test Result", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ::ImGui::Text("The CPU test has been executed successfully!"); + ::ImGui::Text("Check the console for detailed output."); + ::ImGui::Separator(); + ::ImGui::Text("Note: Pound is still in pre-alpha state."); + + ::ImGui::Spacing(); + + if (::ImGui::Button("OK", ImVec2(120, 0))) + { + *show_cpu_result_popup = false; + ::ImGui::CloseCurrentPopup(); + } + + ::ImGui::EndPopup(); + } + + ::ImGui::End(); + return PANEL_SUCCESS; +} diff --git a/gui/panels.h b/gui/panels.h new file mode 100755 index 0000000..fa6d56d --- /dev/null +++ b/gui/panels.h @@ -0,0 +1,113 @@ +#ifndef POUND_PANELS_H +#define POUND_PANELS_H + +#include +#include + +namespace gui::panel +{ +#define PANEL_NAME_CPU "Cpu" +#define PANEL_NAME_PERFORMANCE "Performance" +#define FRAME_TIME_HISTORY_SIZE 128 + +#define PANEL_SUCCESS 0 +#define ERROR_PANEL_IS_CLOSED 1 + +/* + * NAME + * performance_panel_t - Structure for tracking performance metrics history. + * + * SYNOPSIS + * #include "gui/panels.h" + * + * typedef struct { + * std::deque fps_history; // Historical FPS values over time + * std::deque frame_time_history; // Historical frame time values over time + * } performance_panel_t; + * + * DESCRIPTION + * The performance_panel_t structure maintains a history of performance metrics for visualization and analysis. + */ +typedef struct +{ + std::deque fps_history; + std::deque frame_time_history; +} performance_panel_t; + +/* + * NAME + * performance_data_t - Structure for storing current performance metrics. + * + * SYNOPSIS + * #include "gui/panels.h" + * + * typedef struct { + * float_t fps; // Current frames per second + * float_t frame_time; // Time taken to render a single frame in seconds + * float_t cpu_usage; // CPU usage percentage + * float_t memory_usage; // Memory usage percentage + * int32_t frame_count; // Total number of frames rendered since startup + * } performance_data_t; + * + * DESCRIPTION + * The performance_data_t structure contains current runtime metrics per frame for monitoring + * application performance. + */ +typedef struct +{ + float_t fps; + float_t frame_time; + float_t cpu_usage; + float_t memory_usage; + int32_t frame_count; + +} performance_data_t; + +/* + * NAME + * render_performance_panel - Renders a performance monitoring panel with FPS and system metrics. + * + * SYNOPSIS + * #include "gui/gui.h" + * + * int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, + * performance_data_t* data, + * std::chrono::steady_clock::time_point* last_render); + * + * DESCRIPTION + * This function renders a performance monitoring panel that tracks and displays: + * - Real-time FPS (frames per second) calculation + * - Frame time statistics with historical graph visualization + * - CPU and memory usage metrics (placeholder values) + * - Emulation statistics (placeholder values) + * + * + * RETURN VALUE + * Returns one of these codes: + * PANEL_SUCCESS - Normal operation + * ERROR_PANEL_IS_CLOSED - Panel was closed by user + * + */ +int8_t render_performance_panel(performance_panel_t* panel, performance_data_t* data, + std::chrono::steady_clock::time_point* last_render); + +/* + * NAME + * render_cpu_panel - Renders a CPU testing panel with test execution capability. + * + * SYNOPSIS + * #include "gui/panels.cpp" + * + * int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup); + * + * DESCRIPTION + * This function renders a CPU testing panel. + * + * RETURN VALUE + * Returns one of these codes: + * PANEL_SUCCESS - Normal operation + * ERROR_PANEL_IS_CLOSED - Panel was closed by user + */ +int8_t render_cpu_panel(bool* show_cpu_result_popup); +} // namespace gui::panel +#endif //POUND_PANELS_H diff --git a/gui/panels/CPUPanel.cpp b/gui/panels/CPUPanel.cpp deleted file mode 100644 index 3a8f057..0000000 --- a/gui/panels/CPUPanel.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. - -#include "CPUPanel.h" -#include "../Colors.h" -#include -#include - -namespace Pound::GUI { - -CPUPanel::CPUPanel() : Panel("CPU Debug") {} - -void CPUPanel::Render() { - if (!ImGui::Begin(name.c_str(), &visible, ImGuiWindowFlags_NoCollapse)) { - ImGui::End(); - return; - } - - // Control buttons with custom colors - ImGui::PushStyleColor(ImGuiCol_Button, Colors::WithAlpha(Colors::Primary, 0.40f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, Colors::PrimaryHover); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, Colors::PrimaryActive); - - if (ImGui::Button("Run CPU Test", ImVec2(120, 0))) { - if (cpu_test_callback) { - cpu_test_callback(); - show_test_result = true; - } - } - - ImGui::SameLine(); - if (ImGui::Button("Step", ImVec2(60, 0))) { - // TODO: Implement step functionality - } - - ImGui::SameLine(); - - // Reset button with secondary color - ImGui::PushStyleColor(ImGuiCol_Button, Colors::WithAlpha(Colors::Secondary, 0.40f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, Colors::SecondaryHover); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, Colors::SecondaryActive); - - if (ImGui::Button("Reset", ImVec2(60, 0))) { - // TODO: Implement reset functionality - } - - ImGui::PopStyleColor(6); // Pop all 6 color changes - - ImGui::Separator(); - - // Tabs for different views - if (ImGui::BeginTabBar("CPUTabBar")) { - if (ImGui::BeginTabItem("Registers")) { - // General purpose registers - ImGui::Text("General Purpose Registers:"); - ImGui::Separator(); - - if (ImGui::BeginTable("RegisterTable", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 60.0f); - ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 60.0f); - ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableHeadersRow(); - - for (int i = 0; i < 16; i++) { - ImGui::TableNextRow(); - - // Left column - ImGui::TableSetColumnIndex(0); - ImGui::Text("X%d", i); - ImGui::TableSetColumnIndex(1); - ImGui::Text("0x%016llX", cpu_state.registers[i]); - - // Right column - if (i + 16 < 32) { - ImGui::TableSetColumnIndex(2); - ImGui::Text("X%d", i + 16); - ImGui::TableSetColumnIndex(3); - ImGui::Text("0x%016llX", cpu_state.registers[i + 16]); - } - } - - ImGui::EndTable(); - } - - ImGui::Spacing(); - ImGui::Text("Program Counter: 0x%016llX", cpu_state.pc); - ImGui::Text("Flags: 0x%08X", cpu_state.flags); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Memory")) { - static char addr_input[17] = "0000000000000000"; - ImGui::Text("Memory Viewer"); - ImGui::Separator(); - - ImGui::InputText("Address", addr_input, sizeof(addr_input), - ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); - - // TODO: Implement memory viewer - ImGui::Text("Memory viewer will be implemented here"); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Disassembly")) { - ImGui::Text("Disassembly View"); - ImGui::Separator(); - - // TODO: Implement disassembly view - ImGui::Text("Disassembly will be shown here"); - - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - // Test result popup - if (show_test_result) { - ImGui::OpenPopup("CPU Test Result"); - } - - if (ImGui::BeginPopupModal("CPU Test Result", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("The CPU test has been executed successfully!"); - ImGui::Text("Check the console for detailed output."); - ImGui::Separator(); - ImGui::Text("Note: Pound is still in pre-alpha state."); - - ImGui::Spacing(); - - if (ImGui::Button("OK", ImVec2(120, 0))) { - show_test_result = false; - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - ImGui::End(); -} - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/CPUPanel.h b/gui/panels/CPUPanel.h deleted file mode 100644 index 2e92f03..0000000 --- a/gui/panels/CPUPanel.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include "../Panel.h" -#include - -namespace Pound::GUI -{ - - class CPUPanel : public Panel - { - public: - CPUPanel(); - - void Render() override; - void SetCPUTestCallback(std::function callback) { cpu_test_callback = callback; } - void ShowTestResult(bool show = true) { show_test_result = show; } - - private: - std::function cpu_test_callback; - bool show_test_result = false; - - // CPU state display (placeholder for future integration) - struct CPUState - { - uint64_t registers[32] = {0}; - uint64_t pc = 0; - uint32_t flags = 0; - } cpu_state; - }; - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/ConsolePanel.cpp b/gui/panels/ConsolePanel.cpp deleted file mode 100644 index 31b9e28..0000000 --- a/gui/panels/ConsolePanel.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. - -#include "ConsolePanel.h" -#include "../Colors.h" -#include -#include -#include -#include - -namespace Pound::GUI -{ - - ConsolePanel::ConsolePanel() : Panel("Console") {} - - void ConsolePanel::Render() - { - if (!ImGui::Begin(name.c_str(), &visible)) - { - ImGui::End(); - return; - } - - // Options - if (ImGui::BeginPopup("Options")) - { - ImGui::Checkbox("Auto-scroll", &auto_scroll); - ImGui::Checkbox("Show timestamps", &show_timestamps); - ImGui::EndPopup(); - } - - // Buttons - if (ImGui::Button("Options")) - ImGui::OpenPopup("Options"); - ImGui::SameLine(); - if (ImGui::Button("Clear")) - Clear(); - ImGui::SameLine(); - ImGui::Text("Log entries: %zu", log_buffer.size()); - - ImGui::Separator(); - - // Log display - ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - - for (const auto &entry : log_buffer) - { - ImGui::PushStyleColor(ImGuiCol_Text, entry.color); - ImGui::TextUnformatted(entry.text.c_str()); - ImGui::PopStyleColor(); - } - - if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); - - ImGui::PopStyleVar(); - ImGui::EndChild(); - - ImGui::End(); - } - - void ConsolePanel::AddLog(const std::string &text) - { - std::string final_text = text; - - if (show_timestamps) - { - auto now = std::chrono::system_clock::now(); - auto time_t = std::chrono::system_clock::to_time_t(now); - std::stringstream ss; - ss << "[" << std::put_time(std::localtime(&time_t), "%H:%M:%S") << "] "; - final_text = ss.str() + text; - } - - log_buffer.push_back({final_text, GetLogColor(text)}); - - // Keep buffer size limited - while (log_buffer.size() > MAX_LOG_ENTRIES) - { - log_buffer.pop_front(); - } - } - - void ConsolePanel::Clear() - { - log_buffer.clear(); - } - - ImVec4 ConsolePanel::GetLogColor(const std::string &text) const - { - if (text.find("[ERROR]") != std::string::npos) - { - return Colors::Error; - } - else if (text.find("[WARN]") != std::string::npos) - { - return Colors::Warning; - } - else if (text.find("[INFO]") != std::string::npos) - { - return Colors::Info; - } - else if (text.find("[DEBUG]") != std::string::npos) - { - return Colors::TextDisabled; - } - return Colors::Text; - } -} \ No newline at end of file diff --git a/gui/panels/ConsolePanel.h b/gui/panels/ConsolePanel.h deleted file mode 100644 index e3aeda5..0000000 --- a/gui/panels/ConsolePanel.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include "../Panel.h" -#include -#include -#include - -namespace Pound::GUI -{ - - class ConsolePanel : public Panel - { - public: - ConsolePanel(); - - void Render() override; - void AddLog(const std::string &text); - void Clear(); - - private: - struct LogEntry - { - std::string text; - ImVec4 color; - }; - - std::deque log_buffer; - bool auto_scroll = true; - bool show_timestamps = true; - static constexpr size_t MAX_LOG_ENTRIES = 1000; - - ImVec4 GetLogColor(const std::string &text) const; - }; - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/PerformancePanel.cpp b/gui/panels/PerformancePanel.cpp deleted file mode 100644 index 80cb0e8..0000000 --- a/gui/panels/PerformancePanel.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. - -#include "PerformancePanel.h" -#include - -namespace Pound::GUI -{ - - PerformancePanel::PerformancePanel() : Panel("Performance") - { - last_update = std::chrono::steady_clock::now(); - } - - void PerformancePanel::Render() - { - if (!ImGui::Begin(name.c_str(), &visible)) - { - ImGui::End(); - return; - } - - Update(); - - // Performance metrics - ImGui::Text("FPS: %.1f", current_data.fps); - ImGui::Text("Frame Time: %.2f ms", current_data.frame_time); - ImGui::Separator(); - - // FPS Graph - if (!fps_history.empty()) - { - float fps_array[HISTORY_SIZE]; - std::copy(fps_history.begin(), fps_history.end(), fps_array); - - ImGui::Text("FPS History:"); - ImGui::PlotLines("##FPS", fps_array, (int)fps_history.size(), 0, nullptr, - 0.0f, 144.0f, ImVec2(0, 80)); - } - - // Frame Time Graph - if (!frame_time_history.empty()) - { - float frame_time_array[HISTORY_SIZE]; - std::copy(frame_time_history.begin(), frame_time_history.end(), frame_time_array); - - ImGui::Text("Frame Time History (ms):"); - ImGui::PlotLines("##FrameTime", frame_time_array, (int)frame_time_history.size(), 0, nullptr, - 0.0f, 33.33f, ImVec2(0, 80)); - } - - ImGui::Separator(); - - // System info (placeholder) - ImGui::Text("CPU Usage: %.1f%%", current_data.cpu_usage); - ImGui::Text("Memory Usage: %.1f MB", current_data.memory_usage); - - // Emulation stats - ImGui::Separator(); - ImGui::Text("Emulation Statistics:"); - ImGui::Text("Instructions/sec: N/A"); - ImGui::Text("JIT Cache Usage: N/A"); - - ImGui::End(); - } - - void PerformancePanel::Update() - { - frame_count++; - - auto now = std::chrono::steady_clock::now(); - auto duration = std::chrono::duration_cast(now - last_update); - - if (duration.count() >= 100) - { // Update every 100ms - current_data.fps = frame_count * 1000.0f / duration.count(); - current_data.frame_time = duration.count() / (float)frame_count; - - fps_history.push_back(current_data.fps); - frame_time_history.push_back(current_data.frame_time); - - // Keep history size limited - while (fps_history.size() > HISTORY_SIZE) - { - fps_history.pop_front(); - } - while (frame_time_history.size() > HISTORY_SIZE) - { - frame_time_history.pop_front(); - } - - frame_count = 0; - last_update = now; - - // TODO: Get actual CPU and memory usage - current_data.cpu_usage = 0.0f; - current_data.memory_usage = 0.0f; - } - } - -} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/PerformancePanel.h b/gui/panels/PerformancePanel.h deleted file mode 100644 index 2e2dfe7..0000000 --- a/gui/panels/PerformancePanel.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. -#pragma once - -#include "../Panel.h" -#include -#include - -namespace Pound::GUI -{ - - class PerformancePanel : public Panel - { - public: - PerformancePanel(); - - void Render() override; - void Update(); - - private: - struct PerformanceData - { - float fps = 0.0f; - float frame_time = 0.0f; - float cpu_usage = 0.0f; - float memory_usage = 0.0f; - }; - - PerformanceData current_data; - std::deque fps_history; - std::deque frame_time_history; - static constexpr size_t HISTORY_SIZE = 120; - - std::chrono::steady_clock::time_point last_update; - int frame_count = 0; - }; - -} // namespace Pound::GUI \ No newline at end of file