mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
feat!: rewrote program in a data oriented style.
This is because the source code is objected oriented which is not cpu cache friendly, making the program slower than it has to be. Yuzu's entire codebase is written in a objected oriented way and I wonder how much faster it could if they had use DoD principles from the very beginning. That's why I want to instill DoD fundamentals early on so this won't be a problem going forward. Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
parent
653b84c264
commit
ba45834583
26 changed files with 1179 additions and 1162 deletions
91
CONTRIBUTING.md
Normal file
91
CONTRIBUTING.md
Normal file
|
|
@ -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(<Username>:<section>): ...
|
||||
|
||||
Where <section> 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.
|
||||
14
README.md
14
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.
|
||||
|
|
|
|||
20
core/ARM/cpu.cpp
Executable file
20
core/ARM/cpu.cpp
Executable file
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
169
core/main.cpp
169
core/main.cpp
|
|
@ -1,89 +1,128 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#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 <SDL3/SDL_opengl.h>
|
||||
#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<Pound::GUI::GUIManager>();
|
||||
|
||||
// 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<Pound::GUI::ConsolePanel>();
|
||||
auto cpu_panel = std::make_shared<Pound::GUI::CPUPanel>();
|
||||
auto performance_panel = std::make_shared<Pound::GUI::PerformancePanel>();
|
||||
|
||||
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<GLint>(io.DisplaySize.x), static_cast<GLint>(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;
|
||||
}
|
||||
gui::destroy();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,43 +4,50 @@
|
|||
#include <sys/mman.h>
|
||||
#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<uint8_t*>(malloc(sizeof(uint8_t) * MEMORY_CAPACITY));
|
||||
auto data = static_cast<uint8_t*>(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<uint8_t*>(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,24 @@
|
|||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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}/..
|
||||
)
|
||||
73
gui/Colors.h
73
gui/Colors.h
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
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
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "GUIManager.h"
|
||||
#include "Colors.h"
|
||||
#include "Base/Logging/Log.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_sdl3.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Pound::GUI
|
||||
{
|
||||
|
||||
GUIManager::GUIManager() = default;
|
||||
|
||||
GUIManager::~GUIManager()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool GUIManager::Initialize(const std::string &title, int width, int height)
|
||||
{
|
||||
window = std::make_unique<Window>();
|
||||
|
||||
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> 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> &panel)
|
||||
{
|
||||
return panel->GetName() == name;
|
||||
}),
|
||||
panels.end());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#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> panel);
|
||||
void RemovePanel(const std::string& name);
|
||||
|
||||
// Callback for external systems
|
||||
void SetCPUTestCallback(std::function<void()> callback) { cpu_test_callback = callback; }
|
||||
|
||||
private:
|
||||
void BeginFrame();
|
||||
void EndFrame();
|
||||
void RenderMainMenuBar();
|
||||
void ApplyTheme();
|
||||
|
||||
std::unique_ptr<Window> window;
|
||||
std::vector<std::shared_ptr<Panel>> panels;
|
||||
bool running = false;
|
||||
bool show_demo_window = false;
|
||||
|
||||
// Callbacks
|
||||
std::function<void()> cpu_test_callback;
|
||||
};
|
||||
|
||||
} // namespace Pound::GUI
|
||||
27
gui/Panel.h
27
gui/Panel.h
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <imgui.h>
|
||||
|
||||
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
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "Window.h"
|
||||
#include "Base/Logging/Log.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_sdl3.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
|
||||
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
|
||||
36
gui/Window.h
36
gui/Window.h
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
34
gui/color.cpp
Executable file
34
gui/color.cpp
Executable file
|
|
@ -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<float>(((hex >> 16) & 0xFF)) / 255.0f;
|
||||
float g = static_cast<float>(((hex >> 8) & 0xFF)) / 255.0f;
|
||||
float b = static_cast<float>((hex & 0xFF)) / 255.0f;
|
||||
auto vec = ImVec4(r, g, b, alpha);
|
||||
return vec;
|
||||
}
|
||||
137
gui/color.h
Executable file
137
gui/color.h
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
#ifndef POUND_COLORS_H
|
||||
#define POUND_COLORS_H
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
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
|
||||
264
gui/gui.cpp
Executable file
264
gui/gui.cpp
Executable file
|
|
@ -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 <imgui.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imgui_impl_sdl3.h>
|
||||
|
||||
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);
|
||||
}
|
||||
219
gui/gui.h
Executable file
219
gui/gui.h
Executable file
|
|
@ -0,0 +1,219 @@
|
|||
#ifndef POUND_GUI_H
|
||||
#define POUND_GUI_H
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <vector>
|
||||
#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 <stdio.h>
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
*
|
||||
* 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
|
||||
118
gui/panels.cpp
Executable file
118
gui/panels.cpp
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
#include "panels.h"
|
||||
#include <math.h>
|
||||
#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<std::chrono::milliseconds>(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;
|
||||
}
|
||||
113
gui/panels.h
Executable file
113
gui/panels.h
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
#ifndef POUND_PANELS_H
|
||||
#define POUND_PANELS_H
|
||||
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
|
||||
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<float_t> fps_history; // Historical FPS values over time
|
||||
* std::deque<float_t> 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<float_t> fps_history;
|
||||
std::deque<float_t> 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
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "CPUPanel.h"
|
||||
#include "../Colors.h"
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
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
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include "../Panel.h"
|
||||
#include <functional>
|
||||
|
||||
namespace Pound::GUI
|
||||
{
|
||||
|
||||
class CPUPanel : public Panel
|
||||
{
|
||||
public:
|
||||
CPUPanel();
|
||||
|
||||
void Render() override;
|
||||
void SetCPUTestCallback(std::function<void()> callback) { cpu_test_callback = callback; }
|
||||
void ShowTestResult(bool show = true) { show_test_result = show; }
|
||||
|
||||
private:
|
||||
std::function<void()> 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
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "ConsolePanel.h"
|
||||
#include "../Colors.h"
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include "../Panel.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
|
||||
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<LogEntry> 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
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "PerformancePanel.h"
|
||||
#include <algorithm>
|
||||
|
||||
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<std::chrono::milliseconds>(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
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
#pragma once
|
||||
|
||||
#include "../Panel.h"
|
||||
#include <deque>
|
||||
#include <chrono>
|
||||
|
||||
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<float> fps_history;
|
||||
std::deque<float> 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue