From 0533ef053e44af43575a3aeb497f35608da3b661 Mon Sep 17 00:00:00 2001 From: nanako <469449812@qq.com> Date: Sat, 3 Jan 2026 00:44:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96Mirai=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=EF=BC=8C=E5=8C=85=E6=8B=AC=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E7=BB=84=E4=BB=B6=E5=92=8C=E6=B5=8B=E8=AF=95=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + CMakeLists.txt | 27 ++ cmake/compiler_options.cmake | 108 ++++++ cmake/config_macos.cmake | 41 ++ cmake/detect_os.cmake | 205 ++++++++++ cmake/mingw_dll.cmake | 41 ++ cmake/mirai_utils.cmake | 44 +++ cmake/project_cpp_standard.cmake | 118 ++++++ cmake/retrieve_files.cmake | 617 +++++++++++++++++++++++++++++++ cmake/shader_compile.cmake | 515 ++++++++++++++++++++++++++ example/CMakeLists.txt | 5 + example/main.cpp | 3 + src/CMakeLists.txt | 25 ++ src/core/object.cpp | 10 + src/core/object.h | 228 ++++++++++++ src/core/types.h | 27 ++ tests/CMakeLists.txt | 8 + tests/main.cpp | 25 ++ tests/object_test.h | 11 + vcpkg.json | 78 ++++ 20 files changed, 2138 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/compiler_options.cmake create mode 100644 cmake/config_macos.cmake create mode 100644 cmake/detect_os.cmake create mode 100644 cmake/mingw_dll.cmake create mode 100644 cmake/mirai_utils.cmake create mode 100644 cmake/project_cpp_standard.cmake create mode 100644 cmake/retrieve_files.cmake create mode 100644 cmake/shader_compile.cmake create mode 100644 example/CMakeLists.txt create mode 100644 example/main.cpp create mode 100644 src/CMakeLists.txt create mode 100644 src/core/object.cpp create mode 100644 src/core/object.h create mode 100644 src/core/types.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/main.cpp create mode 100644 tests/object_test.h create mode 100644 vcpkg.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db2407e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/cmake-build-* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b0696f8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +# ================================================================================================ +# vcpkg 工具链集成 +# 必须在 project() 之前设置 +# ================================================================================================ +if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) + set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + CACHE STRING "Vcpkg toolchain file") +endif() + +project(mirai_project) + +include(cmake/retrieve_files.cmake) +include(cmake/detect_os.cmake) +include(cmake/config_macos.cmake) +include(cmake/mirai_utils.cmake) +include(cmake/project_cpp_standard.cmake) + +setup_project_options( + STANDARD 23 + INTERFACE_TARGET mirai_project_options +) + +add_subdirectory(src) +add_subdirectory(example) +add_subdirectory(tests) diff --git a/cmake/compiler_options.cmake b/cmake/compiler_options.cmake new file mode 100644 index 0000000..0c74df5 --- /dev/null +++ b/cmake/compiler_options.cmake @@ -0,0 +1,108 @@ +# ================================================================================================ +# MIRAI Framework - 编译器选项配置模块 +# 描述: 配置编译器特定选项、警告级别和优化设置 +# ================================================================================================ + +# ================================================================================================ +# 配置编译器选项 +# ================================================================================================ +function(configure_compiler_options) + # 使用生成器表达式为多配置生成器(如Visual Studio)正确配置选项 + if(MSVC) + add_compile_options( + /wd5054 # 禁用 C5054: 枚举之间的 & 运算符弃用警告 + /wd4324 # 禁用 C4324: 由于对齐说明符,结构被填充警告 + ) + + # Debug配置特定设置 + add_compile_options( + $<$:/Zi> # 调试信息 + $<$:/Od> # 禁用优化 + $<$:/RTC1> # 运行时检查 + $<$:/W4> # 严格警告 + ) + add_compile_definitions( + $<$:MIRAI_DEBUG_BUILD> + ) + + # Release配置特定设置 + add_compile_options( + $<$:/O2> # 最大优化 + $<$:/Ob2> # 内联展开 + $<$:/Zi> # 调试信息(用于调试Release版本) + ) + add_compile_definitions( + $<$:MIRAI_RELEASE_BUILD> + $<$:NDEBUG> + ) + + # RelWithDebInfo配置 + add_compile_options( + $<$:/O2> + $<$:/Ob1> + $<$:/Zi> + ) + add_compile_definitions( + $<$:MIRAI_RELEASE_BUILD> + $<$:NDEBUG> + ) + + # MinSizeRel配置 + add_compile_options( + $<$:/O1> + $<$:/Ob1> + $<$:/Zi> + ) + add_compile_definitions( + $<$:MIRAI_RELEASE_BUILD> + $<$:NDEBUG> + ) + + add_compile_definitions( + _ENABLE_EXTENDED_ALIGNED_STORAGE + ) + + message(STATUS "MSVC编译器选项已配置,支持多配置生成器") + else() + # 非MSVC编译器的传统配置 + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions(MIRAI_DEBUG_BUILD) + add_compile_options(-g -O0 -fno-omit-frame-pointer) + add_compile_options(-Wall -Wextra -Wpedantic) + message(STATUS "Debug模式: 启用调试符号,禁用优化,启用严格警告") + else() + add_compile_definitions(MIRAI_RELEASE_BUILD NDEBUG) + add_compile_options(-O3 -DNDEBUG -march=native) + message(STATUS "Release模式: 启用最高级别优化") + endif() + + # 启用更好的诊断信息 + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_compile_options(-fdiagnostics-color=always) + add_compile_options(-ftemplate-backtrace-limit=0) + message(STATUS "启用彩色诊断和完整模板回溯") + endif() + endif() + + if (MSVC) + add_compile_definitions(MIRAI_MSVC=1) + add_compile_definitions(MIRAI_GCC=0) + add_compile_definitions(MIRAI_CLANG=0) + elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + add_compile_definitions(MIRAI_MSVC=0) + add_compile_definitions(MIRAI_GCC=1) + add_compile_definitions(MIRAI_CLANG=0) + elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_definitions(MIRAI_MSVC=0) + add_compile_definitions(MIRAI_GCC=0) + add_compile_definitions(MIRAI_CLANG=1) + endif() +endfunction() + +# ================================================================================================ +# 应用编译器配置 +# ================================================================================================ +function(apply_compiler_configuration) + configure_compiler_options() + message(STATUS "编译器配置完成") +endfunction() \ No newline at end of file diff --git a/cmake/config_macos.cmake b/cmake/config_macos.cmake new file mode 100644 index 0000000..63a1c44 --- /dev/null +++ b/cmake/config_macos.cmake @@ -0,0 +1,41 @@ + +# 如果是Macos +if (APPLE) + # 获取 Homebrew 安装的 libomp 路径 + execute_process( + COMMAND brew --prefix libomp + OUTPUT_VARIABLE HOMEBREW_LIBOMP_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # 设置 OpenMP 路径变量 + set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PATH}/include") + set(OpenMP_CXX_LIB_NAMES "omp") + set(OpenMP_omp_LIBRARY "${HOMEBREW_LIBOMP_PATH}/lib/libomp.dylib") + enable_language(OBJC OBJCXX) + + # ============================================================================================ + # Apple Accelerate框架支持 (包含AMX向量指令集) + # ============================================================================================ + # 检测是否为Apple Silicon (ARM64) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + message(STATUS "Detected Apple Silicon (${CMAKE_SYSTEM_PROCESSOR})") + + # 查找Accelerate框架(macOS系统自带) + find_library(ACCELERATE_FRAMEWORK Accelerate) + if(ACCELERATE_FRAMEWORK) + message(STATUS "Found Accelerate framework: ${ACCELERATE_FRAMEWORK}") + # 设置全局变量供其他模块使用 + set(APPLE_ACCELERATE_LIBRARY ${ACCELERATE_FRAMEWORK} PARENT_SCOPE) + set(HAS_ACCELERATE TRUE PARENT_SCOPE) + # 启用Accelerate编译选项 + add_compile_definitions(MIRAI_ENABLE_ACCELERATE) + else() + message(WARNING "Accelerate framework not found on macOS") + set(HAS_ACCELERATE FALSE PARENT_SCOPE) + endif() + else() + message(STATUS "Not Apple Silicon, Accelerate framework disabled") + set(HAS_ACCELERATE FALSE PARENT_SCOPE) + endif() +endif () diff --git a/cmake/detect_os.cmake b/cmake/detect_os.cmake new file mode 100644 index 0000000..4df8c15 --- /dev/null +++ b/cmake/detect_os.cmake @@ -0,0 +1,205 @@ +# ============================================================================= +# 顶层平台检测变量 +# 这些变量可在整个项目中使用,用于条件编译和依赖选择 +# ============================================================================= + +# 桌面平台检测 +set(MIRAI_DESKTOP_PLATFORM OFF) +if(WIN32 OR (UNIX AND NOT ANDROID AND NOT IOS)) + set(MIRAI_DESKTOP_PLATFORM ON) +endif() + +# 移动平台检测 +set(MIRAI_MOBILE_PLATFORM OFF) +if(ANDROID OR IOS) + set(MIRAI_MOBILE_PLATFORM ON) +endif() + +# 各平台标志 +set(MIRAI_PLATFORM_WINDOWS OFF) +set(MIRAI_PLATFORM_LINUX OFF) +set(MIRAI_PLATFORM_MACOS OFF) +set(MIRAI_PLATFORM_ANDROID OFF) +set(MIRAI_PLATFORM_IOS OFF) + +if(WIN32) + set(MIRAI_PLATFORM_WINDOWS ON) +elseif(ANDROID) + set(MIRAI_PLATFORM_ANDROID ON) +elseif(IOS) + set(MIRAI_PLATFORM_IOS ON) +elseif(APPLE) + set(MIRAI_PLATFORM_MACOS ON) +elseif(UNIX) + set(MIRAI_PLATFORM_LINUX ON) +endif() + +# ============================================================================= +# 平台定义函数(为目标添加详细的平台宏) +# ============================================================================= + +# 定义一个函数,为指定的目标添加操作系统和架构相关的预处理器定义 +function(add_os_definitions target) + # 检查 target 参数是否提供 + if(NOT target) + message(FATAL_ERROR "函数 add_os_definitions 需要一个 target 参数。") + return() + endif() + + # --- 阶段 1: 确定宏的值 --- + + # 初始化所有平台、架构和特性宏的值为 0 + set(mirai_def_windows 0) + set(mirai_def_macos 0) + set(mirai_def_linux 0) + set(mirai_def_freebsd 0) + set(mirai_def_ios 0) + set(mirai_def_android 0) + set(mirai_def_cygwin 0) + set(mirai_def_unix 0) + set(mirai_def_posix 0) + set(mirai_def_mobile 0) + set(mirai_def_arch_64bit 0) + set(mirai_def_arch_32bit 0) + set(mirai_def_x86 0) + set(mirai_def_arm 0) + set(mirai_def_riscv 0) + set(mirai_def_apple 0) # 用于 iOS 和 macOS 的通用定义 + set(mirai_def_debug 0) # 当前是否处于调试模式 + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(mirai_def_debug 1) + endif () + + # -- 操作系统检测与赋值 -- + # 注意检测顺序:优先检测更具体的平台 + if(CYGWIN) + # Cygwin 环境比较特殊,它在 Windows 上模拟 Unix + set(mirai_def_windows 1) # 基础是 Windows + set(mirai_def_cygwin 1) # 明确是 Cygwin + set(mirai_def_unix 1) # 提供 Unix API + set(mirai_def_posix 1) # 提供 POSIX API +# message(STATUS "检测到 **Cygwin** 环境 (运行于 Windows)") + elseif(WIN32) + # 非 Cygwin 的 Windows 环境 (MSVC, MinGW, etc.) + set(mirai_def_windows 1) +# message(STATUS "检测到 **Windows** 操作系统 (非 Cygwin)") + elseif(ANDROID) + # Android 平台 (通常需要特定工具链设置 ANDROID 变量) + set(mirai_def_android 1) + set(mirai_def_unix 1) # Android NDK 基于 Unix + set(mirai_def_posix 1) # NDK 提供 POSIX API + set(mirai_def_mobile 1) # 移动平台 +# message(STATUS "检测到 **Android** 操作系统") + elseif(IOS) + # iOS 平台 (通常需要特定工具链设置 IOS 变量) + # 需要在 APPLE 之前判断,因为 iOS 下 APPLE 也为 TRUE + set(mirai_def_ios 1) + set(mirai_def_unix 1) # iOS (Darwin) 基于 Unix + set(mirai_def_posix 1) # 提供 POSIX API + set(mirai_def_mobile 1) # 移动平台 + set(mirai_def_apple 1) # iOS 是 Apple 生态的一部分 +# message(STATUS "检测到 **iOS** 操作系统") + elseif(APPLE) + # 此时排除了 iOS,确定是 macOS + set(mirai_def_macos 1) + set(mirai_def_unix 1) # macOS (Darwin) 基于 Unix + set(mirai_def_posix 1) # 提供 POSIX API + set(mirai_def_apple 1) # macOS 是 Apple 生态的一部分 + message(STATUS "检测到 **macOS** 操作系统") + elseif(UNIX) + # 此时排除了 Apple, Android, Cygwin 的其他 Unix-like 系统 + set(mirai_def_unix 1) + set(mirai_def_posix 1) + if(CMAKE_SYSTEM_NAME MATCHES "Linux") + set(mirai_def_linux 1) +# message(STATUS "检测到 **Linux** 操作系统") + elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(mirai_def_freebsd 1) +# message(STATUS "检测到 **FreeBSD** 操作系统") + else() + message(WARNING "检测到未知的 类Unix 操作系统: ${CMAKE_SYSTEM_NAME}") + endif() + else() + message(WARNING "检测到未知的操作系统: ${CMAKE_SYSTEM_NAME}") + endif() + + # -- 架构检测与赋值 -- + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(mirai_def_arch_64bit 1) + set(mirai_def_arch_32bit 0) # 明确设置为 0 +# message(STATUS "检测到 **64-bit** 架构") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(mirai_def_arch_64bit 0) # 明确设置为 0 + set(mirai_def_arch_32bit 1) +# message(STATUS "检测到 **32-bit** 架构") + else() + # 对于未知或未定义的指针大小,两者都保持 0 + message(WARNING "无法明确检测到 32-bit 或 64-bit 架构 (CMAKE_SIZEOF_VOID_P = ${CMAKE_SIZEOF_VOID_P})。将两者都设置为 0。") + endif() + + # 检测特定架构类型 + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86|i386|i486|i586|i686|i786|x86_64|AMD64") + set(mirai_def_x86 1) +# message(STATUS "检测到 **x86/x64** 架构") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|aarch64|ARM64") + set(mirai_def_arm 1) +# message(STATUS "检测到 **ARM** 架构") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "riscv|riscv64|riscv32") + set(mirai_def_riscv 1) +# message(STATUS "检测到 **RISC-V** 架构") + endif() + + # --- 阶段 2: 组装定义列表 --- + set(definitions_list "") # 初始化空列表 + + # 添加平台定义 + list(APPEND definitions_list "MIRAI_PLATFORM_WINDOWS=${mirai_def_windows}") + list(APPEND definitions_list "MIRAI_PLATFORM_MACOS=${mirai_def_macos}") + list(APPEND definitions_list "MIRAI_PLATFORM_LINUX=${mirai_def_linux}") + list(APPEND definitions_list "MIRAI_PLATFORM_FREEBSD=${mirai_def_freebsd}") + list(APPEND definitions_list "MIRAI_PLATFORM_IOS=${mirai_def_ios}") + list(APPEND definitions_list "MIRAI_PLATFORM_ANDROID=${mirai_def_android}") + list(APPEND definitions_list "MIRAI_PLATFORM_CYGWIN=${mirai_def_cygwin}") + list(APPEND definitions_list "MIRAI_PLATFORM_APPLE=${mirai_def_apple}") # 用于 iOS 和 macOS 的通用定义 + + # 添加架构定义 + list(APPEND definitions_list "MIRAI_PLATFORM_ARCH_64BIT=${mirai_def_arch_64bit}") + list(APPEND definitions_list "MIRAI_PLATFORM_ARCH_32BIT=${mirai_def_arch_32bit}") + + # 添加特性定义 + list(APPEND definitions_list "MIRAI_PLATFORM_UNIX=${mirai_def_unix}") + list(APPEND definitions_list "MIRAI_PLATFORM_POSIX=${mirai_def_posix}") + list(APPEND definitions_list "MIRAI_PLATFORM_IS_MOBILE=${mirai_def_mobile}") + + list(APPEND definitions_list "MIRAI_DEBUG=${mirai_def_debug}") # 当前是否处于调试模式 + + list(APPEND definitions_list "MIRAI_PLATFORM_X86=${mirai_def_x86}") + list(APPEND definitions_list "MIRAI_PLATFORM_ARM=${mirai_def_arm}") + list(APPEND definitions_list "MIRAI_PLATFORM_RISCV=${mirai_def_riscv}") + + # --- 阶段 3: 应用所有定义 --- + # **关键:使用一次调用将所有定义添加到目标** + if(definitions_list) # 确保列表非空 + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "INTERFACE_LIBRARY") + target_compile_definitions(${target} INTERFACE ${definitions_list}) + else() + target_compile_definitions(${target} PUBLIC ${definitions_list}) + endif() + endif() + + # 函数作用域结束时,mirai_def_* 变量会自动销毁,无需显式 unset + +endfunction() + +# --- 使用示例 --- +# project(MyProject) +# add_executable(my_app main.c) +# +# # 调用函数为 my_app 添加平台定义 +# add_os_definitions(my_app) +# +# # 你也可以为库调用 +# # add_library(my_lib STATIC my_lib.c) +# # add_os_definitions(my_lib) diff --git a/cmake/mingw_dll.cmake b/cmake/mingw_dll.cmake new file mode 100644 index 0000000..da09762 --- /dev/null +++ b/cmake/mingw_dll.cmake @@ -0,0 +1,41 @@ + +function(auto_copy_mingw_dll target) + if(MINGW) + # 获取MinGW目录 + get_filename_component(MINGW_DIR ${CMAKE_CXX_COMPILER} PATH) + + # 根据你的环境调整DLL列表 + set(MINGW_DLLS + "libstdc++-6.dll" +# "libgcc_s_dw2-1.dll" + "libgcc_s_seh-1.dll" + "libwinpthread-1.dll" + "libbz2-1.dll" + "libbrotlidec.dll" + "libbrotlicommon.dll" + "libharfbuzz-0.dll" + "libpng16-16.dll" + "zlib1.dll" + "libglib-2.0-0.dll" + "libfreetype-6.dll" + "libgraphite2.dll" + "libintl-8.dll" + "libpcre2-8-0.dll" + "libiconv-2.dll" + ) + + foreach(DLL ${MINGW_DLLS}) + # 检查文件是否存在 + if(EXISTS "${MINGW_DIR}/${DLL}") + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${MINGW_DIR}/${DLL}" + "$" + COMMENT "Copying ${DLL} to output directory" + VERBATIM) + else() + message(WARNING "DLL not found: ${MINGW_DIR}/${DLL}") + endif() + endforeach() + endif() +endfunction() \ No newline at end of file diff --git a/cmake/mirai_utils.cmake b/cmake/mirai_utils.cmake new file mode 100644 index 0000000..f00fa6a --- /dev/null +++ b/cmake/mirai_utils.cmake @@ -0,0 +1,44 @@ + +# 定义一个函数来配置项目的默认设置 +# 这包括设置输出目录和项目根目录变量 +function(configure_project_defaults) + # 检查是否在顶层 CMakeLists.txt 中调用 (可选但推荐) + # 确保 CMAKE_SOURCE_DIR 和 CMAKE_CURRENT_SOURCE_DIR 相同 + if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + message(WARNING "configure_project_defaults() 应该在项目的根 CMakeLists.txt 中调用。") + # 如果您确实需要在子目录中设置不同的根目录,请调整此逻辑 + endif() + + # --- 配置输出目录 --- + # 使用 CMAKE_BINARY_DIR 作为基础构建目录 + # ${CMAKE_BINARY_DIR} 指向您配置 CMake 时指定的构建目录 + # 例如,在 CLion 中通常是 cmake-build-debug 或 cmake-build-release + # 如果手动运行 cmake ..,它就是您运行 cmake 命令的目录 + + # **设置可执行文件输出路径**: + # 对于单配置生成器 (如 Makefiles, Ninja), 可执行文件将位于 /bin/ + # 对于多配置生成器 (如 Visual Studio, Xcode), CMake 通常会自动在此路径下附加配置名称 + # (例如 /bin/Debug/, /bin/Release/) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Directory for runtime executables") + message(STATUS "运行时输出目录设置为: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + + # **设置库文件输出路径 (共享库和静态库)**: + # 规则同上,库文件将位于 /lib/ 或 /lib// + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for shared libraries") + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for static libraries") + message(STATUS "库输出目录设置为: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + message(STATUS "存档输出目录设置为: ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}") + + # --- 提示 --- + # 这种全局设置输出目录的方法对于中小型项目是常见的。 + # 对于更复杂的项目或需要更细粒度控制的情况,可以考虑为每个目标(target)单独设置输出目录属性: + # 例如: + # add_executable(my_app main.cpp) + # set_target_properties(my_app PROPERTIES + # RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/executables" + # ) + # add_library(my_lib STATIC my_lib.cpp) + # set_target_properties(my_lib PROPERTIES + # ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_libs" + # ) +endfunction() diff --git a/cmake/project_cpp_standard.cmake b/cmake/project_cpp_standard.cmake new file mode 100644 index 0000000..aef2c1a --- /dev/null +++ b/cmake/project_cpp_standard.cmake @@ -0,0 +1,118 @@ +# 文件: cmake/CompilerSetup.cmake + +# ============================================================================== +# 函数:setup_project_options +# 描述:配置项目级的 C++ 标准、编译器警告、定义和依赖。 +# 此函数遵循现代 CMake 实践,将所有配置封装到一个 INTERFACE 库中。 +# +# 参数: +# standard - (必选) C++ 标准版本 (例如 17, 20, 23)。 +# INTERFACE_TARGET - (必选) 用于接收创建的 INTERFACE 库名称的变量名。 +# +# 用法: +# include(cmake/CompilerSetup.cmake) +# setup_project_options( +# STANDARD 20 +# INTERFACE_TARGET my_project_options +# ) +# # ... 定义你的可执行文件或库 +# add_executable(my_app main.cpp) +# # ... 将配置应用到目标上 +# target_link_libraries(my_app PRIVATE ${my_project_options}) +# ============================================================================== +function(setup_project_options) + # --- 参数解析 --- + set(options "") # 无单值选项 + set(oneValueArgs STANDARD INTERFACE_TARGET) # 定义接收单个值的参数 + set(multiValueArgs "") # 无多值选项 + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # --- 参数验证 --- + if(NOT ARG_STANDARD OR NOT ARG_INTERFACE_TARGET) + message(FATAL_ERROR "setup_project_options 必须提供 STANDARD 和 INTERFACE_TARGET 参数。") + endif() + + set(VALID_STANDARDS 11 14 17 20 23) + list(FIND VALID_STANDARDS ${ARG_STANDARD} _standard_index) + if(_standard_index EQUAL -1) + message(FATAL_ERROR "不支持的 C++ 标准: ${ARG_STANDARD}。有效值: ${VALID_STANDARDS}") + endif() + + # --- 创建 INTERFACE 库 --- + # 这是现代 CMake 的核心:创建一个虚拟目标来承载所有配置属性。 + add_library(${ARG_INTERFACE_TARGET} INTERFACE) + message(STATUS "创建配置接口库: ${ARG_INTERFACE_TARGET}") + + # --- 设置 C++ 标准 (应用到接口库) --- + target_compile_features(${ARG_INTERFACE_TARGET} INTERFACE cxx_std_${ARG_STANDARD}) + + # --- 设置通用编译定义和选项 --- + # 使用 target_compile_definitions 和 target_compile_options,并指定 INTERFACE + # 这样任何链接到此库的目标都会继承这些属性。 + + # --- 平台特定设置 --- + if(WIN32) + target_compile_definitions(${ARG_INTERFACE_TARGET} INTERFACE UNICODE _UNICODE) + message(STATUS "为 Windows 添加 UNICODE 定义") + endif() + + # --- 编译器特定设置 --- + if(MSVC) + # MSVC 特定选项 + target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE + /W4 # 更高警告等级 + # /WX # 将警告视为错误 (可选,但推荐) + /EHsc + /utf-8 # 源码和执行字符集设为 UTF-8 + /Zc:__cplusplus # 修正 __cplusplus 宏 + /wd4100 # 禁用警告: 未使用的形参 + /wd4996 # 禁用警告: 使用了被标记为否决的函数 + ) + message(STATUS "为 MSVC 添加特定编译选项") + else() # GCC / Clang / AppleClang + # 通用于 GCC 和 Clang 的选项 + target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE + -Wall + -Wextra + -Wpedantic # 更加严格的警告 + -Werror # 将所有警告视为错误 (可选,但推荐) + -Wno-unused-parameter + ) + + # C++17 及以上标准的额外警告 + if(${ARG_STANDARD} GREATER_EQUAL 17) + target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE + -Wshadow + -Wnon-virtual-dtor + ) + endif() + + # 【核心修复】区分处理 AppleClang 和标准 Clang/GCC + # AppleClang 不支持 -finput-charset/-fexec-charset,并默认源码为 UTF-8 + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") + target_compile_options(${ARG_INTERFACE_TARGET} INTERFACE + -finput-charset=UTF-8 + -fexec-charset=UTF-8 + ) + message(STATUS "为 GCC/Clang 添加 UTF-8 字符集选项") + else() + message(STATUS "检测到 AppleClang,源码假定为 UTF-8,跳过字符集选项") + endif() + message(STATUS "为 GCC/Clang 添加特定编译选项") + endif() + + # --- MinGW 特定设置 --- + if(MINGW) + # 为 C++17 及以上的 支持添加链接库 + if(${ARG_STANDARD} GREATER_EQUAL 17) + # 使用 target_link_libraries,这才是正确的方式 + target_link_libraries(${ARG_INTERFACE_TARGET} INTERFACE -lstdc++fs) + message(STATUS "为 MinGW C++${ARG_STANDARD} 添加 libstdc++fs 依赖 (用于 )") + endif() + endif() + + # --- 将 INTERFACE 库的名称返回给调用者 --- + set(${ARG_INTERFACE_TARGET} ${ARG_INTERFACE_TARGET} PARENT_SCOPE) + message(STATUS "C++${ARG_STANDARD} 项目配置完成,请链接到 ${ARG_INTERFACE_TARGET} 目标。") + +endfunction() diff --git a/cmake/retrieve_files.cmake b/cmake/retrieve_files.cmake new file mode 100644 index 0000000..2404966 --- /dev/null +++ b/cmake/retrieve_files.cmake @@ -0,0 +1,617 @@ +#[=======================================================================[ + 平台匹配检查函数 + 参数: + platform: 平台标识符 (windows|linux|mac|mobile|desktop) + is_match: 输出变量,表示当前平台是否匹配 +#]=======================================================================] +function(is_current_platform platform is_match) + # 设置默认值为TRUE(用于未知平台) + set(matches FALSE) + + if(platform STREQUAL "windows") + if(WIN32 OR CYGWIN) + set(matches TRUE) + endif() + elseif(platform STREQUAL "linux") + if(UNIX AND NOT APPLE) + set(matches TRUE) + endif() + elseif(platform STREQUAL "macos") + if(APPLE AND NOT IOS) + set(matches TRUE) + endif() + elseif(platform STREQUAL "ios") + if(IOS) + set(matches TRUE) + endif() + elseif(platform STREQUAL "android") + if(ANDROID) + set(matches TRUE) + endif() + # 添加对unix平台的支持 + elseif(platform STREQUAL "unix") + if(UNIX) + set(matches TRUE) + endif() + elseif(platform STREQUAL "mobile") + if(ANDROID OR IOS) + set(matches TRUE) + endif() + elseif(platform STREQUAL "desktop") + if(WIN32 OR (UNIX AND NOT APPLE) OR (APPLE AND NOT IOS)) + set(matches TRUE) + endif() + elseif(platform STREQUAL "web") + if(EMSCRIPTEN) + set(matches TRUE) + endif() + else() + # 未知平台标识,默认匹配 + set(matches TRUE) + endif() + + set(${is_match} ${matches} PARENT_SCOPE) +endfunction() + +#[=======================================================================[ + 主文件检索函数 + 参数: + path: 要检索的根目录路径 + extension: 文件扩展名列表 + out_files: 输出变量名,将包含筛选后的文件列表 + out_cmake_files: (可选) 输出变量名,将包含找到的 .cmake 文件列表 +#]=======================================================================] +function(retrieve_files_custom path extension out_files) + # 1. 参数验证 + if(NOT IS_DIRECTORY "${path}") + message(WARNING "错误:目录 '${path}' 不存在") + return() + endif() + +# message(STATUS "正在检索目录: ${path}") + + # 2. 构建文件匹配模式 + set(file_patterns "") + foreach(ext IN LISTS extension) + list(APPEND file_patterns "${path}/*.${ext}") + endforeach() + + # 3. 递归查找所有匹配的文件 + file(GLOB_RECURSE found_files + RELATIVE ${path} + CONFIGURE_DEPENDS ${file_patterns} + ) + + # 4. 处理找到的文件 + set(filtered_files "") + set(cmake_files_to_include "") + foreach(current_file IN LISTS found_files) + # 4.1 获取文件所在目录 + get_filename_component(file_dir "${current_file}" DIRECTORY) + string(REPLACE "/" ";" dir_components "${file_dir}") + + # 4.2 检查平台兼容性 + set(should_skip_file FALSE) + set(found_platform_dir FALSE) + + foreach(dir_name IN LISTS dir_components) + # 检查是否是平台相关目录 + if(dir_name MATCHES "^(windows|linux|macos|ios|android|unix|mobile|desktop|web)$") + set(found_platform_dir TRUE) + is_current_platform(${dir_name} platform_matches) + if(NOT platform_matches) + set(should_skip_file TRUE) + break() + endif() + endif() + endforeach() + + # 如果文件需要跳过,继续处理下一个文件 + if(should_skip_file) + continue() + endif() + + # 4.3 添加符合条件的文件 + list(APPEND filtered_files "${current_file}") + + # 4.3.1 自动 include .cmake 文件 + get_filename_component(file_extension "${current_file}" LAST_EXT) + if(file_extension STREQUAL ".cmake") + get_filename_component(cmake_file_abs "${current_file}" ABSOLUTE) + list(APPEND cmake_files_to_include "${cmake_file_abs}") + endif() + + # 4.4 设置IDE文件分组 + # 计算相对路径作为分组名称 + get_filename_component(root_abs_path "${path}" ABSOLUTE) + get_filename_component(file_dir_abs_path "${file_dir}" ABSOLUTE) + file(RELATIVE_PATH group_path "${root_abs_path}" "${file_dir_abs_path}") + + # 处理根目录的特殊情况 + if(group_path STREQUAL ".") + set(group_name "") + else() + string(REPLACE "/" "\\" group_name "${group_path}") + endif() + + # 创建IDE分组 + source_group("${group_name}" FILES "${current_file}") + endforeach() + + # 5. 设置输出变量 + set(${out_files} ${filtered_files} PARENT_SCOPE) + # 将找到的 .cmake 文件传递给父作用域 + set(RETRIEVED_CMAKE_FILES ${cmake_files_to_include} PARENT_SCOPE) +endfunction() + +#[=======================================================================[ + 便捷封装函数 + 自动处理常见文件扩展名,针对不同平台添加特定文件类型 +#]=======================================================================] +function(retrieve_files path out_files) + # 设置基础文件类型 + set(file_extensions + "h" # 头文件 + "hpp" # C++头文件 + "ini" # 配置文件 + "cpp" # C++源文件 + "c" # C源文件 + "cc" + "ixx" # C++20模块文件 + "cmake" # CMake脚本文件 + ) + + # 针对Mac平台添加额外文件类型 + if(APPLE) + list(APPEND file_extensions "mm") # Objective-C++源文件 + endif() + + # 执行文件检索 + set(temp_files "") + retrieve_files_custom(${path} "${file_extensions}" temp_files) + + # 合并结果到输出变量 + set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE) + # 将 .cmake 文件列表传递到父作用域 + set(RETRIEVED_CMAKE_FILES ${RETRIEVED_CMAKE_FILES} PARENT_SCOPE) +endfunction() + +#[=======================================================================[ + Proto文件编译和gRPC绑定生成函数 + 参数: + TARGET_NAME: (必需) - 要创建的库目标名称 + PROTO_PATH: (必需) - 包含.proto文件的目录路径 + OUTPUT_PATH: (可选) - 生成文件的输出目录,默认为${CMAKE_CURRENT_BINARY_DIR}/generated + GRPC_ENABLED: (可选) - 是否生成gRPC绑定,默认为TRUE + PROTO_IMPORT_DIRS:(可选) - proto文件的额外导入目录列表 + EXPORT_MACRO: (可选) - 导出宏定义(用于Windows DLL导出) + + 例子: + compile_proto_files( + TARGET_NAME my_proto_lib + PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/protos + OUTPUT_PATH ${CMAKE_BINARY_DIR}/proto_gen + GRPC_ENABLED TRUE + PROTO_IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/protos + ) +#]=======================================================================] +function(compile_proto_files) + # 定义预期的参数 + set(options GRPC_ENABLED) + set(oneValueArgs TARGET_NAME PROTO_PATH OUTPUT_PATH EXPORT_MACRO) + set(multiValueArgs PROTO_IMPORT_DIRS) + + # 解析传递给函数的参数 + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # 参数验证 + if(NOT ARG_TARGET_NAME) + message(FATAL_ERROR "**compile_proto_files**: **缺少必需参数** **TARGET_NAME**.") + endif() + if(NOT ARG_PROTO_PATH) + message(FATAL_ERROR "**compile_proto_files**: **缺少必需参数** **PROTO_PATH**.") + endif() + + # 设置默认值 + if(NOT DEFINED ARG_GRPC_ENABLED) + set(ARG_GRPC_ENABLED TRUE) + endif() + if(NOT ARG_OUTPUT_PATH) + set(ARG_OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/generated") + endif() + + # 查找Protobuf和gRPC + find_package(Protobuf CONFIG REQUIRED) + if(ARG_GRPC_ENABLED) + find_package(gRPC QUIET) + if(NOT gRPC_FOUND) + # 如果找不到gRPC包,尝试手动查找 + find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin) + if(NOT GRPC_CPP_PLUGIN) + message(FATAL_ERROR "**compile_proto_files**: **找不到gRPC C++插件**. 请确保已安装gRPC.") + endif() + else() + set(GRPC_CPP_PLUGIN $) + endif() + endif() + + # 创建输出目录 + file(MAKE_DIRECTORY ${ARG_OUTPUT_PATH}) + + get_filename_component(PROTO_PATH "${ARG_PROTO_PATH}" ABSOLUTE) + + # 递归查找所有.proto文件 + file(GLOB_RECURSE PROTO_FILES + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + CONFIGURE_DEPENDS + "${PROTO_PATH}/*.proto" + ) + + if(NOT PROTO_FILES) + message(WARNING "**compile_proto_files**: 在 '${PROTO_PATH}' 中未找到任何.proto文件") + return() + endif() + + message(STATUS "找到 ${CMAKE_CURRENT_SOURCE_DIR} Proto文件: ${PROTO_FILES}") + + # 准备生成的文件列表 + set(PROTO_SRCS) + set(PROTO_HDRS) + set(GRPC_SRCS) + set(GRPC_HDRS) + + # 构建导入路径参数 + set(PROTO_IMPORT_ARGS) + list(APPEND PROTO_IMPORT_ARGS "-I${PROTO_PATH}") + foreach(IMPORT_DIR ${ARG_PROTO_IMPORT_DIRS}) + list(APPEND PROTO_IMPORT_ARGS "-I${IMPORT_DIR}") + endforeach() + + # 添加一个自定义命令用于生成前清空目标文件夹中的pb.cc和pb.h文件 + add_custom_command( + OUTPUT ${ARG_OUTPUT_PATH}/.cleaned + COMMAND ${CMAKE_COMMAND} -E rm -f ${ARG_OUTPUT_PATH}/*.pb.cc ${ARG_OUTPUT_PATH}/*.pb.h ${ARG_OUTPUT_PATH}/*.grpc.pb.cc ${ARG_OUTPUT_PATH}/*.grpc.pb.h + COMMAND ${CMAKE_COMMAND} -E touch ${ARG_OUTPUT_PATH}/.cleaned + COMMENT "清理旧的生成文件: ${ARG_OUTPUT_PATH}" + VERBATIM + ) + + # 为每个proto文件生成代码 + foreach(PROTO_FILE ${PROTO_FILES}) + # 获取proto文件的绝对路径 + get_filename_component(PROTO_FILE_ABS "${PROTO_FILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + get_filename_component(PROTO_NAME_WE "${PROTO_FILE}" NAME_WE) + get_filename_component(PROTO_DIR "${PROTO_FILE}" DIRECTORY) + + # 计算相对路径以保持目录结构 + if(PROTO_DIR) + file(RELATIVE_PATH REL_DIR "${PROTO_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/${PROTO_DIR}") + set(OUTPUT_SUBDIR "${ARG_OUTPUT_PATH}/${REL_DIR}") + else() + set(OUTPUT_SUBDIR "${ARG_OUTPUT_PATH}") + endif() + + # 创建输出子目录 + file(MAKE_DIRECTORY ${OUTPUT_SUBDIR}) + + # 生成的文件路径 + set(PROTO_SRC "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.pb.cc") + set(PROTO_HDR "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.pb.h") + list(APPEND PROTO_SRCS ${PROTO_SRC}) + list(APPEND PROTO_HDRS ${PROTO_HDR}) + + # 基础protobuf生成命令 + set(PROTOC_ARGS + ${PROTO_IMPORT_ARGS} + "--cpp_out=${ARG_OUTPUT_PATH}" + "${PROTO_FILE_ABS}" + ) + + if(ARG_GRPC_ENABLED) + set(GRPC_SRC "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.grpc.pb.cc") + set(GRPC_HDR "${OUTPUT_SUBDIR}/${PROTO_NAME_WE}.grpc.pb.h") + list(APPEND GRPC_SRCS ${GRPC_SRC}) + list(APPEND GRPC_HDRS ${GRPC_HDR}) + + # 添加自定义命令生成protobuf和gRPC代码 + add_custom_command( + OUTPUT ${PROTO_SRC} ${PROTO_HDR} ${GRPC_SRC} ${GRPC_HDR} + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + ${PROTOC_ARGS} + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + ${PROTO_IMPORT_ARGS} + "--grpc_out=${ARG_OUTPUT_PATH}" + "--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}" + "${PROTO_FILE_ABS}" + DEPENDS ${PROTO_FILE_ABS} + COMMENT "生成Protobuf和gRPC代码: ${PROTO_FILE}" + VERBATIM + ) + else() + # 只生成protobuf代码 + add_custom_command( + OUTPUT ${PROTO_SRC} ${PROTO_HDR} + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + ${PROTOC_ARGS} + DEPENDS ${PROTO_FILE_ABS} + COMMENT "生成Protobuf代码: ${PROTO_FILE}" + VERBATIM + ) + endif() + endforeach() + + # 创建库目标 + add_library(${ARG_TARGET_NAME} STATIC + ${PROTO_SRCS} + ${PROTO_HDRS} + ${GRPC_SRCS} + ${GRPC_HDRS} + ) + + # 设置包含目录 + target_include_directories(${ARG_TARGET_NAME} + PUBLIC + $ + $ + ) + + # 链接必要的库 + target_link_libraries(${ARG_TARGET_NAME} + PUBLIC + protobuf::libprotobuf + ) + + if(ARG_GRPC_ENABLED) + if(gRPC_FOUND) + target_link_libraries(${ARG_TARGET_NAME} + PUBLIC + gRPC::grpc++ + gRPC::grpc++_reflection + ) + else() + # 手动查找并链接gRPC库 + find_library(GRPC_LIBRARY grpc++) + find_library(GRPC_REFLECTION_LIBRARY grpc++_reflection) + if(GRPC_LIBRARY AND GRPC_REFLECTION_LIBRARY) + target_link_libraries(${ARG_TARGET_NAME} + PUBLIC + ${GRPC_LIBRARY} + ${GRPC_REFLECTION_LIBRARY} + ) + else() + message(WARNING "**compile_proto_files**: 无法找到gRPC库,请手动链接") + endif() + endif() + endif() + + # 设置导出宏(如果提供) + if(ARG_EXPORT_MACRO) + target_compile_definitions(${ARG_TARGET_NAME} + PRIVATE ${ARG_EXPORT_MACRO}_EXPORTS + INTERFACE ${ARG_EXPORT_MACRO}_IMPORTS + ) + endif() + + # 设置C++标准 + target_compile_features(${ARG_TARGET_NAME} PUBLIC cxx_std_11) + + # IDE文件分组 + source_group("Proto Files" FILES ${PROTO_FILES}) + source_group("Generated Files\\Protobuf" FILES ${PROTO_SRCS} ${PROTO_HDRS}) + if(ARG_GRPC_ENABLED) + source_group("Generated Files\\gRPC" FILES ${GRPC_SRCS} ${GRPC_HDRS}) + endif() + + # 输出信息 + message(STATUS "创建Proto库目标: ${ARG_TARGET_NAME}") + message(STATUS " Proto文件数量: ${CMAKE_CURRENT_SOURCE_DIR} list length: ${PROTO_FILES}") + message(STATUS " 输出目录: ${ARG_OUTPUT_PATH}") + message(STATUS " gRPC支持: ${ARG_GRPC_ENABLED}") +endfunction() + +#[=======================================================================[ +# 用于添加资源文件并在编译后复制到最终可执行文件所在目录 +# 注意:此函数依赖于 CMAKE_RUNTIME_OUTPUT_DIRECTORY 或 EXECUTABLE_OUTPUT_PATH +# 变量的设置,以确定可执行文件的输出目录。请确保在项目中设置了其中之一。 +# +# 参数: +# TARGET_NAME: (必需) - 关联的目标 (库或可执行文件) 的名称。 +# 资源复制命令将在 TARGET_NAME 构建后执行。 +# RESOURCE_FILES: (必需) - 一个或多个要复制的资源文件的路径列表 (相对或绝对) +# OUTPUT_SUBDIR: (可选) - 相对于可执行文件输出目录的子目录路径 (例如 "assets") +# +# 例子: +# # 确保设置了可执行文件输出目录 (通常在顶层 CMakeLists.txt) +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +# +# # 添加库 +# add_library(my_lib STATIC src/my_lib.cpp) +# +# # 添加资源到 my_lib,但复制到最终可执行文件的输出目录下的 'config' 子目录 +# add_resource_file( +# TARGET_NAME my_lib +# RESOURCE_FILES config/settings.json config/defaults.ini +# OUTPUT_SUBDIR config +# ) +# +# # 添加可执行文件 +# add_executable(my_app main.cpp) +# target_link_libraries(my_app PRIVATE my_lib) +# +# # 添加 my_app 的资源,复制到可执行文件输出目录的根目录 +# add_resource_file( +# TARGET_NAME my_app +# RESOURCE_FILES assets/icon.png +# ) +#]=======================================================================] +function(add_resource_file) + # 定义预期的参数 + set(options "") # 无布尔选项 + set(oneValueArgs TARGET_NAME OUTPUT_SUBDIR) + set(multiValueArgs RESOURCE_FILES) + + # 解析传递给函数的参数 + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # --- 参数验证 --- + if(NOT ARG_TARGET_NAME) + message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **TARGET_NAME**.") + endif() + if(NOT ARG_RESOURCE_FILES) + message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **RESOURCE_FILES**.") + endif() + if(NOT TARGET ${ARG_TARGET_NAME}) + message(WARNING "**add_resource_file**: 目标 '${ARG_TARGET_NAME}' (尚)不存在。请确保在调用 add_executable/add_library('${ARG_TARGET_NAME}') 之后调用此函数。") + # 即使目标尚不存在,仍然尝试配置命令。CMake通常能处理好依赖关系。 + endif() + + # --- 确定最终可执行文件的目标基础目录 --- + set(DESTINATION_BASE "") + if(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(DESTINATION_BASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + elseif(DEFINED EXECUTABLE_OUTPUT_PATH AND EXECUTABLE_OUTPUT_PATH) + # EXECUTABLE_OUTPUT_PATH 是旧变量,但也检查一下 + set(DESTINATION_BASE "${EXECUTABLE_OUTPUT_PATH}") + else() + # 如果是多配置生成器(如 Visual Studio, Xcode),需要考虑配置类型 + get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config) + # 对于多配置,没有单一的顶级运行时目录变量。 + # 可以考虑使用 $ 配合一个已知的可执行文件名,但这会使函数复杂化。 + # 最好的做法是要求用户设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_ + # 或者我们直接报错,强制用户设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY + message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。对于多配置生成器,可能需要设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_ 变量。") + else() + # 对于单配置生成器(如 Makefiles, Ninja),可以默认到 CMAKE_BINARY_DIR + set(DESTINATION_BASE "${CMAKE_BINARY_DIR}") + message(WARNING "**add_resource_file**: **未设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY**。默认将资源复制到 CMAKE_BINARY_DIR ('${CMAKE_BINARY_DIR}')。强烈建议设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY 以获得可预测的行为。") + endif() + # message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。") + endif() + + # 处理子目录 + set(DESTINATION_DIR "${DESTINATION_BASE}") # 默认目标目录 + if(ARG_OUTPUT_SUBDIR) + # 清理子目录路径字符串 + string(STRIP "${ARG_OUTPUT_SUBDIR}" _subdir) + if(IS_ABSOLUTE "${_subdir}") + message(FATAL_ERROR "**add_resource_file**: **OUTPUT_SUBDIR** ('${ARG_OUTPUT_SUBDIR}') **必须是相对路径**。") + else() + # 移除可能存在的前导/后导斜杠,以便干净地拼接路径 + string(REGEX REPLACE "^[/\\\\]+" "" _subdir "${_subdir}") + string(REGEX REPLACE "[/\\\\]+$" "" _subdir "${_subdir}") + if(_subdir) # 仅当子目录清理后非空时才追加 + set(DESTINATION_DIR "${DESTINATION_BASE}/${_subdir}") + endif() + endif() + endif() + + # --- 准备源文件路径 --- + set(ABS_RESOURCE_FILES "") + foreach(RESOURCE_FILE ${ARG_RESOURCE_FILES}) + get_filename_component(RESOURCE_FILE_REALPATH "${RESOURCE_FILE}" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + # if(IS_ABSOLUTE "${RESOURCE_FILE}") + # # 如果已经是绝对路径,直接使用 + # list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE}") + # else() + # # 如果是相对路径,相对于当前源目录进行解析 + # list(APPEND ABS_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE}") + # endif() + list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE_REALPATH}") + + # 检查文件是否存在 (在配置时发出警告,有助于早期发现错误) + # list(GET ABS_RESOURCE_FILES -1 _current_abs_file) # 获取刚才添加的绝对路径 + if(NOT EXISTS "${RESOURCE_FILE_REALPATH}") + message(WARNING "**add_resource_file**: **资源文件** '${RESOURCE_FILE}' (解析为 '${RESOURCE_FILE_REALPATH}') **在配置时不存在**。") + endif() + endforeach() + + # --- 添加自定义命令 --- + # 使用 add_custom_command 在目标构建完成后执行复制操作 + if(ABS_RESOURCE_FILES) # 确保有文件需要复制 + # 注意:DESTINATION_DIR 可能包含特定于配置的路径(例如,如果 CMAKE_RUNTIME_OUTPUT_DIRECTORY + # 设置为 ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)。 + # add_custom_command 的 COMMAND 参数在构建时执行,此时这些变量/生成器表达式已解析。 + add_custom_command( + TARGET ${ARG_TARGET_NAME} + POST_BUILD # 指定在目标构建之后执行 + # 步骤 1: 确保目标目录存在 (copy_if_different 不会创建目录) + COMMAND ${CMAKE_COMMAND} -E make_directory "${DESTINATION_DIR}" + # 步骤 2: 复制文件 + COMMAND ${CMAKE_COMMAND} -E copy_if_different # 使用CMake内置命令复制(仅当文件不同时) + ${ABS_RESOURCE_FILES} # 要复制的源文件列表(绝对路径) + "${DESTINATION_DIR}" # 最终可执行文件所在的目标目录 (带引号以处理空格) + COMMENT "为 ${ARG_TARGET_NAME} 将资源复制到可执行文件目录: ${DESTINATION_DIR}..." # 构建时显示的注释 + VERBATIM # 确保参数(尤其是路径和生成器表达式)被正确处理 + ) + else() + message(WARNING "**add_resource_file**: 没有有效的资源文件提供给目标 '${ARG_TARGET_NAME}'。") + endif() + + # --- 可选: 将资源文件添加到 IDE 项目结构中 --- + if(ABS_RESOURCE_FILES) + set(_source_group_name "Resource Files") # 基础组名 + if(ARG_OUTPUT_SUBDIR) + # 使用与目标目录结构匹配的组名 + string(STRIP "${ARG_OUTPUT_SUBDIR}" _clean_subdir) + string(REPLACE "\\" "/" _clean_subdir "${_clean_subdir}") # 统一使用正斜杠 + string(REGEX REPLACE "^[/]+" "" _clean_subdir "${_clean_subdir}") + string(REGEX REPLACE "[/]+$" "" _clean_subdir "${_clean_subdir}") + if(_clean_subdir) + set(_source_group_name "Resource Files/${_clean_subdir}") + endif() + endif() + # 使用 source_group 将文件添加到 IDE 的指定组下 + source_group(${_source_group_name} FILES ${ABS_RESOURCE_FILES}) + endif() + +endfunction() + +function(simple_executable) + set(source_files "") + retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} source_files) + add_executable(${PROJECT_NAME} ${source_files}) + + # 自动 include 检索到的 .cmake 文件 + if(RETRIEVED_CMAKE_FILES) + foreach(cmake_file IN LISTS RETRIEVED_CMAKE_FILES) + message(STATUS "自动 include CMake 文件: ${cmake_file}") + include("${cmake_file}") + endforeach() + endif() + + target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options) + message(STATUS "创建可执行文件目标: ${PROJECT_NAME},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}") + add_os_definitions(${PROJECT_NAME}) +endfunction() + +function(simple_library library_type) + set(source_files "") + retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} source_files) + add_library(${PROJECT_NAME} ${library_type} ${source_files}) + + # 自动 include 检索到的 .cmake 文件 + if(RETRIEVED_CMAKE_FILES) + foreach(cmake_file IN LISTS RETRIEVED_CMAKE_FILES) + message(STATUS "自动 include CMake 文件: ${cmake_file}") + include("${cmake_file}") + endforeach() + endif() + + if(library_type STREQUAL "INTERFACE") + target_include_directories(${PROJECT_NAME} INTERFACE + $ + $ + ) + target_link_libraries(${PROJECT_NAME} INTERFACE mirai_project_options) + else() + target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ + ) + target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options) + endif() + message(STATUS "创建库目标: ${PROJECT_NAME},类型: ${library_type},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}") + add_os_definitions(${PROJECT_NAME}) +endfunction() diff --git a/cmake/shader_compile.cmake b/cmake/shader_compile.cmake new file mode 100644 index 0000000..aa0a668 --- /dev/null +++ b/cmake/shader_compile.cmake @@ -0,0 +1,515 @@ +# ============================================================================ +# MIRAI 着色器编译 CMake 模块 +# ============================================================================ +# 提供自动化 GLSL 着色器编译和代码生成功能 +# 使用 glslangValidator 或 glslc (shaderc) 编译 GLSL 到 SPIR-V +# +# 主要函数: +# add_glsl_shader_library() - 创建 GLSL 着色器库目标 +# compile_glsl_shader() - 编译单个 GLSL 着色器 +# +# 辅助函数: +# find_python_with_jinja2() - 查找带 Jinja2 的 Python +# find_glsl_compiler() - 查找 GLSL 编译器 +# ============================================================================ + +include_guard(GLOBAL) + +# ============================================================================ +# find_python_with_jinja2 - 查找带 Jinja2 模块的 Python +# ============================================================================ +function(find_python_with_jinja2 OUT_PYTHON_EXECUTABLE) + # 首先查找 Python + find_package(Python3 COMPONENTS Interpreter QUIET) + + if(NOT Python3_FOUND) + message(FATAL_ERROR "Python3 not found. Please install Python 3.") + endif() + + # 检查 Jinja2 是否已安装 + execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import jinja2; print(jinja2.__version__)" + RESULT_VARIABLE JINJA2_CHECK_RESULT + OUTPUT_VARIABLE JINJA2_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(NOT JINJA2_CHECK_RESULT EQUAL 0) + message(FATAL_ERROR + "Python Jinja2 module not found.\n" + "Please install it with: ${Python3_EXECUTABLE} -m pip install jinja2" + ) + endif() + + message(STATUS "Found Python with Jinja2: ${Python3_EXECUTABLE} (Jinja2 ${JINJA2_VERSION})") + set(${OUT_PYTHON_EXECUTABLE} "${Python3_EXECUTABLE}" PARENT_SCOPE) +endfunction() + +# ============================================================================ +# find_glsl_compiler - 查找 GLSL 编译器 (glslangValidator 或 glslc) +# ============================================================================ +function(find_glsl_compiler OUT_COMPILER OUT_COMPILER_TYPE) + # 首先尝试查找 glslc (shaderc) + find_program(GLSLC_EXECUTABLE glslc + HINTS + $ENV{VULKAN_SDK}/Bin + $ENV{VULKAN_SDK}/bin + ${Vulkan_GLSLC_EXECUTABLE} + ) + + if(GLSLC_EXECUTABLE) + message(STATUS "Found GLSL compiler: ${GLSLC_EXECUTABLE} (glslc)") + set(${OUT_COMPILER} "${GLSLC_EXECUTABLE}" PARENT_SCOPE) + set(${OUT_COMPILER_TYPE} "glslc" PARENT_SCOPE) + return() + endif() + + # 尝试查找 glslangValidator + find_program(GLSLANG_VALIDATOR_EXECUTABLE glslangValidator + HINTS + $ENV{VULKAN_SDK}/Bin + $ENV{VULKAN_SDK}/bin + ${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE} + ) + + if(GLSLANG_VALIDATOR_EXECUTABLE) + message(STATUS "Found GLSL compiler: ${GLSLANG_VALIDATOR_EXECUTABLE} (glslangValidator)") + set(${OUT_COMPILER} "${GLSLANG_VALIDATOR_EXECUTABLE}" PARENT_SCOPE) + set(${OUT_COMPILER_TYPE} "glslangValidator" PARENT_SCOPE) + return() + endif() + + message(FATAL_ERROR + "No GLSL compiler found. Please install Vulkan SDK or shaderc.\n" + "Expected: glslc or glslangValidator in PATH or VULKAN_SDK" + ) +endfunction() + +# ============================================================================ +# 内部变量设置 +# ============================================================================ + +# 着色器编译器目标名称 +set(MIRAI_SHADER_COMPILER_TARGET "mirai_shader_compile") + +# 代码生成脚本路径 +set(MIRAI_SHADER_GENERATOR_SCRIPT "${CMAKE_SOURCE_DIR}/tools/generate_shader_bindings.py") + +# 模板目录 +set(MIRAI_SHADER_TEMPLATE_DIR "${CMAKE_SOURCE_DIR}/tools/templates") + +# ============================================================================ +# _mirai_ensure_shader_compiler - 内部函数,确保着色器编译器可用 +# ============================================================================ +function(_mirai_ensure_shader_compiler) + # 检查编译器目标是否存在 + if(NOT TARGET ${MIRAI_SHADER_COMPILER_TARGET}) + message(FATAL_ERROR + "Shader compiler target '${MIRAI_SHADER_COMPILER_TARGET}' not found.\n" + "Make sure add_subdirectory(tools/shader_compile) is called before using add_glsl_shader_library()." + ) + endif() +endfunction() + +# ============================================================================ +# _get_shader_stage_from_extension - 从文件扩展名获取着色器阶段 +# ============================================================================ +function(_get_shader_stage_from_extension FILE_PATH OUT_STAGE) + get_filename_component(EXT "${FILE_PATH}" EXT) + string(TOLOWER "${EXT}" EXT_LOWER) + + if(EXT_LOWER STREQUAL ".vert") + set(${OUT_STAGE} "vert" PARENT_SCOPE) + elseif(EXT_LOWER STREQUAL ".frag") + set(${OUT_STAGE} "frag" PARENT_SCOPE) + elseif(EXT_LOWER STREQUAL ".comp") + set(${OUT_STAGE} "comp" PARENT_SCOPE) + elseif(EXT_LOWER STREQUAL ".geom") + set(${OUT_STAGE} "geom" PARENT_SCOPE) + elseif(EXT_LOWER STREQUAL ".tesc") + set(${OUT_STAGE} "tesc" PARENT_SCOPE) + elseif(EXT_LOWER STREQUAL ".tese") + set(${OUT_STAGE} "tese" PARENT_SCOPE) + else() + set(${OUT_STAGE} "" PARENT_SCOPE) + endif() +endfunction() + +# ============================================================================ +# compile_glsl_shader - 编译单个 GLSL 着色器 +# ============================================================================ +# +# 用法: +# compile_glsl_shader( +# INPUT shader.vert +# OUTPUT shader.vert.spv +# STAGE vert +# DEFINES DEBUG FEATURE_X +# INCLUDE_DIRS include/ +# ) +# +# 参数: +# INPUT - 输入 GLSL 文件路径(必需) +# OUTPUT - 输出 SPIR-V 文件路径(必需) +# STAGE - 着色器阶段 (vert/frag/comp/geom/tesc/tese)(可选,自动检测) +# DEFINES - 预处理器定义(可选) +# INCLUDE_DIRS - include 目录(可选) +# +function(compile_glsl_shader) + cmake_parse_arguments(SHADER "" "INPUT;OUTPUT;STAGE" "DEFINES;INCLUDE_DIRS" ${ARGN}) + + # 验证必需参数 + if(NOT SHADER_INPUT) + message(FATAL_ERROR "compile_glsl_shader: INPUT is required") + endif() + if(NOT SHADER_OUTPUT) + message(FATAL_ERROR "compile_glsl_shader: OUTPUT is required") + endif() + + # 自动检测着色器阶段 + if(NOT SHADER_STAGE) + _get_shader_stage_from_extension("${SHADER_INPUT}" SHADER_STAGE) + if(NOT SHADER_STAGE) + message(FATAL_ERROR "compile_glsl_shader: Cannot determine shader stage from extension. Please specify STAGE.") + endif() + endif() + + # 查找 GLSL 编译器 + find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE) + + # 构建编译命令参数 + set(COMPILE_ARGS "") + + if(GLSL_COMPILER_TYPE STREQUAL "glslc") + # glslc 参数 + list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}") + list(APPEND COMPILE_ARGS "--target-env=vulkan1.3") + list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}") + + # 添加 include 目录 + foreach(INC_DIR ${SHADER_INCLUDE_DIRS}) + list(APPEND COMPILE_ARGS "-I${INC_DIR}") + endforeach() + + # 添加宏定义 + foreach(DEF ${SHADER_DEFINES}) + list(APPEND COMPILE_ARGS "-D${DEF}") + endforeach() + + list(APPEND COMPILE_ARGS "${SHADER_INPUT}") + else() + # glslangValidator 参数 + list(APPEND COMPILE_ARGS "-V") # 生成 SPIR-V + list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3") + list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}") + list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}") + + # 添加 include 目录 + foreach(INC_DIR ${SHADER_INCLUDE_DIRS}) + list(APPEND COMPILE_ARGS "-I${INC_DIR}") + endforeach() + + # 添加宏定义 + foreach(DEF ${SHADER_DEFINES}) + list(APPEND COMPILE_ARGS "-D${DEF}") + endforeach() + + list(APPEND COMPILE_ARGS "${SHADER_INPUT}") + endif() + + # 创建编译命令 + add_custom_command( + OUTPUT "${SHADER_OUTPUT}" + COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS} + DEPENDS "${SHADER_INPUT}" + COMMENT "Compiling GLSL shader: ${SHADER_INPUT}" + VERBATIM + ) +endfunction() + +# ============================================================================ +# add_glsl_shader_library - 添加 GLSL 着色器库到现有目标 +# ============================================================================ +# +# 将 GLSL 着色器编译和代码生成附加到现有的编译目标上 +# +# 用法: +# # 方案1: 自动搜索着色器 +# add_glsl_shader_library(TARGET mirai_shader +# SHADER_PATH shaders/glsl +# INCLUDE_DIRS shaders/glsl/common +# ) +# +# # 方案2: 指定具体着色器文件 +# add_glsl_shader_library(TARGET mirai_shader +# SHADERS shaders/ui/rect.vert shaders/ui/rect.frag +# INCLUDE_DIRS shaders/common +# ) +# +# 参数: +# TARGET - 现有的编译目标(必需) +# SHADERS - 具体的着色器文件列表(可选) +# SHADER_PATH - 着色器搜索目录,相对于 CMAKE_CURRENT_SOURCE_DIR(可选) +# INCLUDE_DIRS - 着色器 include 路径(可选) +# DEFINES - 预处理器定义(可选) +# RECURSIVE - 是否递归搜索子目录(可选,默认 OFF) +# +function(add_glsl_shader_library) + # 解析参数 + set(options RECURSIVE) + set(oneValueArgs TARGET SHADER_PATH) + set(multiValueArgs SHADERS INCLUDE_DIRS DEFINES) + cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # 验证必需参数 + if(NOT SHADER_TARGET) + message(FATAL_ERROR "add_glsl_shader_library: TARGET is required") + endif() + + # 验证目标存在 + if(NOT TARGET ${SHADER_TARGET}) + message(FATAL_ERROR "add_glsl_shader_library: Target '${SHADER_TARGET}' does not exist") + endif() + + # 收集着色器文件 + set(ALL_SHADERS "${SHADER_SHADERS}") + + if(SHADER_SHADER_PATH) + set(SHADER_EXTENSIONS "vert;frag;comp;geom;tesc;tese") + + if(SHADER_RECURSIVE) + foreach(EXT ${SHADER_EXTENSIONS}) + file(GLOB_RECURSE FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}") + list(APPEND ALL_SHADERS ${FOUND_SHADERS}) + endforeach() + else() + foreach(EXT ${SHADER_EXTENSIONS}) + file(GLOB FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}") + list(APPEND ALL_SHADERS ${FOUND_SHADERS}) + endforeach() + endif() + endif() + + # 去重 + if(ALL_SHADERS) + list(REMOVE_DUPLICATES ALL_SHADERS) + endif() + + # 设置输出目录 + set(SHADER_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") + set(SHADER_INTERMEDIATE_DIR "${CMAKE_CURRENT_BINARY_DIR}/shader_intermediate") + + file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}") + file(MAKE_DIRECTORY "${SHADER_INTERMEDIATE_DIR}") + + # 确保编译器可用 + _mirai_ensure_shader_compiler() + + # 查找 Python with Jinja2 + find_python_with_jinja2(PYTHON_EXECUTABLE) + + # 查找 GLSL 编译器 + find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE) + + # 构建 include 路径参数 + set(INCLUDE_ARGS "") + foreach(INC_DIR ${SHADER_INCLUDE_DIRS}) + if(IS_ABSOLUTE "${INC_DIR}") + list(APPEND INCLUDE_ARGS "${INC_DIR}") + else() + list(APPEND INCLUDE_ARGS "${CMAKE_CURRENT_SOURCE_DIR}/${INC_DIR}") + endif() + endforeach() + + # 收集生成的文件 + set(ALL_GENERATED_HEADERS "") + set(ALL_SPV_FILES "") + + # 按着色器名称分组(去掉扩展名后相同的文件归为一组) + set(SHADER_GROUPS "") + foreach(SHADER_SOURCE ${ALL_SHADERS}) + get_filename_component(SHADER_NAME_WE "${SHADER_SOURCE}" NAME_WE) + get_filename_component(SHADER_DIR "${SHADER_SOURCE}" DIRECTORY) + + # 创建唯一的组标识符 + string(REPLACE "/" "_" DIR_SAFE "${SHADER_DIR}") + set(GROUP_ID "${DIR_SAFE}_${SHADER_NAME_WE}") + + # 添加到组列表 + list(APPEND SHADER_GROUPS "${GROUP_ID}") + list(APPEND ${GROUP_ID}_SOURCES "${SHADER_SOURCE}") + set(${GROUP_ID}_NAME "${SHADER_NAME_WE}") + set(${GROUP_ID}_DIR "${SHADER_DIR}") + endforeach() + + # 去重组列表 + if(SHADER_GROUPS) + list(REMOVE_DUPLICATES SHADER_GROUPS) + endif() + + # 处理每个着色器组 + foreach(GROUP_ID ${SHADER_GROUPS}) + set(GROUP_SOURCES "${${GROUP_ID}_SOURCES}") + set(GROUP_NAME "${${GROUP_ID}_NAME}") + set(GROUP_DIR "${${GROUP_ID}_DIR}") + + # 每个着色器组的输出目录 + set(SPIRV_OUTPUT_DIR "${SHADER_INTERMEDIATE_DIR}/${GROUP_NAME}") + set(GENERATED_HPP "${SHADER_OUTPUT_DIR}/${GROUP_NAME}_bindings.hpp") + + file(MAKE_DIRECTORY "${SPIRV_OUTPUT_DIR}") + + set(GROUP_SPV_FILES "") + + # 编译组内的每个着色器 + foreach(SHADER_SOURCE ${GROUP_SOURCES}) + get_filename_component(SHADER_FILENAME "${SHADER_SOURCE}" NAME) + _get_shader_stage_from_extension("${SHADER_SOURCE}" SHADER_STAGE) + + if(NOT SHADER_STAGE) + message(WARNING "Skipping unknown shader type: ${SHADER_SOURCE}") + continue() + endif() + + set(SPV_OUTPUT "${SPIRV_OUTPUT_DIR}/${SHADER_FILENAME}.spv") + + # 构建编译命令参数 + set(COMPILE_ARGS "") + + if(GLSL_COMPILER_TYPE STREQUAL "glslc") + list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}") + list(APPEND COMPILE_ARGS "--target-env=vulkan1.3") + list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}") + + foreach(INC_DIR ${INCLUDE_ARGS}) + list(APPEND COMPILE_ARGS "-I${INC_DIR}") + endforeach() + + foreach(DEF ${SHADER_DEFINES}) + list(APPEND COMPILE_ARGS "-D${DEF}") + endforeach() + + list(APPEND COMPILE_ARGS "${SHADER_SOURCE}") + else() + list(APPEND COMPILE_ARGS "-V") + list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3") + list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}") + list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}") + + foreach(INC_DIR ${INCLUDE_ARGS}) + list(APPEND COMPILE_ARGS "-I${INC_DIR}") + endforeach() + + foreach(DEF ${SHADER_DEFINES}) + list(APPEND COMPILE_ARGS "-D${DEF}") + endforeach() + + list(APPEND COMPILE_ARGS "${SHADER_SOURCE}") + endif() + + # 创建编译命令 + add_custom_command( + OUTPUT "${SPV_OUTPUT}" + COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS} + DEPENDS "${SHADER_SOURCE}" + COMMENT "Compiling GLSL: ${SHADER_FILENAME}" + VERBATIM + ) + + list(APPEND GROUP_SPV_FILES "${SPV_OUTPUT}") + list(APPEND ALL_SPV_FILES "${SPV_OUTPUT}") + endforeach() + + # 使用 mirai_shader_compile 工具生成反射和绑定 + if(GROUP_SPV_FILES) + # 创建标记文件表示编译完成 + set(COMPILED_MARKER "${SPIRV_OUTPUT_DIR}/.compiled") + + add_custom_command( + OUTPUT "${COMPILED_MARKER}" + COMMAND ${CMAKE_COMMAND} -E touch "${COMPILED_MARKER}" + DEPENDS ${GROUP_SPV_FILES} + COMMENT "GLSL shaders compiled: ${GROUP_NAME}" + VERBATIM + ) + + # 生成绑定头文件 + add_custom_command( + OUTPUT "${GENERATED_HPP}" + COMMAND $ + --dir "${SPIRV_OUTPUT_DIR}" + --output "${GENERATED_HPP}" + --name "${GROUP_NAME}" + DEPENDS "${COMPILED_MARKER}" ${MIRAI_SHADER_COMPILER_TARGET} + COMMENT "Generating bindings: ${GROUP_NAME}" + VERBATIM + ) + + list(APPEND ALL_GENERATED_HEADERS "${GENERATED_HPP}") + endif() + endforeach() + + # 附加到目标:添加生成目录到 include 路径 + target_include_directories(${SHADER_TARGET} PUBLIC + $ + ) + + # 添加编译依赖 + if(ALL_GENERATED_HEADERS) + add_custom_target(${SHADER_TARGET}_shaders DEPENDS ${ALL_GENERATED_HEADERS}) + add_dependencies(${SHADER_TARGET} ${SHADER_TARGET}_shaders) + endif() + + # 导出变量 + set(${SHADER_TARGET}_SHADER_HEADERS ${ALL_GENERATED_HEADERS} PARENT_SCOPE) + set(${SHADER_TARGET}_SHADER_OUTPUT_DIR ${SHADER_OUTPUT_DIR} PARENT_SCOPE) + set(${SHADER_TARGET}_SPV_FILES ${ALL_SPV_FILES} PARENT_SCOPE) + + message(STATUS "Added GLSL shaders to target: ${SHADER_TARGET}") + message(STATUS " Output: ${SHADER_OUTPUT_DIR}") + if(ALL_SHADERS) + list(LENGTH ALL_SHADERS SHADER_COUNT) + message(STATUS " Shaders: ${SHADER_COUNT} files") + endif() +endfunction() + +# ============================================================================ +# add_shader_library - 兼容性别名(调用 add_glsl_shader_library) +# ============================================================================ +# +# 为了向后兼容,保留 add_shader_library 函数名 +# 现在默认使用 GLSL 编译 +# +function(add_shader_library) + # 解析参数 + set(options RECURSIVE) + set(oneValueArgs TARGET SHADER_PATH) + set(multiValueArgs SHADERS REF_PATHS DEFINES) + cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # 转换参数并调用新函数 + set(FORWARD_ARGS "TARGET" "${SHADER_TARGET}") + + if(SHADER_SHADER_PATH) + list(APPEND FORWARD_ARGS "SHADER_PATH" "${SHADER_SHADER_PATH}") + endif() + + if(SHADER_SHADERS) + list(APPEND FORWARD_ARGS "SHADERS" ${SHADER_SHADERS}) + endif() + + if(SHADER_REF_PATHS) + list(APPEND FORWARD_ARGS "INCLUDE_DIRS" ${SHADER_REF_PATHS}) + endif() + + if(SHADER_DEFINES) + list(APPEND FORWARD_ARGS "DEFINES" ${SHADER_DEFINES}) + endif() + + if(SHADER_RECURSIVE) + list(APPEND FORWARD_ARGS "RECURSIVE") + endif() + + add_glsl_shader_library(${FORWARD_ARGS}) +endfunction() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..559ca9f --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,5 @@ +project(mirai_example) + +simple_executable() + +target_link_libraries(${PROJECT_NAME} PUBLIC mirai) diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..8a3b3be --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7c80f3d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,25 @@ +project(mirai) + +find_package(Vulkan REQUIRED) +find_package(SDL3 CONFIG REQUIRED) +find_package(harfbuzz CONFIG REQUIRED) +find_package(Freetype CONFIG REQUIRED) +find_package(yoga CONFIG REQUIRED) +find_package(Eigen3 CONFIG REQUIRED) +find_package(spdlog CONFIG REQUIRED) +find_package(fmt CONFIG REQUIRED) +find_package(Stb REQUIRED) + +simple_library(STATIC) + +target_link_libraries(${PROJECT_NAME} PUBLIC + Vulkan::Vulkan + SDL3::SDL3 + Freetype::Freetype + harfbuzz::harfbuzz + yoga::yogacore + Eigen3::Eigen + spdlog::spdlog + fmt::fmt +) +target_link_directories(${PROJECT_NAME} PUBLIC ${Stb_INCLUDE_DIR}) diff --git a/src/core/object.cpp b/src/core/object.cpp new file mode 100644 index 0000000..8b9e981 --- /dev/null +++ b/src/core/object.cpp @@ -0,0 +1,10 @@ +#include "object.h" + +namespace mirai { + std::atomic object::next_id_{1}; + + object::~object() = default; + object::object() : id_(next_id_.fetch_add(1, std::memory_order_relaxed)) { + + } +} diff --git a/src/core/object.h b/src/core/object.h new file mode 100644 index 0000000..550d31e --- /dev/null +++ b/src/core/object.h @@ -0,0 +1,228 @@ +#pragma once +#include +#include +#include + +#include "types.h" + +namespace mirai { + using object_id = u64; + constexpr object_id invalid_object_id = 0; + + struct type_info { + std::string_view name; + size_type hash; + const type_info* parent; + + [[nodiscard]] constexpr bool operator==(const type_info& other) const noexcept { + return hash == other.hash; + } + + [[nodiscard]] constexpr bool operator!=(const type_info& other) const noexcept { + return !(*this == other); + } + + [[nodiscard]] constexpr bool is_derived_from(const type_info& base) const noexcept { + auto current = this; + while (current) { + if (*current == base) { + return true; + } + current = current->parent; + } + return false; + } + }; + + consteval auto compile_time_hash(std::string_view str) noexcept { + size_type hash = 14695981039346656037ULL; + for (char c : str) { + hash ^= static_cast(c); + hash *= 1099511628211ULL; + } + return hash; + } + + template + consteval auto get_type_name() noexcept { + #if defined(__clang__) + constexpr std::string_view prefix = "[T = "; + constexpr std::string_view suffix = "]"; + constexpr std::string_view function = __PRETTY_FUNCTION__; + #elif defined(__GNUC__) + constexpr std::string_view prefix = "with T = "; + constexpr std::string_view suffix = "]"; + constexpr std::string_view function = __PRETTY_FUNCTION__; + #elif defined(_MSC_VER) + constexpr std::string_view prefix = "get_type_name<"; + constexpr std::string_view suffix = ">(void)"; + constexpr std::string_view function = __FUNCSIG__; + #else + return typeid(T).name(); + #endif + + const auto start = function.find(prefix) + prefix.size(); + const auto end = function.rfind(suffix); + return function.substr(start, end - start); + } + + template + [[nodiscard]] constexpr const auto& get_type_info() noexcept { + static type_info info{ + get_type_name(), + compile_time_hash(get_type_name()), + nullptr + }; + return info; + } + template + [[nodiscard]] constexpr const auto& get_type_info_with_parent() noexcept { + if constexpr (std::is_same_v) { + static type_info info{ + get_type_name(), + compile_time_hash(get_type_name()), + nullptr + }; + return info; + } else { + static type_info info{ + get_type_name(), + compile_time_hash(get_type_name()), + &get_type_info_with_parent() + }; + return info; + } + } + + class object : public std::enable_shared_from_this { + public: + using ptr = std::shared_ptr; + using weak_ptr = std::weak_ptr; + using parent_t = void; + + virtual ~object(); + + object(const object&) = delete; + object& operator=(const object&) = delete; + object(object&&) = delete; + object& operator=(object&&) = delete; + + [[nodiscard]] constexpr virtual auto get_type() const noexcept -> const type_info& { + return get_type_info(); + } + [[nodiscard]] constexpr virtual auto get_parent_type() const noexcept -> const type_info* { + return nullptr; + } + + [[nodiscard]] constexpr auto type_name() const noexcept { + return get_type().name; + } + + template requires std::derived_from + [[nodiscard]] constexpr auto is() const noexcept { + return get_type().is_derived_from(get_type_info()); + } + + template requires std::derived_from + [[nodiscard]] auto as() noexcept -> T* { + if (is()) { + return static_cast(this); + } + return nullptr; + } + + template requires std::derived_from + [[nodiscard]] auto as() const noexcept -> const T* { + if (is()) { + return static_cast(this); + } + return nullptr; + } + + public: + #if MIRAI_DEBUG + void set_debug_name(std::string name) { + debug_name_ = std::move(name); + } + + [[nodiscard]] const std::string& get_debug_name() const noexcept { + return debug_name_; + } + private: + std::string debug_name_; + #endif + + + public: + [[nodiscard]] auto id() const noexcept { + return id_; + } + + template requires std::derived_from + [[nodiscard]] auto shared_from_this_as() noexcept -> std::shared_ptr { + return std::dynamic_pointer_cast(shared_from_this()); + } + + template requires std::derived_from + [[nodiscard]] auto shared_from_this_as() const noexcept -> std::shared_ptr { + return std::dynamic_pointer_cast(shared_from_this()); + } + + template requires std::derived_from + [[nodiscard]] auto weak_from_this_as() noexcept -> std::weak_ptr { + return std::weak_ptr(shared_from_this_as()); + } + + template requires std::derived_from + [[nodiscard]] auto weak_from_this_as() const noexcept -> std::weak_ptr { + return std::weak_ptr(shared_from_this_as()); + } + protected: + object(); + + virtual void on_created() {} + virtual void on_destroying() {} + private: + object_id id_; + static std::atomic next_id_; + friend class object_registry; + }; + + template + struct object_deleter { + void operator()(T* ptr) { + if (ptr) { + ptr->on_destroying(); + delete ptr; + } + } + }; + + #define MIRAI_OBJECT_TYPE_INFO(class_name, parent_class) \ + public: \ + using parent_t = parent_class; \ + [[nodiscard]] constexpr virtual const mirai::type_info& get_type() const noexcept override { \ + return mirai::get_type_info_with_parent(); \ + } \ + [[nodiscard]] constexpr virtual const mirai::type_info* get_parent_type() const noexcept override { \ + return &mirai::get_type_info(); \ + } \ + friend struct mirai::object_factory; \ + template \ + friend struct mirai::object_deleter; \ + friend class object_registry; + + struct object_factory { + template requires std::derived_from + static auto create(Args&&... args) { + auto ptr = std::shared_ptr(new T(std::forward(args)...), object_deleter{}); + ptr->on_created(); + return ptr; + } + }; + + template requires std::derived_from + auto make_obj(Args&&... args) { + return object_factory::create(std::forward(args)...); + } +} diff --git a/src/core/types.h b/src/core/types.h new file mode 100644 index 0000000..0fad612 --- /dev/null +++ b/src/core/types.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +#include + +namespace mirai { + using u8 = std::uint8_t; + using u16 = std::uint16_t; + using u32 = std::uint32_t; + using u64 = std::uint64_t; + using i8 = std::int8_t; + using i16 = std::int16_t; + using i32 = std::int32_t; + using i64 = std::int64_t; + using f32 = float; + using f64 = double; + using size_type = std::size_t; + using index_type = std::ptrdiff_t; + using ptrdiff_type = std::ptrdiff_t; + using uintptr_type = std::uintptr_t; + using intptr_type = std::intptr_t; + + using byte_type = std::byte; + using byte_span = std::span; + using const_byte_span = std::span; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..7870338 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +project(mirai_tests) + +enable_testing() +find_package(GTest CONFIG REQUIRED) + +simple_executable() +target_link_libraries(${PROJECT_NAME} PRIVATE mirai GTest::gtest GTest::gtest_main) +add_test(AllTestsInMain ${PROJECT_NAME}) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..835dca0 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,25 @@ +#include +#include "object_test.h" + +TEST(test_object, derived) { + auto test_obj = mirai::make_obj(); + auto test_obj2 = mirai::make_obj(); + + // 检查类型信息 + + // test_obj应该继承自mirai::object + EXPECT_TRUE(test_obj->is()); + EXPECT_TRUE(test_obj2->is()); + + // test_obj应该是test_class1类型,但不是test_class2类型 + EXPECT_TRUE(test_obj->is()); + EXPECT_FALSE(test_obj->is()); + + // test_obj2应该是test_class2类型,也是test_class1类型 + EXPECT_TRUE(test_obj2->is()); + EXPECT_TRUE(test_obj2->is()); + + // test_obj的类型名称检查 + EXPECT_EQ(test_obj->type_name(), "class test_class1"); + EXPECT_EQ(test_obj2->type_name(), "class test_class2"); +} diff --git a/tests/object_test.h b/tests/object_test.h new file mode 100644 index 0000000..b303da4 --- /dev/null +++ b/tests/object_test.h @@ -0,0 +1,11 @@ +#pragma once +#include "core/object.h" + +class test_class1 : public mirai::object { + MIRAI_OBJECT_TYPE_INFO(test_class1, mirai::object) + +}; + +class test_class2 : public test_class1 { + MIRAI_OBJECT_TYPE_INFO(test_class2, test_class1) +}; diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..27ddcbb --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "mirai", + "version": "0.1.0", + "description": "Modern C++23 GUI Framework based on Vulkan 1.3", + "homepage": "https://github.com/mirai-framework/mirai", + "license": "MIT", + "supports": "windows | linux | osx", + "builtin-baseline": "5422eb983d4ca8bc4851ac9771d6e74554efc5c8", + "dependencies": [ + { + "name": "sdl3", + "version>=": "3.2.28" + }, + { + "name": "spirv-cross", + "version>=": "1.3.296.0" + }, + { + "name": "nlohmann-json", + "version>=": "3.12.0#1" + }, + { + "name": "stb", + "version>=": "2024-07-29#1" + }, + { + "name": "freetype", + "version>=": "2.13.3", + "features": ["bzip2", "png", "zlib"] + }, + { + "name": "harfbuzz", + "version>=": "12.2.0", + "features": ["freetype"] + }, + { + "name": "yoga", + "version>=": "3.2.1" + }, + { + "name": "eigen3", + "version>=": "3.4.1" + }, + { + "name": "spdlog", + "version>=": "1.16.0" + }, + { + "name": "fmt", + "version>=": "12.1.0" + }, + { + "name": "gtest", + "version>=": "1.17.0#2" + } + ], + "overrides": [], + "features": { + "tests": { + "description": "Build unit tests", + "dependencies": [ + { + "name": "gtest", + "version>=": "1.17.0#2" + } + ] + }, + "examples": { + "description": "Build example applications", + "dependencies": [] + }, + "docs": { + "description": "Build documentation", + "dependencies": [] + } + } +} \ No newline at end of file