diff --git a/AGENTS.md b/AGENTS.md index ee4268d..f3c4dc8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -89,8 +89,15 @@ cuber/ CMakeLists.txt # Build configuration cuber.cpp # Entry point cbt/ # Project namespace - window.hpp # Window RAII wrapper - window.cpp # Window implementation + opengl/ # OpenGL abstraction layer + context.hpp # OpenGL context RAII wrapper + context.cpp # Context implementation + buffer.hpp # Buffer resource (VBO, EBO, UBO, SSBO) + buffer.cpp # Buffer implementation + texture.hpp # Texture resource + texture.cpp # Texture implementation + descriptor.hpp # Descriptor set (Vulkan-style binding) + descriptor.cpp # Descriptor implementation deps/ # Custom Find*.cmake scripts Findfmt.cmake # fmt library ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 66a3d97..5ed16ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,12 @@ find_package(glad REQUIRED) find_package(asio REQUIRED) # Target setup -add_executable(cuber "cuber.cpp" "cbt/window.cpp") +add_executable(cuber "cuber.cpp" + "cbt/opengl/context.cpp" + "cbt/opengl/buffer.cpp" + "cbt/opengl/texture.cpp" + "cbt/opengl/descriptor.cpp" +) target_include_directories(cuber PRIVATE ".") target_compile_features(cuber PRIVATE cxx_std_23) target_link_libraries(cuber PRIVATE fmt::fmt glfw::glfw glad::glad asio::asio) diff --git a/cbt/opengl/buffer.cpp b/cbt/opengl/buffer.cpp new file mode 100644 index 0000000..38d527c --- /dev/null +++ b/cbt/opengl/buffer.cpp @@ -0,0 +1,72 @@ +#include "cbt/opengl/buffer.hpp" + +#include "glad/glad.h" + +namespace cbt::opengl { + +buffer::buffer() { + glGenBuffers(1, &m_id); +} + +buffer::buffer(buffer_type type) + : m_type(type) { + glGenBuffers(1, &m_id); +} + +buffer::~buffer() { + if (m_id) { + glDeleteBuffers(1, &m_id); + } +} + +buffer::buffer(buffer&& other) noexcept + : m_id(other.m_id) + , m_type(other.m_type) { + other.m_id = 0; +} + +buffer& buffer::operator=(buffer&& other) noexcept { + if (this != &other) { + if (m_id) { + glDeleteBuffers(1, &m_id); + } + m_id = other.m_id; + m_type = other.m_type; + other.m_id = 0; + } + return *this; +} + +auto buffer::bind() const -> void { + glBindBuffer(static_cast(m_type), m_id); +} + +auto buffer::unbind() const -> void { + glBindBuffer(static_cast(m_type), 0); +} + +auto buffer::upload(const void* data, size_t size) -> void { + bind(); + glBufferData(static_cast(m_type), size, data, GL_STATIC_DRAW); + unbind(); +} + +auto buffer::upload(std::vector data) -> void { + upload(data.data(), data.size()); +} + +auto buffer::upload_dynamic(const void* data, size_t size) -> void { + bind(); + glBufferData(static_cast(m_type), size, data, GL_DYNAMIC_DRAW); + unbind(); +} + +auto buffer::id() const -> GLuint { + return m_id; +} + +auto buffer::valid() const -> bool { + return m_id != 0; +} + +} diff --git a/cbt/opengl/buffer.hpp b/cbt/opengl/buffer.hpp new file mode 100644 index 0000000..e959c9c --- /dev/null +++ b/cbt/opengl/buffer.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "glad/glad.h" + +namespace cbt::opengl { + +enum class buffer_type { + vertex = GL_ARRAY_BUFFER, + index = GL_ELEMENT_ARRAY_BUFFER, + uniform = GL_UNIFORM_BUFFER, + storage = GL_SHADER_STORAGE_BUFFER, +}; + +class buffer { + GLuint m_id = 0; + buffer_type m_type = buffer_type::vertex; + +public: + buffer(); + explicit buffer(buffer_type type); + ~buffer(); + + buffer(const buffer&) = delete; + buffer& operator=(const buffer&) = delete; + + buffer(buffer&& other) noexcept; + buffer& operator=(buffer&& other) noexcept; + + auto bind() const -> void; + auto unbind() const -> void; + auto upload(const void* data, size_t size) -> void; + auto upload(std::vector data) -> void; + auto upload_dynamic(const void* data, size_t size) -> void; + auto id() const -> GLuint; + auto valid() const -> bool; +}; + +} diff --git a/cbt/window.cpp b/cbt/opengl/context.cpp similarity index 67% rename from cbt/window.cpp rename to cbt/opengl/context.cpp index c65f5e7..a550d98 100644 --- a/cbt/window.cpp +++ b/cbt/opengl/context.cpp @@ -1,16 +1,16 @@ #define GLFW_INCLUDE_NONE #include "GLFW/glfw3.h" -#include "window.hpp" +#include "cbt/opengl/context.hpp" #include #include "glad/glad.h" #include "fmt/std.h" -namespace cbt { +namespace cbt::opengl { -auto window::init() -> bool { +auto context::init() -> bool { if (!glfwInit()) { fmt::print("Failed to initialize GLFW\n"); return false; @@ -18,28 +18,29 @@ auto window::init() -> bool { return true; } -auto window::terminate() -> void { +auto context::terminate() -> void { glfwTerminate(); } -auto window::setup_opengl() -> bool { +auto context::setup_gl() -> bool { if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { fmt::print("Failed to initialize GLAD\n"); return false; } - info_opengl(); + print_info(); return true; } -auto window::info_opengl() -> void { +auto context::print_info() -> void { fmt::print("OpenGL Info:\n"); fmt::print(" vendor | {}\n", std::string_view(reinterpret_cast(glGetString(GL_VENDOR)))); fmt::print(" renderer| {}\n", std::string_view(reinterpret_cast(glGetString(GL_RENDERER)))); fmt::print(" version | {}\n", std::string_view(reinterpret_cast(glGetString(GL_VERSION)))); fmt::print(" glsl | {}\n", std::string_view(reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)))); + fmt::print("\n"); } -window::window(std::string title, int width, int height) { +context::context(std::string title, int width, int height) { if (!init()) { return; } @@ -56,37 +57,41 @@ window::window(std::string title, int width, int height) { return; } - glfwMakeContextCurrent(static_cast(m_window)); + glfwMakeContextCurrent(m_window); - if (!setup_opengl()) { + if (!setup_gl()) { terminate(); return; } } -window::~window() { +context::~context() { if (m_window) { - glfwDestroyWindow(static_cast(m_window)); + glfwDestroyWindow(m_window); } if (m_initialized) { terminate(); } } -auto window::should_close() const -> bool { - return m_window && glfwWindowShouldClose(static_cast(m_window)); +auto context::should_close() const -> bool { + return m_window && glfwWindowShouldClose(m_window); } -auto window::valid() const -> bool { +auto context::valid() const -> bool { return m_window != nullptr; } -auto window::swap_buffers() -> void { - glfwSwapBuffers(static_cast(m_window)); +auto context::swap_buffers() -> void { + glfwSwapBuffers(m_window); } -auto window::poll_events() -> void { +auto context::poll_events() -> void { glfwPollEvents(); } +auto context::raw() const -> GLFWwindow* { + return m_window; +} + } diff --git a/cbt/opengl/context.hpp b/cbt/opengl/context.hpp new file mode 100644 index 0000000..69b6aab --- /dev/null +++ b/cbt/opengl/context.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#define GLFW_INCLUDE_NONE +#include "GLFW/glfw3.h" + +namespace cbt::opengl { + +class context { + GLFWwindow* m_window = nullptr; + bool m_initialized = false; + + static auto init() -> bool; + static auto terminate() -> void; + static auto setup_gl() -> bool; + static auto print_info() -> void; + +public: + explicit context(std::string title, int width, int height); + ~context(); + + auto should_close() const -> bool; + auto valid() const -> bool; + auto swap_buffers() -> void; + auto poll_events() -> void; + auto raw() const -> GLFWwindow*; +}; + +} diff --git a/cbt/opengl/descriptor.cpp b/cbt/opengl/descriptor.cpp new file mode 100644 index 0000000..2b56b47 --- /dev/null +++ b/cbt/opengl/descriptor.cpp @@ -0,0 +1,32 @@ +#include "cbt/opengl/descriptor.hpp" + +#include "glad/glad.h" + +namespace cbt::opengl { + +descriptor_set::descriptor_set() {} + +auto descriptor_set::add_texture(texture tex, GLuint unit) -> void { + m_bindings.push_back({unit, std::move(tex), std::nullopt}); +} + +auto descriptor_set::add_buffer(buffer buf, GLuint unit) -> void { + m_bindings.push_back({unit, std::nullopt, std::move(buf)}); +} + +auto descriptor_set::bind_all() -> void { + for (auto& binding : m_bindings) { + if (binding.tex) { + binding.tex->bind(binding.texture_unit); + } + if (binding.buf) { + binding.buf->bind(); + } + } +} + +auto descriptor_set::count() const -> size_t { + return m_bindings.size(); +} + +} diff --git a/cbt/opengl/descriptor.hpp b/cbt/opengl/descriptor.hpp new file mode 100644 index 0000000..b380154 --- /dev/null +++ b/cbt/opengl/descriptor.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "cbt/opengl/buffer.hpp" +#include "cbt/opengl/texture.hpp" + +#include +#include +#include + +namespace cbt::opengl { + +struct descriptor_binding { + GLuint texture_unit = 0; + std::optional tex; + std::optional buf; +}; + +class descriptor_set { + std::vector m_bindings; + +public: + descriptor_set(); + auto add_texture(texture tex, GLuint unit = 0) -> void; + auto add_buffer(buffer buf, GLuint unit = 0) -> void; + auto bind_all() -> void; + auto count() const -> size_t; +}; + +} diff --git a/cbt/opengl/texture.cpp b/cbt/opengl/texture.cpp new file mode 100644 index 0000000..9e9b815 --- /dev/null +++ b/cbt/opengl/texture.cpp @@ -0,0 +1,85 @@ +#include "cbt/opengl/texture.hpp" + +#include "glad/glad.h" + +namespace cbt::opengl { + +texture::texture() { + glGenTextures(1, &m_id); +} + +texture::texture(texture_target target) + : m_target(target) { + glGenTextures(1, &m_id); +} + +texture::~texture() { + if (m_id) { + glDeleteTextures(1, &m_id); + } +} + +texture::texture(texture&& other) noexcept + : m_id(other.m_id) + , m_target(other.m_target) { + other.m_id = 0; +} + +texture& texture::operator=(texture&& other) noexcept { + if (this != &other) { + if (m_id) { + glDeleteTextures(1, &m_id); + } + m_id = other.m_id; + m_target = other.m_target; + other.m_id = 0; + } + return *this; +} + +auto texture::bind(GLuint unit) -> void { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(static_cast(m_target), m_id); +} + +auto texture::unbind() -> void { + glBindTexture(static_cast(m_target), 0); +} + +auto texture::upload(const void* data, int width, int height, texture_format format, texture_type type) -> void { + bind(); + glTexImage2D(static_cast(m_target), 0, + static_cast(format), width, height, 0, + static_cast(format), static_cast(type), data); + unbind(); +} + +auto texture::set_filter(GLenum min_filter, GLenum mag_filter) -> void { + bind(); + glTexParameteri(static_cast(m_target), GL_TEXTURE_MIN_FILTER, min_filter); + glTexParameteri(static_cast(m_target), GL_TEXTURE_MAG_FILTER, mag_filter); + unbind(); +} + +auto texture::set_wrap(GLenum wrap_s, GLenum wrap_t) -> void { + bind(); + glTexParameteri(static_cast(m_target), GL_TEXTURE_WRAP_S, wrap_s); + glTexParameteri(static_cast(m_target), GL_TEXTURE_WRAP_T, wrap_t); + unbind(); +} + +auto texture::generate_mipmaps() -> void { + bind(); + glGenerateMipmap(static_cast(m_target)); + unbind(); +} + +auto texture::id() const -> GLuint { + return m_id; +} + +auto texture::valid() const -> bool { + return m_id != 0; +} + +} diff --git a/cbt/opengl/texture.hpp b/cbt/opengl/texture.hpp new file mode 100644 index 0000000..d77f856 --- /dev/null +++ b/cbt/opengl/texture.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "glad/glad.h" + +namespace cbt::opengl { + +enum class texture_target { + _1d = GL_TEXTURE_1D, + _2d = GL_TEXTURE_2D, + _3d = GL_TEXTURE_3D, + cube = GL_TEXTURE_CUBE_MAP, +}; + +enum class texture_format { + rgb = GL_RGB, + rgba = GL_RGBA, + rg = GL_RG, + red = GL_RED, + depth = GL_DEPTH_COMPONENT, +}; + +enum class texture_type { + ubyte = GL_UNSIGNED_BYTE, + ushort = GL_UNSIGNED_SHORT, + uint = GL_UNSIGNED_INT, + float_ = GL_FLOAT, +}; + +class texture { + GLuint m_id = 0; + texture_target m_target = texture_target::_2d; + +public: + texture(); + explicit texture(texture_target target); + ~texture(); + + texture(const texture&) = delete; + texture& operator=(const texture&) = delete; + + texture(texture&& other) noexcept; + texture& operator=(texture&& other) noexcept; + + auto bind(GLuint unit = 0) -> void; + auto unbind() -> void; + auto upload(const void* data, int width, int height, texture_format format, texture_type type) -> void; + auto set_filter(GLenum min_filter, GLenum mag_filter) -> void; + auto set_wrap(GLenum wrap_s, GLenum wrap_t) -> void; + auto generate_mipmaps() -> void; + auto id() const -> GLuint; + auto valid() const -> bool; +}; + +} diff --git a/cbt/window.hpp b/cbt/window.hpp deleted file mode 100644 index 3a5862f..0000000 --- a/cbt/window.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -namespace cbt { - -class window { - void* m_window = nullptr; - bool m_initialized = false; - - static auto init() -> bool; - static auto terminate() -> void; - static auto setup_opengl() -> bool; - static auto info_opengl() -> void; - -public: - explicit window(std::string title, int width, int height); - ~window(); - - auto should_close() const -> bool; - auto valid() const -> bool; - auto swap_buffers() -> void; - auto poll_events() -> void; - auto raw() const -> void* { return m_window; } -}; - -} diff --git a/cuber.cpp b/cuber.cpp index 37c2f15..5313ab2 100644 --- a/cuber.cpp +++ b/cuber.cpp @@ -1,7 +1,7 @@ #define GLFW_INCLUDE_NONE #include "GLFW/glfw3.h" -#include "cbt/window.hpp" +#include "cbt/opengl/context.hpp" #include @@ -10,9 +10,9 @@ #include "glad/glad.h" auto main(int, char const*[]) -> int { - auto w = cbt::window("cuber", 1280, 720); + auto ctx = cbt::opengl::context("cuber", 1280, 720); - if (!w.valid()) { + if (!ctx.valid()) { return 1; } @@ -29,18 +29,18 @@ auto main(int, char const*[]) -> int { while (io.poll()) {} }; - while (!w.should_close()) { + while (!ctx.should_close()) { process_signals(); - if (quit || glfwGetKey(static_cast(w.raw()), GLFW_KEY_Q) == GLFW_PRESS) { + if (quit || glfwGetKey(ctx.raw(), GLFW_KEY_Q) == GLFW_PRESS) { break; } glClearColor(0.6f, 0.8f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - w.swap_buffers(); - w.poll_events(); + ctx.swap_buffers(); + ctx.poll_events(); } return 0; diff --git a/deps/FindGTest.cmake b/deps/FindGTest.cmake new file mode 100644 index 0000000..7a02c91 --- /dev/null +++ b/deps/FindGTest.cmake @@ -0,0 +1,84 @@ +# ============================================================================== +# Find GTest +# ============================================================================== +# This module fetches the Google Test framework. +# +# Targets provided: +# GTest::gtest - The gtest library target +# GTest::gtest_main - The gtest_main library target +# +# Variables set: +# GTEST_FOUND - TRUE if GTest is available +# GTEST_LIBRARIES - The gtest library targets (gtest gtest_main) +# GTEST_INCLUDE_DIRS - Include directories for GTest +# GTEST_VERSION - Version of GTest +# ============================================================================== + +if(DEFINED _FINDGTEST_INCLUDED) + return() +endif() +set(_FINDGTEST_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 1.17.0 +if (DEFINED gtest_FIND_VERSION AND NOT gtest_FIND_VERSION STREQUAL "") + set(GTEST_VERSION "${gtest_FIND_VERSION}") +else() + set(GTEST_VERSION "1.17.0") +endif() + +message(STATUS "Fetching GoogleTest ${GTEST_VERSION}") +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(GTEST_FETCH_METHOD "GIT") +else() + set(GTEST_FETCH_METHOD "ZIP") +endif() + +if (GTEST_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v${GTEST_VERSION} + ) +else() + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v${GTEST_VERSION}.zip + ) +endif() + +FetchContent_MakeAvailable(googletest) + +message(STATUS "{GoogleTest} version: ${GTEST_VERSION}") + +# Provide in-tree aliases to the raw library targets +if(TARGET gtest AND NOT TARGET GTest::gtest) + add_library(GTest::gtest ALIAS gtest) +endif() +if(TARGET gtest_main AND NOT TARGET GTest::gtest_main) + add_library(GTest::gtest_main ALIAS gtest_main) +endif() + +set(GTEST_INCLUDE_DIRS ${googletest_SOURCE_DIR}/googletest/include) +set(GTEST_LIBRARIES gtest gtest_main) +set(GTEST_FOUND TRUE) + +# Mark GTest includes as SYSTEM to suppress warnings from its headers +if (GTEST_INCLUDE_DIRS AND TARGET gtest) + set_target_properties(gtest PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIRS}" + ) +endif() +if (GTEST_INCLUDE_DIRS AND TARGET gtest_main) + set_target_properties(gtest_main PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIRS}" + ) +endif() + +mark_as_advanced( + GTest_INCLUDE_DIRS + GTest_LIBRARY + GTest_MAIN_LIBRARY +) diff --git a/deps/FindVulkan.cmake b/deps/FindVulkan.cmake new file mode 100644 index 0000000..b6f05b0 --- /dev/null +++ b/deps/FindVulkan.cmake @@ -0,0 +1,104 @@ +# ============================================================================== +# Find Vulkan +# ============================================================================== +# This module finds a locally installed Vulkan SDK. +# +# Targets provided: +# vulkan::vulkan - The Vulkan library target +# +# Variables set: +# Vulkan_FOUND - TRUE if Vulkan is available +# Vulkan_LIBRARIES - The Vulkan library target (Vulkan::Vulkan) +# Vulkan_INCLUDE_DIR - Include directory for Vulkan headers +# Vulkan_VERSION - Version of the found Vulkan SDK +# ============================================================================== + +if (DEFINED _FINDVULKAN_INCLUDED) + return() +endif() +set(_FINDVULKAN_INCLUDED TRUE) + +# detect_vulkan_sdk( ) +# +# Detects the Vulkan SDK root and sets the include path, lib path, and version. +# Search order: +# 1. VULKAN_SDK environment variable (all platforms) +# 2. C:/VulkanSDK/ (Windows fallback) +# 3. /usr (Linux system packages) +# +# Parameters: +# - OUT_INC Name of the variable to set with the SDK include path +# - OUT_LIB Name of the variable to set with the SDK lib path +# - OUT_VER Name of the variable to set with the SDK version string +function(detect_vulkan_sdk OUT_INC OUT_LIB OUT_VER) + # 1. VULKAN_SDK env var (set by the LunarG installer on all platforms) + if (DEFINED ENV{VULKAN_SDK}) + set(_ROOT "$ENV{VULKAN_SDK}") + # The env var already points inside the versioned dir on all platforms + # e.g. C:/VulkanSDK/1.4.335.0 or ~/VulkanSDK/1.4.xxx/x86_64 + set(${OUT_VER} "env" PARENT_SCOPE) + set(${OUT_INC} "${_ROOT}/Include" PARENT_SCOPE) # Windows + set(${OUT_LIB} "${_ROOT}/Lib" PARENT_SCOPE) # Windows + # On Linux/macOS the SDK uses lowercase paths + if (NOT WIN32) + set(${OUT_INC} "${_ROOT}/include" PARENT_SCOPE) + set(${OUT_LIB} "${_ROOT}/lib" PARENT_SCOPE) + endif() + return() + endif() + + # 2. Windows: scan C:/VulkanSDK for latest version directory + if (WIN32) + set(_ROOT "C:/VulkanSDK") + file(GLOB _VERSIONS RELATIVE "${_ROOT}" "${_ROOT}/*") + list(SORT _VERSIONS ORDER DESCENDING) + list(GET _VERSIONS 0 _VERSION) + set(${OUT_VER} "${_VERSION}" PARENT_SCOPE) + set(${OUT_INC} "${_ROOT}/${_VERSION}/Include" PARENT_SCOPE) + set(${OUT_LIB} "${_ROOT}/${_VERSION}/Lib" PARENT_SCOPE) + return() + endif() + + # 3. Linux/macOS: fall back to system paths + set(${OUT_VER} "system" PARENT_SCOPE) + set(${OUT_INC} "/usr/include" PARENT_SCOPE) + set(${OUT_LIB} "/usr/lib" PARENT_SCOPE) +endfunction() + +detect_vulkan_sdk(_Vulkan_INC _Vulkan_LIB Vulkan_VERSION) +message(STATUS "Vulkan SDK: ${Vulkan_VERSION}") + +# Library name differs by platform +if (WIN32) + set(_Vulkan_LIB_NAME "vulkan-1") +else() + set(_Vulkan_LIB_NAME "vulkan") +endif() + +find_library(_Vulkan_LIBRARY ${_Vulkan_LIB_NAME} HINTS "${_Vulkan_LIB}") + +if (NOT _Vulkan_LIBRARY) + if (Vulkan_FIND_REQUIRED) + message(FATAL_ERROR "Could not find ${_Vulkan_LIB_NAME} in ${_Vulkan_LIB}") + else() + message(STATUS " [ ] Vulkan (${_Vulkan_LIB_NAME})") + return() + endif() +endif() + +message(STATUS " [x] Vulkan (${_Vulkan_LIBRARY})") + +if (NOT TARGET vulkan::vulkan) + add_library(vulkan::vulkan UNKNOWN IMPORTED) + set_target_properties(vulkan::vulkan PROPERTIES + IMPORTED_LOCATION "${_Vulkan_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${_Vulkan_INC}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_Vulkan_INC}" + ) +endif() + +set(Vulkan_FOUND TRUE) +set(Vulkan_LIBRARIES Vulkan::Vulkan) +set(Vulkan_INCLUDE_DIR "${_Vulkan_INC}") + +unset(_Vulkan_LIBRARY CACHE) diff --git a/deps/Findctre.cmake b/deps/Findctre.cmake new file mode 100644 index 0000000..aa70359 --- /dev/null +++ b/deps/Findctre.cmake @@ -0,0 +1,79 @@ +# ============================================================================== +# Find ctre +# ============================================================================== +# This module fetches the CTRE (Compile Time Regular Expressions) library. +# +# Targets provided: +# ctre::ctre - The ctre library target +# +# Variables set: +# ctre_FOUND - TRUE if ctre is available +# ctre_LIBRARIES - The ctre library target (ctre::ctre) +# ctre_INCLUDE_DIR - Include directories for ctre +# ctre_VERSION - Version of ctre (if available) +# ============================================================================== + +if (DEFINED _FINDCTRE_INCLUDED) + return() +endif() +set(_FINDCTRE_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 3.10.0 +if (DEFINED ctre_FIND_VERSION AND NOT ctre_FIND_VERSION STREQUAL "") + set(CTRE_VERSION "${ctre_FIND_VERSION}") +else() + set(CTRE_VERSION "3.10.0") +endif() + +message(STATUS "Fetching ctre ${CTRE_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(CTRE_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +if (CTRE_FETCH_METHOD STREQUAL "GIT") + # Turn off BUILD_TESTING globally to prevent CTest from being included in CTRE + set(BUILD_TESTING OFF CACHE BOOL "Disable testing globally" FORCE) + # Set the CTRE_BUILD_TESTS option before including the CTRE library + set(CTRE_BUILD_TESTS OFF CACHE BOOL "Build ctre Tests" FORCE) + set(CTRE_BUILD_PACKAGE OFF CACHE BOOL "Build ctre Package" FORCE) + + FetchContent_Declare( + ctre + GIT_REPOSITORY https://github.com/hanickadot/compile-time-regular-expressions.git + GIT_TAG v${CTRE_VERSION} + ) +endif() + +FetchContent_MakeAvailable(ctre) + +# Turn BUILD_TESTING back on after including CTRE +set(BUILD_TESTING ON CACHE BOOL "Enable testing globally" FORCE) + +if (NOT TARGET ctre::ctre) + if (TARGET ctre) + add_library(ctre::ctre ALIAS ctre) + else() + message(FATAL_ERROR "Could not fetch ctre; no target ctre or ctre::ctre available") + endif() +endif() + +set(ctre_FOUND TRUE) +set(ctre_LIBRARIES ctre::ctre) +set(ctre_VERSION "${CTRE_VERSION}") +get_target_property(_ctre_inc ctre::ctre INTERFACE_INCLUDE_DIRECTORIES) +set(ctre_INCLUDE_DIR "${_ctre_inc}") + +# Mark ctre includes as SYSTEM to suppress warnings from its headers +if (_ctre_inc AND TARGET ctre) + set_target_properties(ctre PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_ctre_inc}" + ) +endif() + +set(CTRE_LICENSE_FILE "${ctre_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to CTRE license file") diff --git a/deps/Findearcut.cmake b/deps/Findearcut.cmake new file mode 100644 index 0000000..9539860 --- /dev/null +++ b/deps/Findearcut.cmake @@ -0,0 +1,57 @@ +# ============================================================================== +# Find earcut +# ============================================================================== +# This module fetches the earcut.hpp polygon triangulation library. +# +# Targets provided: +# earcut::earcut - The earcut library target +# +# Variables set: +# earcut_FOUND - TRUE if earcut is available +# earcut_LIBRARIES - The earcut library target (earcut::earcut) +# earcut_INCLUDE_DIR - Include directories for earcut +# earcut_VERSION - Version of earcut (if available) +# ============================================================================== + +if (DEFINED _FINDEARCUT_INCLUDED) + return() +endif() +set(_FINDEARCUT_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to v2.2.4 +if (DEFINED earcut_FIND_VERSION AND NOT earcut_FIND_VERSION STREQUAL "") + set(EARCUT_VERSION "${earcut_FIND_VERSION}") +else() + set(EARCUT_VERSION "2.2.4") +endif() + +message(STATUS "Fetching earcut ${EARCUT_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (NOT GIT_EXECUTABLE) + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +FetchContent_Declare( + earcut + GIT_REPOSITORY https://github.com/mapbox/earcut.hpp.git + GIT_TAG v${EARCUT_VERSION} + SOURCE_SUBDIR _unused # no CMakeLists.txt here; skips add_subdirectory +) + +# SOURCE_SUBDIR points to a non-existent directory so FetchContent_MakeAvailable +# skips add_subdirectory; we only need the headers under include/. +FetchContent_MakeAvailable(earcut) + +add_library(earcut INTERFACE) +target_include_directories(earcut SYSTEM INTERFACE "${earcut_SOURCE_DIR}/include") +add_library(earcut::earcut ALIAS earcut) + +set(earcut_FOUND TRUE) +set(earcut_LIBRARIES earcut::earcut) +set(earcut_VERSION "${EARCUT_VERSION}") +set(earcut_INCLUDE_DIR "${earcut_SOURCE_DIR}/include") + +set(EARCUT_LICENSE_FILE "${earcut_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to earcut license file") diff --git a/deps/Findglm.cmake b/deps/Findglm.cmake new file mode 100644 index 0000000..b0e2fa5 --- /dev/null +++ b/deps/Findglm.cmake @@ -0,0 +1,75 @@ +# ============================================================================== +# Find glm +# ============================================================================== +# This module fetches the GLM (OpenGL Mathematics) library. +# +# Targets provided: +# glm::glm - The glm library target +# +# Variables set: +# glm_FOUND - TRUE if glm is available +# glm_LIBRARIES - The glm library target (glm::glm) +# glm_INCLUDE_DIR - Include directories for glm +# glm_VERSION - Version of glm (if available) +# ============================================================================== + +if (DEFINED _FINDGLM_INCLUDED) + return() +endif() +set(_FINDGLM_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 1.0.1 +if (DEFINED glm_FIND_VERSION AND NOT glm_FIND_VERSION STREQUAL "") + set(GLM_VERSION "${glm_FIND_VERSION}") +else() + set(GLM_VERSION "1.0.1") +endif() + +message(STATUS "Fetching glm ${GLM_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(GLM_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +set(_glm_warn_deprecated "${CMAKE_WARN_DEPRECATED}") +set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "Suppress deprecated warnings while fetching glm" FORCE) + +if (GLM_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG ${GLM_VERSION} + ) +endif() + +FetchContent_MakeAvailable(glm) + +set(CMAKE_WARN_DEPRECATED "${_glm_warn_deprecated}" CACHE BOOL "Restore deprecation warnings" FORCE) + +if (NOT TARGET glm::glm) + if (TARGET glm) + add_library(glm::glm ALIAS glm) + else() + message(FATAL_ERROR "Could not fetch glm; no target glm or glm::glm available") + endif() +endif() + +set(glm_FOUND TRUE) +set(glm_LIBRARIES glm::glm) +set(glm_VERSION "${GLM_VERSION}") +get_target_property(_glm_inc glm::glm INTERFACE_INCLUDE_DIRECTORIES) +set(glm_INCLUDE_DIR "${_glm_inc}") + +# Mark glm includes as SYSTEM to suppress warnings from its headers +if (_glm_inc AND TARGET glm) + set_target_properties(glm PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_glm_inc}" + ) +endif() + +set(GLM_LICENSE_FILE "${glm_SOURCE_DIR}/copying.txt" CACHE FILEPATH "Path to GLM license file") diff --git a/deps/Findhttplib.cmake b/deps/Findhttplib.cmake new file mode 100644 index 0000000..3d62c6e --- /dev/null +++ b/deps/Findhttplib.cmake @@ -0,0 +1,93 @@ +# ============================================================================== +# Find httplib +# ============================================================================== +# This module fetches the cpp-httplib HTTP library. +# +# Targets provided: +# httplib::httplib - The httplib library target +# +# Variables set: +# httplib_FOUND - TRUE if httplib is available +# httplib_LIBRARIES - The httplib library target (httplib::httplib) +# httplib_INCLUDE_DIR - Include directories for httplib +# httplib_VERSION - Version of httplib +# ============================================================================== + +if (DEFINED _FINDHTTPLIB_INCLUDED) + return() +endif() +set(_FINDHTTPLIB_INCLUDED TRUE) + +message(STATUS "Fetching httplib") + +include(FetchContent) + +# Choose fetch method depending on git availability +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(HTTPLIB_FETCH_METHOD "GIT") +else() + set(HTTPLIB_FETCH_METHOD "ZIP") +endif() + +# Disable optional compression dependencies +set(HTTPLIB_USE_ZSTD_IF_AVAILABLE OFF CACHE BOOL "" FORCE) +set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF CACHE BOOL "" FORCE) + +if (HTTPLIB_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + httplib + GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git + GIT_TAG v0.28.0 + ) +else() + # GitHub release ZIP for v0.28.0 + FetchContent_Declare( + httplib + URL https://github.com/yhirose/cpp-httplib/archive/refs/tags/v0.28.0.zip + ) +endif() + +FetchContent_MakeAvailable(httplib) + +set(httplib_FOUND TRUE) +set(httplib_VERSION "0.28.0") + +# Ensure we have a usable target +if (NOT TARGET httplib::httplib) + if (TARGET httplib) + add_library(httplib::httplib ALIAS httplib) + else() + # As a last resort, create a header-only INTERFACE target around httplib.h + message(STATUS "No 'httplib' or 'httplib::httplib' target exists. Creating a header-only target manually.") + + add_library(httplib INTERFACE) + add_library(httplib::httplib ALIAS httplib) + + # Try to infer the include directory (where httplib.h lives) + if (DEFINED httplib_SOURCE_DIR) + target_include_directories(httplib INTERFACE "${httplib_SOURCE_DIR}") + elseif (DEFINED HTTPLIB_SOURCE_DIR) + target_include_directories(httplib INTERFACE "${HTTPLIB_SOURCE_DIR}") + endif() + endif() +endif() + +set(httplib_FOUND TRUE) +set(httplib_LIBRARIES httplib::httplib) + +get_target_property(_httplib_inc httplib::httplib INTERFACE_INCLUDE_DIRECTORIES) +if (_httplib_inc) + set(httplib_INCLUDE_DIR "${_httplib_inc}") +endif() + +# Mark httplib includes as SYSTEM to suppress warnings from its headers +if (_httplib_inc AND TARGET httplib) + set_target_properties(httplib PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_httplib_inc}" + ) +endif() + +set(HTTPLIB_LICENSE_FILE "${httplib_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to httplib license file") + +message(STATUS "httplib version: ${httplib_VERSION}") diff --git a/deps/Findimgui.cmake b/deps/Findimgui.cmake new file mode 100644 index 0000000..4e42967 --- /dev/null +++ b/deps/Findimgui.cmake @@ -0,0 +1,59 @@ +# ============================================================================== +# Find imgui +# ============================================================================== +# Builds Dear ImGui from the bundled sources in deps/imgui/. +# +# Targets provided: +# imgui::imgui - The Dear ImGui library target +# +# Variables set: +# imgui_FOUND - TRUE if imgui is available +# imgui_LIBRARIES - The imgui library target (imgui::imgui) +# imgui_INCLUDE_DIR - Include directories for imgui +# +# Configuration (set before find_package): +# IMGUI_BACKEND_PLATFORM - Platform backend (default: glfw) +# IMGUI_BACKEND_RENDERER - Renderer backend (default: opengl3) +# IMGUI_BUILD_DEMO - Include demo sources (default: ON) +# ============================================================================== + +if (DEFINED _FINDIMGUI_INCLUDED) + return() +endif() +set(_FINDIMGUI_INCLUDED TRUE) + +set(IMGUI_BACKEND_PLATFORM "glfw" CACHE STRING "Dear ImGui platform backend") +set(IMGUI_BACKEND_RENDERER "opengl3" CACHE STRING "Dear ImGui renderer backend") +option(IMGUI_BUILD_DEMO "Include Dear ImGui demo window sources" ON) + +set(_imgui_source_dir "${CMAKE_CURRENT_LIST_DIR}/imgui") + +if (NOT EXISTS "${_imgui_source_dir}/imgui.cpp") + message(FATAL_ERROR "imgui sources not found at '${_imgui_source_dir}'") +endif() + +# On Emscripten, GLFW is provided by compiler flags (-sUSE_GLFW=3), not a CMake target. +# Create a stub interface target so imgui's CMakeLists skips find_package(glfw3). +if (IS_EMSCRIPTEN AND NOT TARGET glfw) + add_library(glfw INTERFACE) +endif() + +add_subdirectory("${_imgui_source_dir}" "${CMAKE_BINARY_DIR}/_deps/imgui-build" EXCLUDE_FROM_ALL) + +if (NOT TARGET imgui::imgui) + message(FATAL_ERROR "imgui: target imgui::imgui was not created") +endif() + +set(imgui_FOUND TRUE) +set(imgui_LIBRARIES imgui::imgui) +get_target_property(_imgui_inc imgui INTERFACE_INCLUDE_DIRECTORIES) +set(imgui_INCLUDE_DIR "${_imgui_inc}") + +# Mark imgui includes as SYSTEM to suppress warnings from its headers +if (_imgui_inc AND TARGET imgui) + set_target_properties(imgui PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_imgui_inc}" + ) +endif() + +set(IMGUI_LICENSE_FILE "${_imgui_source_dir}/LICENSE.txt" CACHE FILEPATH "Path to Dear ImGui license file") diff --git a/deps/Findimnodes.cmake b/deps/Findimnodes.cmake new file mode 100644 index 0000000..7b68d3e --- /dev/null +++ b/deps/Findimnodes.cmake @@ -0,0 +1,47 @@ +# ============================================================================== +# Find imnodes +# ============================================================================== +# Builds imnodes from the bundled sources in deps/imnodes/. +# +# Targets provided: +# imnodes::imnodes - The imnodes library target +# +# Variables set: +# imnodes_FOUND - TRUE if imnodes is available +# imnodes_LIBRARIES - The imnodes library target (imnodes::imnodes) +# imnodes_INCLUDE_DIR - Include directories for imnodes +# ============================================================================== + +if (DEFINED _FINDIMNODES_INCLUDED) + return() +endif() +set(_FINDIMNODES_INCLUDED TRUE) + +set(_imnodes_source_dir "${CMAKE_CURRENT_LIST_DIR}/imnodes") + +if (NOT EXISTS "${_imnodes_source_dir}/imnodes.cpp") + message(FATAL_ERROR "imnodes sources not found at '${_imnodes_source_dir}'") +endif() + +if (NOT TARGET imgui::imgui) + message(FATAL_ERROR "imnodes requires imgui::imgui to be available before find_package(imnodes)") +endif() + +add_subdirectory("${_imnodes_source_dir}" "${CMAKE_BINARY_DIR}/_deps/imnodes-build" EXCLUDE_FROM_ALL) + +if (NOT TARGET imnodes::imnodes) + message(FATAL_ERROR "imnodes: target imnodes::imnodes was not created") +endif() + +set(imnodes_FOUND TRUE) +set(imnodes_LIBRARIES imnodes::imnodes) +get_target_property(_imnodes_inc imnodes INTERFACE_INCLUDE_DIRECTORIES) +set(imnodes_INCLUDE_DIR "${_imnodes_inc}") + +if (_imnodes_inc AND TARGET imnodes) + set_target_properties(imnodes PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_imnodes_inc}" + ) +endif() + +set(IMNODES_LICENSE_FILE "${_imnodes_source_dir}/LICENSE.md" CACHE FILEPATH "Path to imnodes license file") diff --git a/deps/Findimplot.cmake b/deps/Findimplot.cmake new file mode 100644 index 0000000..a68a7c2 --- /dev/null +++ b/deps/Findimplot.cmake @@ -0,0 +1,52 @@ +# ============================================================================== +# Find implot +# ============================================================================== +# Builds ImPlot from the bundled sources in deps/implot/. +# +# Targets provided: +# implot::implot - The ImPlot library target +# +# Variables set: +# implot_FOUND - TRUE if implot is available +# implot_LIBRARIES - The implot library target (implot::implot) +# implot_INCLUDE_DIR - Include directories for implot +# +# Configuration (set before find_package): +# IMPLOT_BUILD_DEMO - Include demo sources (default: ON) +# ============================================================================== + +if (DEFINED _FINDIMPLOT_INCLUDED) + return() +endif() +set(_FINDIMPLOT_INCLUDED TRUE) + +option(IMPLOT_BUILD_DEMO "Include ImPlot demo sources" ON) + +set(_implot_source_dir "${CMAKE_CURRENT_LIST_DIR}/implot") + +if (NOT EXISTS "${_implot_source_dir}/implot.cpp") + message(FATAL_ERROR "implot sources not found at '${_implot_source_dir}'") +endif() + +if (NOT TARGET imgui::imgui) + message(FATAL_ERROR "implot requires imgui::imgui to be available before find_package(implot)") +endif() + +add_subdirectory("${_implot_source_dir}" "${CMAKE_BINARY_DIR}/_deps/implot-build" EXCLUDE_FROM_ALL) + +if (NOT TARGET implot::implot) + message(FATAL_ERROR "implot: target implot::implot was not created") +endif() + +set(implot_FOUND TRUE) +set(implot_LIBRARIES implot::implot) +get_target_property(_implot_inc implot INTERFACE_INCLUDE_DIRECTORIES) +set(implot_INCLUDE_DIR "${_implot_inc}") + +if (_implot_inc AND TARGET implot) + set_target_properties(implot PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_implot_inc}" + ) +endif() + +set(IMPLOT_LICENSE_FILE "${_implot_source_dir}/LICENSE" CACHE FILEPATH "Path to ImPlot license file") diff --git a/deps/Findimplot3d.cmake b/deps/Findimplot3d.cmake new file mode 100644 index 0000000..6146959 --- /dev/null +++ b/deps/Findimplot3d.cmake @@ -0,0 +1,52 @@ +# ============================================================================== +# Find implot3d +# ============================================================================== +# Builds ImPlot3D from the bundled sources in deps/implo3d/. +# +# Targets provided: +# implot3d::implot3d - The ImPlot3D library target +# +# Variables set: +# implot3d_FOUND - TRUE if implot3d is available +# implot3d_LIBRARIES - The implot3d library target (implot3d::implot3d) +# implot3d_INCLUDE_DIR - Include directories for implot3d +# +# Configuration (set before find_package): +# IMPLOT3D_BUILD_DEMO - Include demo sources (default: ON) +# ============================================================================== + +if (DEFINED _FINDIMPLOT3D_INCLUDED) + return() +endif() +set(_FINDIMPLOT3D_INCLUDED TRUE) + +option(IMPLOT3D_BUILD_DEMO "Include ImPlot3D demo sources" ON) + +set(_implot3d_source_dir "${CMAKE_CURRENT_LIST_DIR}/implo3d") + +if (NOT EXISTS "${_implot3d_source_dir}/implot3d.cpp") + message(FATAL_ERROR "implot3d sources not found at '${_implot3d_source_dir}'") +endif() + +if (NOT TARGET imgui::imgui) + message(FATAL_ERROR "implot3d requires imgui::imgui to be available before find_package(implot3d)") +endif() + +add_subdirectory("${_implot3d_source_dir}" "${CMAKE_BINARY_DIR}/_deps/implot3d-build" EXCLUDE_FROM_ALL) + +if (NOT TARGET implot3d::implot3d) + message(FATAL_ERROR "implot3d: target implot3d::implot3d was not created") +endif() + +set(implot3d_FOUND TRUE) +set(implot3d_LIBRARIES implot3d::implot3d) +get_target_property(_implot3d_inc implot3d INTERFACE_INCLUDE_DIRECTORIES) +set(implot3d_INCLUDE_DIR "${_implot3d_inc}") + +if (_implot3d_inc AND TARGET implot3d) + set_target_properties(implot3d PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_implot3d_inc}" + ) +endif() + +set(IMPLOT3D_LICENSE_FILE "${_implot3d_source_dir}/LICENSE" CACHE FILEPATH "Path to ImPlot3D license file") diff --git a/deps/Findkfr.cmake b/deps/Findkfr.cmake new file mode 100644 index 0000000..22cb39a --- /dev/null +++ b/deps/Findkfr.cmake @@ -0,0 +1,105 @@ +# ============================================================================== +# Find kfr +# ============================================================================== +# This module fetches the KFR DSP library. +# +# Targets provided: +# kfr::kfr - The kfr library target +# +# Variables set: +# kfr_FOUND - TRUE if kfr is available +# kfr_LIBRARIES - The kfr library target (kfr::kfr) +# kfr_INCLUDE_DIR - Include directories for kfr +# kfr_VERSION - Version of kfr (if available) +# ============================================================================== + +if (DEFINED _FINDKFR_INCLUDED) + return() +endif() +set(_FINDKFR_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 7.0.1 +if (DEFINED kfr_FIND_VERSION AND NOT kfr_FIND_VERSION STREQUAL "") + set(KFR_VERSION "${kfr_FIND_VERSION}") +else() + set(KFR_VERSION "7.0.1") +endif() + +message(STATUS "Fetching kfr ${KFR_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(KFR_FETCH_METHOD "GIT") +else() + set(KFR_FETCH_METHOD "ZIP") +endif() + +# Disable tests, examples, and other unnecessary components +set(ENABLE_TESTS OFF CACHE BOOL "" FORCE) +set(ENABLE_EXAMPLES OFF CACHE BOOL "" FORCE) +set(KFR_ENABLE_ASMTEST OFF CACHE BOOL "" FORCE) +set(KFR_ENABLE_CAPI_BUILD OFF CACHE BOOL "" FORCE) +set(KFR_INSTALL_HEADERS OFF CACHE BOOL "" FORCE) +set(KFR_INSTALL_LIBRARIES OFF CACHE BOOL "" FORCE) + +# Create dummy uninstall target to prevent KFR from creating one +if (NOT TARGET uninstall) + add_custom_target(uninstall) +endif() + +if (KFR_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + kfr + GIT_REPOSITORY https://github.com/kfrlib/kfr.git + GIT_TAG ${KFR_VERSION} + ) +else() + FetchContent_Declare( + kfr + URL https://github.com/kfrlib/kfr/archive/refs/tags/${KFR_VERSION}.zip + ) +endif() + +FetchContent_MakeAvailable(kfr) + +# Fix KFR's redundant standard library linking +# KFR explicitly links stdc++/pthread/m, but these are already linked by the compiler +# On Windows with Clang: these Unix libraries don't exist +# On macOS: stdc++ becomes c++ which duplicates the automatic libc++ linking +if (TARGET kfr) + get_target_property(_kfr_link_libs kfr INTERFACE_LINK_LIBRARIES) + if (_kfr_link_libs) + list(REMOVE_ITEM _kfr_link_libs "stdc++" "c++" "pthread" "m") + set_target_properties(kfr PROPERTIES INTERFACE_LINK_LIBRARIES "${_kfr_link_libs}") + endif() +endif() + +# Remove KFR's uninstall target +if (TARGET uninstall) + set_target_properties(uninstall PROPERTIES EXCLUDE_FROM_ALL TRUE) +endif() + +if (NOT TARGET kfr::kfr) + if (TARGET kfr) + add_library(kfr::kfr ALIAS kfr) + else() + message(FATAL_ERROR "Could not fetch kfr; no target kfr or kfr::kfr available") + endif() +endif() + +set(kfr_FOUND TRUE) +set(kfr_LIBRARIES kfr::kfr) +set(kfr_VERSION "${KFR_VERSION}") +get_target_property(_kfr_inc kfr::kfr INTERFACE_INCLUDE_DIRECTORIES) +set(kfr_INCLUDE_DIR "${_kfr_inc}") + +# Mark kfr includes as SYSTEM to suppress warnings from its headers +if (_kfr_inc AND TARGET kfr) + set_target_properties(kfr PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_kfr_inc}" + ) +endif() + +set(KFR_LICENSE_FILE "${kfr_SOURCE_DIR}/LICENSE.txt" CACHE FILEPATH "Path to KFR license file") diff --git a/deps/Findminiaudio.cmake b/deps/Findminiaudio.cmake new file mode 100644 index 0000000..3d16cef --- /dev/null +++ b/deps/Findminiaudio.cmake @@ -0,0 +1,63 @@ +# ============================================================================== +# Find miniaudio +# ============================================================================== +# This module fetches the miniaudio audio playback and capture library. +# +# Targets provided: +# miniaudio::miniaudio - The miniaudio library target +# +# Variables set: +# miniaudio_FOUND - TRUE if miniaudio is available +# miniaudio_LIBRARIES - The miniaudio library target (miniaudio::miniaudio) +# miniaudio_INCLUDE_DIR - Include directories for miniaudio +# miniaudio_VERSION - Version of miniaudio +# ============================================================================== + +if (DEFINED _FINDMINIAUDIO_INCLUDED) + return() +endif() +set(_FINDMINIAUDIO_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 0.11.21 +if (DEFINED miniaudio_FIND_VERSION AND NOT miniaudio_FIND_VERSION STREQUAL "") + set(MINIAUDIO_VERSION "${miniaudio_FIND_VERSION}") +else() + set(MINIAUDIO_VERSION "0.11.21") +endif() + +message(STATUS "Fetching miniaudio ${MINIAUDIO_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(MINIAUDIO_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +if (MINIAUDIO_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + miniaudio + GIT_REPOSITORY https://github.com/mackron/miniaudio.git + GIT_TAG ${MINIAUDIO_VERSION} + ) +endif() + +FetchContent_MakeAvailable(miniaudio) + +if (NOT TARGET miniaudio) + add_library(miniaudio INTERFACE) + target_include_directories(miniaudio SYSTEM INTERFACE "${miniaudio_SOURCE_DIR}") +endif() + +if (NOT TARGET miniaudio::miniaudio) + add_library(miniaudio::miniaudio ALIAS miniaudio) +endif() + +set(miniaudio_FOUND TRUE) +set(miniaudio_LIBRARIES miniaudio::miniaudio) +set(miniaudio_VERSION "${MINIAUDIO_VERSION}") +set(miniaudio_INCLUDE_DIR "${miniaudio_SOURCE_DIR}") + +set(MINIAUDIO_LICENSE_FILE "${miniaudio_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to miniaudio license file") diff --git a/deps/Findmvt.cmake b/deps/Findmvt.cmake new file mode 100644 index 0000000..531d9bb --- /dev/null +++ b/deps/Findmvt.cmake @@ -0,0 +1,62 @@ +# ============================================================================== +# Findmvt.cmake +# ============================================================================== +# This module provides the Mapbox Vector Tile Specification (v2.1) for use +# with protozero. The .proto file is downloaded as a reference; actual decoding +# is done manually via protozero (no code generation step required). +# +# Targets provided: +# mvt::mvt - Interface target (pulls in protozero) +# +# Variables set: +# mvt_FOUND - TRUE if mvt is available +# mvt_LIBRARIES - The library target (mvt::mvt) +# mvt_PROTO_FILE - Path to the downloaded vector_tile.proto +# ============================================================================== + +if (DEFINED _FINDMVT_INCLUDED) + return() +endif() +set(_FINDMVT_INCLUDED TRUE) + +# ------------------------------------------------------------------------------ +# Protozero (the actual parsing library) +# ------------------------------------------------------------------------------ +find_package(protozero REQUIRED) + +# ------------------------------------------------------------------------------ +# Download .proto file (reference only — no code generation) +# ------------------------------------------------------------------------------ +set(_MVT_PROTO_DIR "${CMAKE_CURRENT_BINARY_DIR}/mvt_proto") +set(_MVT_PROTO_FILE "${_MVT_PROTO_DIR}/vector_tile.proto") + +file(MAKE_DIRECTORY "${_MVT_PROTO_DIR}") + +if (NOT EXISTS "${_MVT_PROTO_FILE}") + message(STATUS "Downloading vector_tile.proto") + file(DOWNLOAD + "https://raw.githubusercontent.com/mapbox/vector-tile-spec/refs/heads/master/2.1/vector_tile.proto" + "${_MVT_PROTO_FILE}" + STATUS _download_status + ) + list(GET _download_status 0 _download_code) + if (NOT _download_code EQUAL 0) + list(GET _download_status 1 _download_error) + message(FATAL_ERROR "Failed to download vector_tile.proto: ${_download_error}") + endif() +endif() + +message(STATUS "MVT proto: ${_MVT_PROTO_FILE}") + +# ------------------------------------------------------------------------------ +# Interface target +# ------------------------------------------------------------------------------ +if (NOT TARGET mvt::mvt) + add_library(mvt INTERFACE) + target_link_libraries(mvt INTERFACE protozero::protozero) + add_library(mvt::mvt ALIAS mvt) +endif() + +set(mvt_FOUND TRUE) +set(mvt_LIBRARIES mvt::mvt) +set(mvt_PROTO_FILE "${_MVT_PROTO_FILE}" CACHE FILEPATH "Path to vector_tile.proto") diff --git a/deps/Findprotozero.cmake b/deps/Findprotozero.cmake new file mode 100644 index 0000000..bc5b417 --- /dev/null +++ b/deps/Findprotozero.cmake @@ -0,0 +1,57 @@ +# ============================================================================== +# Findprotozero.cmake +# ============================================================================== +# This module fetches the protozero header-only protobuf reader library. +# +# Targets provided: +# protozero::protozero - The protozero interface target +# +# Variables set: +# protozero_FOUND - TRUE if protozero is available +# protozero_LIBRARIES - The library target (protozero::protozero) +# protozero_INCLUDE_DIR - Include directory for protozero headers +# protozero_VERSION - Version of protozero +# ============================================================================== + +if (DEFINED _FINDPROTOZERO_INCLUDED) + return() +endif() +set(_FINDPROTOZERO_INCLUDED TRUE) + +if (DEFINED protozero_FIND_VERSION AND NOT protozero_FIND_VERSION STREQUAL "") + set(PROTOZERO_VERSION "${protozero_FIND_VERSION}") +else() + set(PROTOZERO_VERSION "1.8.1") +endif() + +message(STATUS "Fetching protozero ${PROTOZERO_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (NOT GIT_EXECUTABLE) + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +FetchContent_Declare( + protozero + GIT_REPOSITORY https://github.com/mapbox/protozero.git + GIT_TAG v${PROTOZERO_VERSION} + SOURCE_SUBDIR _unused # no CMakeLists.txt here; skips add_subdirectory +) + +# SOURCE_SUBDIR points to a non-existent directory so FetchContent_MakeAvailable +# skips add_subdirectory (protozero's tests/tools/docs are never configured). +FetchContent_MakeAvailable(protozero) + +add_library(protozero INTERFACE) +target_include_directories(protozero SYSTEM INTERFACE "${protozero_SOURCE_DIR}/include") +add_library(protozero::protozero ALIAS protozero) + +set(protozero_FOUND TRUE) +set(protozero_LIBRARIES protozero::protozero) +set(protozero_VERSION "${PROTOZERO_VERSION}") +get_target_property(_protozero_inc protozero::protozero INTERFACE_INCLUDE_DIRECTORIES) +set(protozero_INCLUDE_DIR "${_protozero_inc}") + +set(PROTOZERO_LICENSE_FILE "${protozero_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to protozero license file") diff --git a/deps/Findsokol.cmake b/deps/Findsokol.cmake new file mode 100644 index 0000000..40c08ff --- /dev/null +++ b/deps/Findsokol.cmake @@ -0,0 +1,56 @@ +# ============================================================================== +# Findsokol.cmake +# ============================================================================== +# This module fetches the sokol single-header C libraries. +# https://github.com/floooh/sokol +# +# Targets provided: +# sokol::sokol - Interface target (headers at repo root) +# +# Variables set: +# sokol_FOUND - TRUE if sokol is available +# sokol_LIBRARIES - The library target (sokol::sokol) +# sokol_INCLUDE_DIR - Include directory (repo root) +# ============================================================================== + +if (DEFINED _FINDSOKOL_INCLUDED) + return() +endif() +set(_FINDSOKOL_INCLUDED TRUE) + +if (DEFINED sokol_FIND_VERSION AND NOT sokol_FIND_VERSION STREQUAL "") + set(SOKOL_TAG "${sokol_FIND_VERSION}") +else() + set(SOKOL_TAG "HEAD") +endif() + +message(STATUS "Fetching sokol @ ${SOKOL_TAG}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (NOT GIT_EXECUTABLE) + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +FetchContent_Declare( + sokol + GIT_REPOSITORY https://github.com/floooh/sokol.git + GIT_TAG ${SOKOL_TAG} + GIT_SHALLOW TRUE + SOURCE_SUBDIR _unused # no CMakeLists.txt here; skips add_subdirectory +) + +# SOURCE_SUBDIR points to a non-existent directory so FetchContent_MakeAvailable +# skips add_subdirectory; sokol is single-header files, we only need the root dir. +FetchContent_MakeAvailable(sokol) + +add_library(sokol INTERFACE) +target_include_directories(sokol SYSTEM INTERFACE "${sokol_SOURCE_DIR}") +add_library(sokol::sokol ALIAS sokol) + +set(sokol_FOUND TRUE) +set(sokol_LIBRARIES sokol::sokol) +set(sokol_INCLUDE_DIR "${sokol_SOURCE_DIR}") + +set(SOKOL_LICENSE_FILE "${sokol_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to sokol license file") diff --git a/deps/Findspdlog.cmake b/deps/Findspdlog.cmake new file mode 100644 index 0000000..6ad431e --- /dev/null +++ b/deps/Findspdlog.cmake @@ -0,0 +1,76 @@ +# ============================================================================== +# Find spdlog +# ============================================================================== +# This module fetches the spdlog logging library. +# +# Targets provided: +# spdlog::spdlog - The spdlog library target +# +# Variables set: +# spdlog_FOUND - TRUE if spdlog is available +# spdlog_LIBRARIES - The spdlog library target (spdlog::spdlog) +# spdlog_INCLUDE_DIR - Include directories for spdlog +# spdlog_VERSION - Version of spdlog (if available) +# ============================================================================== + +if (DEFINED _FINDSPDLOG_INCLUDED) + return() +endif() +set(_FINDSPDLOG_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to v1.17.0 +# spdlog tags are prefixed with 'v', so prepend it if not already present +if (DEFINED spdlog_FIND_VERSION AND NOT spdlog_FIND_VERSION STREQUAL "") + set(SPDLOG_VERSION "v${spdlog_FIND_VERSION}") +else() + set(SPDLOG_VERSION "v1.17.0") +endif() + +message(STATUS "Fetching spdlog ${SPDLOG_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(SPDLOG_FETCH_METHOD "GIT") +else() + set(SPDLOG_FETCH_METHOD "ZIP") +endif() + +if (SPDLOG_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG ${SPDLOG_VERSION} + ) +else() + FetchContent_Declare( + spdlog + URL https://github.com/gabime/spdlog/archive/refs/tags/${SPDLOG_VERSION}.zip + ) +endif() + +FetchContent_MakeAvailable(spdlog) + +if (NOT TARGET spdlog::spdlog) + if (TARGET spdlog) + add_library(spdlog::spdlog ALIAS spdlog) + else() + message(FATAL_ERROR "Could not fetch spdlog; no target spdlog or spdlog::spdlog available") + endif() +endif() + +set(spdlog_FOUND TRUE) +set(spdlog_LIBRARIES spdlog::spdlog) +set(spdlog_VERSION "${SPDLOG_VERSION}") +get_target_property(_spdlog_inc spdlog::spdlog INTERFACE_INCLUDE_DIRECTORIES) +set(spdlog_INCLUDE_DIR "${_spdlog_inc}") + +# Mark spdlog includes as SYSTEM to suppress warnings from its headers +if (_spdlog_inc AND TARGET spdlog) + set_target_properties(spdlog PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_spdlog_inc}" + ) +endif() + +set(SPDLOG_LICENSE_FILE "${spdlog_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to spdlog license file") diff --git a/deps/Findsqlite3.cmake b/deps/Findsqlite3.cmake new file mode 100644 index 0000000..1564673 --- /dev/null +++ b/deps/Findsqlite3.cmake @@ -0,0 +1,46 @@ +# ============================================================================== +# Findsqlite3.cmake +# ============================================================================== +# This module fetches and builds the SQLite amalgamation as a static library. +# +# Targets provided: +# sqlite3::sqlite3 - The SQLite static library target +# +# Variables set: +# sqlite3_FOUND - TRUE if sqlite3 is available +# sqlite3_LIBRARIES - The sqlite3 library target (sqlite3::sqlite3) +# sqlite3_INCLUDE_DIR - Include directory for sqlite3.h +# sqlite3_VERSION - Version of sqlite3 +# ============================================================================== + +if (DEFINED _FINDSQLITE3_INCLUDED) + return() +endif() +set(_FINDSQLITE3_INCLUDED TRUE) + +set(SQLITE3_VERSION "3.52.0") + +message(STATUS "Fetching sqlite3 ${SQLITE3_VERSION}") + +include(FetchContent) + +FetchContent_Declare( + sqlite3_amalgamation + URL https://sqlite.org/2026/sqlite-amalgamation-3520000.zip + URL_HASH SHA3_256=3fab435106250ea1cdfd5f9fa1657ad832fd431df1d817d48af66ba166e0d16e + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) + +FetchContent_MakeAvailable(sqlite3_amalgamation) + +if (NOT TARGET sqlite3::sqlite3) + add_library(sqlite3 STATIC ${sqlite3_amalgamation_SOURCE_DIR}/sqlite3.c) + target_include_directories(sqlite3 PUBLIC ${sqlite3_amalgamation_SOURCE_DIR}) + target_link_libraries(sqlite3 PRIVATE ${CMAKE_DL_LIBS}) + add_library(sqlite3::sqlite3 ALIAS sqlite3) +endif() + +set(sqlite3_FOUND TRUE) +set(sqlite3_LIBRARIES sqlite3::sqlite3) +set(sqlite3_VERSION "${SQLITE3_VERSION}") +set(sqlite3_INCLUDE_DIR "${sqlite3_amalgamation_SOURCE_DIR}") diff --git a/deps/Findstb.cmake b/deps/Findstb.cmake new file mode 100644 index 0000000..cbd0d2e --- /dev/null +++ b/deps/Findstb.cmake @@ -0,0 +1,75 @@ +# ============================================================================== +# Find stb +# ============================================================================== +# This module fetches the stb single-file public domain libraries. +# +# Targets provided: +# stb::stb - The stb library target +# +# Variables set: +# stb_FOUND - TRUE if stb is available +# stb_LIBRARIES - The stb library target (stb::stb) +# stb_INCLUDE_DIR - Include directories for stb +# stb_VERSION - Version of stb (commit-based) +# +# Usage notes: +# stb headers are header-only but require an implementation macro to be +# defined in exactly ONE translation unit (.cpp file) before including the +# header, e.g.: +# +# #define STB_IMAGE_IMPLEMENTATION +# #include +# +# Each stb header has its own implementation macro. Do NOT define the macro +# in header files or in more than one translation unit or you will get +# duplicate symbol linker errors. +# ============================================================================== + +if (DEFINED _FINDSTB_INCLUDED) + return() +endif() +set(_FINDSTB_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 2.30 +if (DEFINED stb_FIND_VERSION AND NOT stb_FIND_VERSION STREQUAL "") + set(STB_VERSION "${stb_FIND_VERSION}") +else() + set(STB_VERSION "2fb8c5a3deb2110c89669f8d6f36e5833b556b44") +endif() + +message(STATUS "Fetching stb ${STB_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(STB_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +if (STB_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG ${STB_VERSION} + ) +endif() + +FetchContent_MakeAvailable(stb) + +if (NOT TARGET stb) + add_library(stb INTERFACE) + target_include_directories(stb SYSTEM INTERFACE "${stb_SOURCE_DIR}") +endif() + +if (NOT TARGET stb::stb) + add_library(stb::stb ALIAS stb) +endif() + +set(stb_FOUND TRUE) +set(stb_LIBRARIES stb::stb) +set(stb_VERSION "${STB_VERSION}") +set(stb_INCLUDE_DIR "${stb_SOURCE_DIR}") + +set(STB_LICENSE_FILE "${stb_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to stb license file") diff --git a/deps/Findtinyobjloader.cmake b/deps/Findtinyobjloader.cmake new file mode 100644 index 0000000..29a1d4e --- /dev/null +++ b/deps/Findtinyobjloader.cmake @@ -0,0 +1,70 @@ +# ============================================================================== +# Find tinyobjloader +# ============================================================================== +# This module fetches the tinyobjloader library. +# +# Targets provided: +# tinyobjloader::tinyobjloader - The tinyobjloader library target +# +# Variables set: +# tinyobjloader_FOUND - TRUE if tinyobjloader is available +# tinyobjloader_LIBRARIES - The tinyobjloader library target +# tinyobjloader_INCLUDE_DIR - Include directories for tinyobjloader +# tinyobjloader_VERSION - Version of tinyobjloader (if available) +# ============================================================================== + +if (DEFINED _FINDTINYOBJLOADER_INCLUDED) + return() +endif() +set(_FINDTINYOBJLOADER_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to v2.0.0rc13 +if (DEFINED tinyobjloader_FIND_VERSION AND NOT tinyobjloader_FIND_VERSION STREQUAL "") + set(TINYOBJLOADER_VERSION "${tinyobjloader_FIND_VERSION}") +else() + set(TINYOBJLOADER_VERSION "v2.0.0rc13") +endif() + +message(STATUS "Fetching tinyobjloader ${TINYOBJLOADER_VERSION} via FetchContent...") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(TINYOBJLOADER_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +if (TINYOBJLOADER_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + tinyobjloader + GIT_REPOSITORY https://github.com/tinyobjloader/tinyobjloader.git + GIT_TAG ${TINYOBJLOADER_VERSION} + ) +endif() + +FetchContent_MakeAvailable(tinyobjloader) + +if (NOT TARGET tinyobjloader::tinyobjloader) + if (TARGET tinyobjloader) + add_library(tinyobjloader::tinyobjloader ALIAS tinyobjloader) + else() + message(FATAL_ERROR "Could not fetch tinyobjloader; no target tinyobjloader or tinyobjloader::tinyobjloader available") + endif() +endif() + +set(tinyobjloader_FOUND TRUE) +set(tinyobjloader_LIBRARIES tinyobjloader::tinyobjloader) +set(tinyobjloader_VERSION "${TINYOBJLOADER_VERSION}") +get_target_property(_tol_inc tinyobjloader::tinyobjloader INTERFACE_INCLUDE_DIRECTORIES) +set(tinyobjloader_INCLUDE_DIR "${_tol_inc}") + +# Mark tinyobjloader includes as SYSTEM to suppress warnings from its headers +if (_tol_inc AND TARGET tinyobjloader) + set_target_properties(tinyobjloader PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_tol_inc}" + ) +endif() + +set(TINYOBJLOADER_LICENSE_FILE "${tinyobjloader_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to tinyobjloader license file") diff --git a/deps/Findutf8cpp.cmake b/deps/Findutf8cpp.cmake new file mode 100644 index 0000000..77de3c3 --- /dev/null +++ b/deps/Findutf8cpp.cmake @@ -0,0 +1,75 @@ +# ============================================================================== +# Find utf8cpp +# ============================================================================== +# This module fetches the UTF-8 with C++ library. +# +# Targets provided: +# utf8cpp::utf8cpp - The utf8cpp library target +# +# Variables set: +# utf8cpp_FOUND - TRUE if utf8cpp is available +# utf8cpp_LIBRARIES - The utf8cpp library target (utf8cpp::utf8cpp) +# utf8cpp_INCLUDE_DIR - Include directories for utf8cpp +# utf8cpp_VERSION - Version of utf8cpp +# ============================================================================== + +if (DEFINED _FINDUTF8CPP_INCLUDED) + return() +endif() +set(_FINDUTF8CPP_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 4.0.6 +if (DEFINED utf8cpp_FIND_VERSION AND NOT utf8cpp_FIND_VERSION STREQUAL "") + set(UTF8CPP_VERSION "${utf8cpp_FIND_VERSION}") +else() + set(UTF8CPP_VERSION "4.0.6") +endif() + +message(STATUS "Fetching utf8cpp ${UTF8CPP_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(UTF8CPP_FETCH_METHOD "GIT") +else() + set(UTF8CPP_FETCH_METHOD "ZIP") +endif() + +if (UTF8CPP_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + utf8cpp + GIT_REPOSITORY https://github.com/nemtrif/utfcpp.git + GIT_TAG v${UTF8CPP_VERSION} + ) +else() + FetchContent_Declare( + utf8cpp + URL https://github.com/nemtrif/utfcpp/archive/refs/tags/v${UTF8CPP_VERSION}.zip + ) +endif() + +FetchContent_MakeAvailable(utf8cpp) + +if (NOT TARGET utf8cpp::utf8cpp) + if (TARGET utf8cpp) + add_library(utf8cpp::utf8cpp ALIAS utf8cpp) + else() + message(FATAL_ERROR "Could not fetch utf8cpp; no target utf8cpp or utf8cpp::utf8cpp available") + endif() +endif() + +set(utf8cpp_FOUND TRUE) +set(utf8cpp_LIBRARIES utf8cpp::utf8cpp) +set(utf8cpp_VERSION "${UTF8CPP_VERSION}") +get_target_property(_utf8cpp_inc utf8cpp::utf8cpp INTERFACE_INCLUDE_DIRECTORIES) +set(utf8cpp_INCLUDE_DIR "${_utf8cpp_inc}") + +# Mark utf8cpp includes as SYSTEM to suppress warnings from its headers +if (_utf8cpp_inc AND TARGET utf8cpp) + set_target_properties(utf8cpp PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_utf8cpp_inc}" + ) +endif() + +set(UTF8CPP_LICENSE_FILE "${utf8cpp_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to utf8cpp license file") diff --git a/deps/Findzlib.cmake b/deps/Findzlib.cmake new file mode 100644 index 0000000..ba471d4 --- /dev/null +++ b/deps/Findzlib.cmake @@ -0,0 +1,53 @@ +# ============================================================================== +# Findzlib.cmake +# ============================================================================== +# This module fetches and builds the zlib compression library. +# https://github.com/madler/zlib +# +# Targets provided: +# ZLIB::ZLIBSTATIC - Static library target +# +# Variables set: +# zlib_FOUND - TRUE if zlib is available +# zlib_LIBRARIES - The library target (ZLIB::ZLIBSTATIC) +# zlib_INCLUDE_DIR - Include directories for zlib +# ============================================================================== + +if (DEFINED _FINDZLIB_INCLUDED) + return() +endif() +set(_FINDZLIB_INCLUDED TRUE) + +if (DEFINED zlib_FIND_VERSION AND NOT zlib_FIND_VERSION STREQUAL "") + set(ZLIB_TAG "v${zlib_FIND_VERSION}") +else() + set(ZLIB_TAG "v1.3.2") +endif() + +message(STATUS "Fetching zlib @ ${ZLIB_TAG}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (NOT GIT_EXECUTABLE) + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +FetchContent_Declare( + zlib + GIT_REPOSITORY https://github.com/madler/zlib.git + GIT_TAG ${ZLIB_TAG} + GIT_SHALLOW TRUE + EXCLUDE_FROM_ALL +) + +set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(zlib) + +# zlib's CMakeLists provides ZLIB::ZLIBSTATIC for the static build +set(zlib_FOUND TRUE) +set(zlib_LIBRARIES ZLIB::ZLIBSTATIC) +set(zlib_INCLUDE_DIR "${zlib_SOURCE_DIR};${zlib_BINARY_DIR}") + +set(ZLIB_LICENSE_FILE "${zlib_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to zlib license file") diff --git a/deps/Flags.cmake b/deps/Flags.cmake new file mode 100644 index 0000000..c083949 --- /dev/null +++ b/deps/Flags.cmake @@ -0,0 +1,162 @@ +# ============================================================================== +# Compiler and Linker Flags +# ============================================================================== +# This module sets platform-specific and compiler-specific flags, libraries, +# and definitions used throughout the build system. +# +# Variables set: +# BASE_LIBRARIES - Libraries to link based on target platform +# BASE_DEFINITIONS - Preprocessor definitions based on target platform +# +# This module also configures global compile and link options for: +# - Emscripten (WebAssembly) builds +# - Clang/GCC warning flags +# - MSVC warning flags and settings +# +# Requires: Platform.cmake must be included first (for IS_* variables) +# ============================================================================== + +# ------------------------------------------------------------------------------ +# Platform-Specific Link Libraries +# ------------------------------------------------------------------------------ +# These libraries are required for OpenGL and windowing on each platform. + +# detect_windows_sdk() +# +# Detects the latest installed Windows 10 SDK and sets out_var to its um/x64 lib path. +# +# Parameters: +# - OUT_VAR Name of the variable to set with the SDK lib path +function(detect_windows_sdk OUT_VAR) + set(_ROOT "C:/Program Files (x86)/Windows Kits/10/Lib") + file(GLOB _VERSIONS RELATIVE "${_ROOT}" "${_ROOT}/10.*") + list(SORT _VERSIONS ORDER DESCENDING) + list(GET _VERSIONS 0 _VERSION) + message(STATUS "Windows SDK: ${_VERSION}") + set(${OUT_VAR} "${_ROOT}/${_VERSION}/um/x64" PARENT_SCOPE) +endfunction() + +# find_and_link( ) +# +# Searches for a library and, if found, appends it to the output list. +# +# Parameters: +# - OUT_VAR Name of the list variable to append the library to +# - LIB_NAME The library filename to search for (e.g. "d3d12.lib") +# - DISPLAY_NAME Human-readable name for status messages (e.g. "Direct3D 12") +function(find_and_link OUT_VAR LIB_NAME DISPLAY_NAME) + find_library(_FOUND_LIB ${LIB_NAME} HINTS "${_WINSDK_LIB_PATH}") + if (_FOUND_LIB) + message(STATUS " [x] ${DISPLAY_NAME} (${LIB_NAME})") + list(APPEND ${OUT_VAR} "${LIB_NAME}") + set(${OUT_VAR} "${${OUT_VAR}}" PARENT_SCOPE) + else() + message(STATUS " [ ] ${DISPLAY_NAME} (${LIB_NAME})") + endif() + unset(_FOUND_LIB CACHE) +endfunction() + +# find_and_link_framework( ) +# +# Searches for an Apple framework and, if found, appends -framework to the output list. +# +# Parameters: +# - OUT_VAR Name of the list variable to append the flag to +# - FRAMEWORK_NAME The framework name to search for (e.g. "Cocoa") +# - DISPLAY_NAME Human-readable name for status messages +function(find_and_link_framework OUT_VAR FRAMEWORK_NAME DISPLAY_NAME) + find_library(_FOUND_LIB ${FRAMEWORK_NAME}) + if (_FOUND_LIB) + message(STATUS " [x] ${DISPLAY_NAME} (-framework ${FRAMEWORK_NAME})") + list(APPEND ${OUT_VAR} "-framework ${FRAMEWORK_NAME}") + set(${OUT_VAR} "${${OUT_VAR}}" PARENT_SCOPE) + else() + message(STATUS " [ ] ${DISPLAY_NAME} (-framework ${FRAMEWORK_NAME})") + endif() + unset(_FOUND_LIB CACHE) +endfunction() + +set(BASE_LIBRARIES "") +if (IS_MACOS) + # TODO: Support macOS + message(FATAL_ERROR "macOS not supported!") + message(STATUS "macOS link libraries:") + find_and_link_framework(BASE_LIBRARIES "Cocoa" "Cocoa") + find_and_link_framework(BASE_LIBRARIES "IOKit" "IOKit") + find_and_link_framework(BASE_LIBRARIES "CoreVideo" "Core Video") + find_and_link_framework(BASE_LIBRARIES "OpenGL" "OpenGL") +elseif (IS_LINUX) # Linux, BSD, Solaris, Minix + message(STATUS "Linux link libraries:") + find_and_link(BASE_LIBRARIES "dl" "Dynamic Linking") + find_and_link(BASE_LIBRARIES "m" "Math") + find_and_link(BASE_LIBRARIES "GL" "OpenGL") + find_and_link(BASE_LIBRARIES "X11" "X11") +elseif (IS_WINDOWS) + detect_windows_sdk(_WINSDK_LIB_PATH) + message(STATUS "Windows link libraries:") + find_and_link(BASE_LIBRARIES "OpenGL32.lib" "OpenGL") + find_and_link(BASE_LIBRARIES "winmm.lib" "WinMM") + find_and_link(BASE_LIBRARIES "dwmapi.lib" "DWM API") +elseif(IS_EMSCRIPTEN) + # add_compile_definitions("ImDrawIdx=unsigned int") + add_compile_options( + "-fexceptions" + ) + add_link_options( + # "-pthread" + "-fexceptions" + # "-sUSE_PTHREADS=1" + # "-sALLOW_MEMORY_GROWTH=1" + # "-sINITIAL_MEMORY=67108864" + "-sFETCH=1" + # Graphics + "-sUSE_GLFW=3" + # "--use-port=emdawnwebgpu" + "-sUSE_WEBGL2=1" + "-sFULL_ES3=1" + # "-sSTACK_SIZE=1048576" + # Safari Pointer Lock API polyfill + "--pre-js=${CMAKE_SOURCE_DIR}/res/pre.js" + ) +endif() + +# ------------------------------------------------------------------------------ +# Platform-Specific Definitions +# ------------------------------------------------------------------------------ +set(BASE_DEFINITIONS + "ASIO_STANDALONE" +) +if (IS_WINDOWS) + set(BASE_DEFINITIONS + ${BASE_DEFINITIONS} + # See _WIN32_WINNT version constants + # https://learn.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt + "_WIN32_WINNT=0x0A00" + ) +elseif (IS_EMSCRIPTEN) + set(BASE_DEFINITIONS + ${BASE_DEFINITIONS} + "ASIO_DISABLE_THREADS" + ) +endif() + +# ------------------------------------------------------------------------------ +# Compiler Warning Flags +# ------------------------------------------------------------------------------ +# These are stored in BASE_OPTIONS and applied per-target via +# target_compile_options() to avoid polluting external dependencies. +set(BASE_OPTIONS "") +if (IS_CLANG_OR_GCC) + set(BASE_OPTIONS + "-Wall" # Enable all common warnings + "-Wextra" # Enable extra warnings + "-Werror" # Treat warnings as errors + ) +elseif (IS_MSVC) + set(BASE_OPTIONS + "/W4" # Warning level 4 (high) + "/WX" # Treat warnings as errors + "/utf-8" # Set source and execution character set to UTF-8 + "/Zc:__cplusplus" # Report correct C++ standard version in __cplusplus + ) +endif() diff --git a/deps/IDE.cmake b/deps/IDE.cmake new file mode 100644 index 0000000..a342224 --- /dev/null +++ b/deps/IDE.cmake @@ -0,0 +1,82 @@ +# ============================================================================== +# IDE Integration +# ============================================================================== +# This module configures IDE-specific settings for better project organization. +# Currently supports Visual Studio and Xcode by grouping dependency targets +# into folders for cleaner navigation in the IDE's solution/project explorer. +# +# Folder structure created: +# deps/ - Third-party libraries (fmt, glad, imgui, sqlite3, etc.) +# deps/GLFW/ - GLFW library and related targets +# deps/gtest/ - Google Test targets +# deps/tinyobj/ - tinyobjloader and its uninstall target +# ============================================================================== + +# ------------------------------------------------------------------------------ +# Visual Studio and Xcode Folder Organization +# ------------------------------------------------------------------------------ +# Group dependency targets into logical folders for easier navigation. +# This only affects IDE project generation and has no effect on the build. +# Helper function to set folder property if target exists +function(set_target_folder target folder) + if (TARGET ${target}) + set_target_properties(${target} PROPERTIES FOLDER ${folder}) + endif() +endfunction() + +if (CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR MATCHES "Xcode") + # General deps + # Networking / async + set_target_folder(asio deps) + + # Text / parsing + set_target_folder(ctre deps) + set_target_folder(fmt deps) + set_target_folder(utf8cpp deps) + + # Math / geometry + set_target_folder(earcut deps) + set_target_folder(glm deps) + + # Graphics / windowing + set_target_folder(glad deps) + set_target_folder(imgui deps) + set_target_folder(imgui_backend deps) + set_target_folder(implot deps) + set_target_folder(sokol deps) + set_target_folder(stb deps) + + # Audio + set_target_folder(miniaudio deps) + + # Map / geo + set_target_folder(protozero deps) + set_target_folder(mvt deps) + + # Data / storage + set_target_folder(sqlite3 deps) + set_target_folder(zlib deps) + set_target_folder(zlibstatic deps) + + # Logging + set_target_folder(spdlog deps) + + # 3D assets + set_target_folder(tinyobjloader deps/tinyobj) + + # GLFW + set_target_folder(glfw deps/GLFW) + set_target_folder(update_mappings deps/GLFW) + + # GTest + set_target_folder(gtest deps/gtest) + set_target_folder(gtest_main deps/gtest) + set_target_folder(gmock deps/gtest) + set_target_folder(gmock_main deps/gtest) + + # Hide uninstall target + if (TARGET uninstall) + set_target_properties(uninstall PROPERTIES FOLDER deps/tinyobj) + set_target_properties(uninstall PROPERTIES EXCLUDE_FROM_ALL TRUE) + endif() +endif() diff --git a/deps/Platform.cmake b/deps/Platform.cmake new file mode 100644 index 0000000..6326538 --- /dev/null +++ b/deps/Platform.cmake @@ -0,0 +1,64 @@ +# ============================================================================== +# Platform Detection +# ============================================================================== +# This module detects the current platform and compiler, setting IS_* variables +# that can be used throughout the build system for conditional logic. +# +# Compiler flags set: +# IS_CLANG_OR_GCC - TRUE if using Clang or GCC compiler +# IS_MSVC - TRUE if using Microsoft Visual C++ compiler +# +# Platform flags set: +# IS_WINDOWS - TRUE if building for Windows +# IS_LINUX - TRUE if building for Linux +# IS_MACOS - TRUE if building for macOS +# IS_IOS - TRUE if building for iOS +# IS_ANDROID - TRUE if building for Android +# IS_EMSCRIPTEN - TRUE if building for WebAssembly via Emscripten +# ============================================================================== + +# ------------------------------------------------------------------------------ +# Compiler Detection +# ------------------------------------------------------------------------------ +set(IS_CLANG_OR_GCC FALSE) +set(IS_MSVC FALSE) + +if(MSVC) + set(IS_MSVC TRUE) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + set(IS_CLANG_OR_GCC TRUE) +endif() + +# ------------------------------------------------------------------------------ +# Platform Detection +# ------------------------------------------------------------------------------ +set(IS_WINDOWS FALSE) +set(IS_LINUX FALSE) +set(IS_MACOS FALSE) +set(IS_IOS FALSE) +set(IS_ANDROID FALSE) +set(IS_EMSCRIPTEN FALSE) + +if(EMSCRIPTEN) + message(STATUS "Platform: Emscripten") + set(IS_EMSCRIPTEN TRUE) +elseif(ANDROID) + message(STATUS "Platform: Android") + set(IS_ANDROID TRUE) +elseif(APPLE) + if(IOS) + message(STATUS "Platform: iOS") + set(IS_IOS TRUE) + else() + message(STATUS "Platform: macOS") + set(IS_MACOS TRUE) + endif() +elseif(WIN32) + message(STATUS "Platform: Windows") + set(IS_WINDOWS TRUE) +elseif(UNIX) + message(STATUS "Platform: Linux") + set(IS_LINUX TRUE) +else() + message(FATAL_ERROR "Unknown platform!") +endif()