mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-12 01:36:57 +00:00
Rewrite the entirety of Pound
feat!: rewrote program in a data oriented style.
This commit is contained in:
commit
33091fe066
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 {
|
||||
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();
|
||||
|
|
|
|||
157
core/main.cpp
157
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>();
|
||||
gui::window_t window = {.data = nullptr, .gl_context = nullptr};
|
||||
(void)gui::window_init(&window, "Pound Emulator", Config::windowWidth(), Config::windowHeight());
|
||||
|
||||
// Initialize GUI
|
||||
if (!gui_manager->Initialize("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>();
|
||||
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_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.");
|
||||
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,35 +2,14 @@
|
|||
|
||||
# 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}
|
||||
)
|
||||
|
||||
# Include directories for GUI
|
||||
|
|
|
|||
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