build: Refactor CMake build system

This new architecture decomposes the project into several distict static
libraries: common, host, kvm, and frontend.

By using static libraries, changes within one module will only require
that library to be re-linked, rather than recompiling and re-linking the
entire executable.

The third party library ImGui is now built as a static library target.

Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
Ronald Caesar 2025-09-14 13:28:09 -04:00
parent 8b483849f4
commit a3ed44003b
16 changed files with 133 additions and 302 deletions

View file

@ -1,5 +1,3 @@
# Copyright 2025 Xenon Emulator Project. All rights reserved.
set(BUILD_SHARED_LIBS OFF)
set(BUILD_TESTING OFF)
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON SYSTEM ON)
@ -19,3 +17,20 @@ if (NOT TARGET SDL3::SDL3)
set(SDL_PIPEWIRE OFF)
add_subdirectory(SDL3)
endif()
# ImGui
set(IMGUI_SRC
imgui/imgui.cpp
imgui/imgui_demo.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
imgui/backends/imgui_impl_sdl3.cpp
imgui/backends/imgui_impl_opengl3.cpp
)
add_library(imgui STATIC ${IMGUI_SRC})
target_link_libraries(imgui PRIVATE SDL3::SDL3)
target_include_directories(imgui PUBLIC
imgui
imgui/backends
)

View file

@ -1,103 +1,97 @@
# Copyright 2025 Xenon Emulator Project. All rights reserved.
cmake_minimum_required(VERSION 3.22)
#------------------------
# ---- Project Setup ----
#------------------------
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd_Party/imgui)
project(Pound)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# Enable asserts in release mode.
string( REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
# Enable asserts in release mode. Windows throws a metric fuck ton of compiler
# errors when asserts are disabled.
#if (UNIX)
# string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
#endif()
# Optimizations
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
if (WIN32)
add_compile_options($<$<CONFIG:Release>:/Oi>)
add_compile_options($<$<CONFIG:Release>:/Ot>)
endif()
project(Pound)
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
#-------------------------------
# ---- Dependency Discovery ----
#-------------------------------
find_package(OpenGL REQUIRED)
find_package(fmt 10.2.1 CONFIG)
find_package(SDL3 3.2.10 CONFIG)
add_subdirectory(3rd_Party)
#-----------------------------
# ---- Target Definitions ----
#-----------------------------
if(WIN32)
add_compile_options(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
endif()
add_executable(Pound
src/main.cpp
)
add_subdirectory(src/kvm)
add_subdirectory(3rd_Party)
add_subdirectory(src/common)
add_subdirectory(src/frontend)
add_subdirectory(src/host)
add_subdirectory(src/kvm)
add_subdirectory(src/targets/switch1/hardware)
# Detect Host System Endianness.
#--------------------------------
# ---- Target Configurations ----
#--------------------------------
include(TestBigEndian)
TEST_BIG_ENDIAN(WORDS_BIGENDIAN)
if(WORDS_BIGENDIAN)
message(STATUS "Host Endianness: Big Endian")
else()
message(STATUS "Host Endianness: Little Endian")
endif()
# Configure Preprocessor Definitions based on Endianness.
if(WORDS_BIGENDIAN)
target_compile_definitions(Pound PRIVATE HOST_IS_BIG_ENDIAN=1)
target_compile_definitions(Pound PRIVATE HOST_IS_LITTLE_ENDIAN=0)
else()
target_compile_definitions(Pound PRIVATE HOST_IS_BIG_ENDIAN=0)
target_compile_definitions(Pound PRIVATE HOST_IS_LITTLE_ENDIAN=1)
endif()
list(APPEND POUND_PROJECT_TARGETS common frontend host kvm)
foreach(TARGET ${POUND_PROJECT_TARGETS})
# Apply Endianness definitions to all our targets.
if(WORDS_BIGENDIAN)
target_compile_definitions(${TARGET} PRIVATE HOST_IS_BIG_ENDIAN=1 HOST_IS_LITTLE_ENDIAN=0)
else()
target_compile_definitions(${TARGET} PRIVATE HOST_IS_BIG_ENDIAN=0 HOST_IS_LITTLE_ENDIAN=1)
endif()
target_compile_options(${TARGET} PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:
-Wall
-Wpedantic
-Wshadow
-Wpointer-arith
-Wcast-qual
-Wcast-align
-Wconversion>
)
endforeach()
target_compile_options(Pound PRIVATE -Wall -Wpedantic
-Wshadow
-Wpointer-arith
-Wcast-qual
-Wcast-align
-Wconversion
)
# Link libraries
target_link_libraries(Pound PRIVATE fmt::fmt SDL3::SDL3)
# Optimizations
set_property(TARGET Pound PROPERTY CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
if (WIN32)
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
# Disables Warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
target_compile_options(Pound PRIVATE $<$<CONFIG:Release>:/Oi>)
target_compile_options(Pound PRIVATE $<$<CONFIG:Release>:/Ot>)
endif()
target_link_libraries(Pound PRIVATE
common
frontend
host
kvm
# ImGui
set(IMGUI_SRC
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_demo.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
OpenGL::GL
fmt::fmt
SDL3::SDL3
imgui
)
target_sources(Pound PRIVATE ${IMGUI_SRC})
target_include_directories(Pound PRIVATE
${IMGUI_DIR}
${IMGUI_DIR}/backends
)
find_package(OpenGL REQUIRED)
target_link_libraries(Pound PRIVATE OpenGL::GL)
# add ./gui directory

View file

@ -1,59 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <stdio.h>
#include "Arch.h"
#include "Assert.h"
#include "Logging/Backend.h"
#ifdef _MSC_VER
#define Crash() __debugbreak()
#else
#if defined(ARCH_X86_64)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_X86)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_AARCH64)
#define Crash() __asm__ __volatile__("brk 0")
#elif defined(ARCH_PPC)
#include <signal.h>
#ifdef SIGTRAP
#define Crash() raise(SIGTRAP)
#else
#define Crash() raise(SIGABRT)
#endif
#else
#define Crash() __builtin_trap()
#endif
#endif // _MSVC_VER
void throw_fail_impl()
{
::fflush(stdout);
Crash();
}
void assert_fail_impl()
{
printf("Assertion Failed!\n");
throw_fail_impl();
}
[[noreturn]] void unreachable_impl()
{
::fflush(stdout);
Crash();
throw std::runtime_error("Unreachable code");
}
void assert_fail_debug_msg(const std::string& msg)
{
printf("Assertion Failed! %s\n", msg.c_str());
assert_fail_impl();
}
void throw_fail_debug_msg(const std::string& msg)
{
printf("Assertion Failed! %s\n", msg.c_str());
throw_fail_impl();
}

View file

@ -1,121 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include "Logging/Log.h"
// Sometimes we want to try to continue even after hitting an assert.
// However touching this file yields a global recompilation as this header is included almost
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
void assert_fail_impl();
void throw_fail_impl();
[[noreturn]] void unreachable_impl();
void assert_fail_debug_msg(const std::string& msg);
void throw_fail_debug_msg(const std::string& msg);
#ifdef _MSC_VER
#define POUND_NO_INLINE __declspec(noinline)
#else
#define POUND_NO_INLINE __attribute__((noinline))
#endif
#define THROW(_a_) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
throw_fail_impl(); \
} \
})
#define ASSERT(_a_) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
assert_fail_impl(); \
} \
})
#define THROW_MSG(_a_, ...) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
throw_fail_impl(); \
} \
})
#define ASSERT_MSG(_a_, ...) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
assert_fail_impl(); \
} \
})
#define UNREACHABLE() \
do \
{ \
LOG_CRITICAL(Debug, "Unreachable code!"); \
unreachable_impl(); \
} while (0)
#define UNREACHABLE_MSG(...) \
do \
{ \
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
unreachable_impl(); \
} while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
#define DEBUG_ASSERT(_a_) \
do \
{ \
} while (0)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
do \
{ \
} while (0)
#endif
#define UNIMPLEMENTED() THROW_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) THROW_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do \
{ \
ASSERT(_a_); \
if (!(_a_)) [[unlikely]] \
{ \
_b_ \
} \
} while (0)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do \
{ \
ASSERT_MSG(_a_, __VA_ARGS__); \
if (!(_a_)) [[unlikely]] \
{ \
_b_ \
} \
} while (0)

View file

@ -1,7 +1,6 @@
#Copyright 2025 Pound Emulator Project.All rights reserved.
add_library(common STATIC)
set(COMMON_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Assert.cpp
target_sources(common PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Config.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IoFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PathUtil.cpp
@ -9,8 +8,12 @@ set(COMMON_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Thread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Logging/Backend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Logging/Filter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Logging/TextFormatter.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/Logging/TextFormatter.cpp
)
target_sources(Pound PRIVATE ${COMMON_SOURCES})
target_link_libraries(common PUBLIC fmt::fmt)
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..)
target_include_directories(common PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -4,7 +4,6 @@
#include "IoFile.h"
#include "Assert.h"
#include "Error.h"
#include "Logging/Log.h"
#include "PathUtil.h"
@ -287,7 +286,6 @@ uptr IOFile::GetFileMapping()
mapping = hfile;
fileMapping = std::bit_cast<uptr>(mapping);
ASSERT_MSG(fileMapping, "{}", Base::GetLastErrorMsg());
return fileMapping;
#else
fileMapping = fileno(file);

View file

@ -1,8 +1,7 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "common/Assert.h"
#include "Filter.h"
#include <algorithm>
namespace Base {
namespace Log {
@ -33,13 +32,13 @@ template <typename Iterator>
bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
const auto levelSeparator = std::find(begin, end, ':');
if (levelSeparator == end) {
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string_view(begin, end));
// LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string_view(begin, end));
return false;
}
const Level level = GetLevelByName(levelSeparator + 1, end);
if (level == Level::Count) {
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end));
//LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end));
return false;
}
@ -50,7 +49,7 @@ bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
const Class logClass = GetClassByName(begin, levelSeparator);
if (logClass == Class::Count) {
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
//LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
return false;
}
@ -88,7 +87,6 @@ const char* GetLogClassName(Class logClass) {
default:
break;
}
UNREACHABLE();
}
const char* GetLevelName(Level logLevel) {
@ -108,7 +106,6 @@ const char* GetLevelName(Level logLevel) {
break;
}
#undef LVL
UNREACHABLE();
}
Filter::Filter(Level defaultLevel) {

View file

@ -1,12 +1,12 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "common/Assert.h"
#include "common/Config.h"
#include "TextFormatter.h"
#include "Filter.h"
#include "LogEntry.h"
#include <algorithm>
namespace Base {
namespace Log {
@ -57,7 +57,7 @@ void PrintColoredMessage(const Entry &entry) {
color = ESC "[0;92m";
break;
case Level::Count:
UNREACHABLE();
break;
}
PrintMessage(color, entry);

View file

@ -1,11 +1,14 @@
#Copyright 2025 Pound Emulator Project.All rights reserved.
add_library(frontend STATIC)
#GUI sources
set(GUI_SOURCES ${CMAKE_CURRENT_SOURCE_DIR} / gui.cpp ${CMAKE_CURRENT_SOURCE_DIR} /
color.cpp ${CMAKE_CURRENT_SOURCE_DIR} / panels.cpp)
target_sources(frontend PRIVATE
${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})
target_link_libraries(frontend PRIVATE common imgui SDL3::SDL3)
#Include directories for GUI
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..)
target_include_directories(frontend PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -1,7 +1,7 @@
#ifndef POUND_COLORS_H
#define POUND_COLORS_H
#include <imgui.h>
#include "imgui.h"
#include <algorithm>
#include <cstdint>

View file

@ -1,6 +1,6 @@
#include "gui.h"
#include "color.h"
#include "common/Assert.h"
#include <assert.h>
#include "common/Logging/Log.h"
#include "imgui_impl_opengl3_loader.h"
@ -16,8 +16,8 @@ static void apply_theme();
bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height)
{
ASSERT(nullptr != window);
ASSERT(nullptr != title);
assert(nullptr != window);
assert(nullptr != title);
bool ret = ::SDL_Init(SDL_INIT_VIDEO);
if (false == ret)
@ -119,8 +119,8 @@ void gui::window_destroy(gui::window_t* window)
bool gui::init_imgui(gui::window_t* main_window)
{
ASSERT(nullptr != main_window->data);
ASSERT(nullptr != main_window->gl_context);
assert(nullptr != main_window->data);
assert(nullptr != main_window->gl_context);
// Initialize ImGui
IMGUI_CHECKVERSION();

View file

@ -2,14 +2,14 @@
#include <imgui.h>
#include <math.h>
#include "kvm/kvm.h"
#include "common/Assert.h"
#include <assert.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)
{
ASSERT(nullptr != panel);
ASSERT(nullptr != data);
ASSERT(nullptr != last_render);
assert(nullptr != panel);
assert(nullptr != data);
assert(nullptr != last_render);
bool is_visible = true;
(void)::ImGui::Begin(PANEL_NAME_PERFORMANCE, &is_visible);
@ -82,7 +82,7 @@ int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* pan
int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup)
{
ASSERT(nullptr != show_cpu_result_popup);
assert(nullptr != show_cpu_result_popup);
bool is_visible = true;
(void)::ImGui::Begin(PANEL_NAME_CPU, &is_visible, ImGuiWindowFlags_NoCollapse);

View file

@ -1,13 +1,10 @@
# Copyright 2025 Pound Emulator Project. All rights reserved.
add_library(host STATIC)
set(HOST_SOURCES
target_sources(host PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/memory/arena.cpp
)
target_sources(Pound PRIVATE
${HOST_SOURCES}
)
target_include_directories(Pound PRIVATE
target_include_directories(host PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -1,11 +1,15 @@
#Copyright 2025 Pound Emulator Project.All rights reserved.
add_library(kvm STATIC)
set(KVM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/mmu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mmio.cpp
${CMAKE_CURRENT_SOURCE_DIR}/kvm.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guest.cpp
target_sources(kvm PRIVATE
mmu.cpp
mmio.cpp
kvm.cpp
guest.cpp
)
target_sources(Pound PRIVATE ${KVM_SOURCES})
target_link_libraries(kvm PRIVATE common host)
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..)
target_include_directories(kvm PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -1,5 +1,5 @@
#include "guest.h"
#include <assert.h>
#include <cassert>
namespace pound::kvm::memory
{

View file

@ -1,7 +1,7 @@
#ifndef POUND_KVM_GUEST_H
#define POUND_KVM_GUEST_H
#include <assert.h>
#include <cassert>
#include <stdint.h>
#include <string.h>