因為專案會分成跑引擎的 SDK CMakeList 與實際專案範例的 CMakeList 設定,這邊大致介紹其內容設定與較重要的部分。
CMake 是常見用來處理 C++ library 的依賴跟環境設定的設定檔。而最常用的為以下兩個
CMakeLists.txt
CMakePresets.json
CMakeLists.txt 最重要的事情是告訴 CMake 此專案需要設定那些東西,譬如專案名稱、程式連結、需編譯的程式等等。
以下先用事先測試用的專案當作範例,這是其中接下來用來輸出三角形範例的的 CMakeLists
# 指定此專案要求的最低 CMake 版本
cmake_minimum_required(VERSION 3.25)
# 建立名為 DayX_Triangle 的專案
project(DayX_Triangle LANGUAGES CXX)
# 建立一個名為 DayX_Triangle 的可執行檔 Target
# 一般小程式用 executable 就好
add_executable(DayX_Triangle WIN32
main.cpp
)
# 要求此專案至少需要 C++20
target_compile_features(DayX_Triangle PRIVATE
cxx_std_20
)
# 設定 DayX_Triangle 需要連結的 Library
target_link_libraries(DayX_Triangle PRIVATE #PRIVATE 表示這個相依性只屬於 DayX_Triangle
# MyDXEngineSDK::MyDXEngineSDK:是透過 add_library(... ALIAS ...) 來找到
MyDXEngineSDK::MyDXEngineSDK
)
# 複製 DayX_Triangle 執行時需要的相依檔案 EX:DLL
copy_runtime_dependencies(DayX_Triangle)
# 設定 Visual Studio 啟動 Debug 時的 Working Directory
set_target_properties(DayX_Triangle PROPERTIES
VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
# add_custom_command 會在編譯完成之後執行額外指令
add_custom_command(TARGET DayX_Triangle POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/../assets"
"$<TARGET_FILE_DIR:DayX_Triangle>/assets"
)
另一個是引擎 SDK 的設定
# 建立名為 MyDXEngineSDK 的靜態函式庫,並指定要編譯進去的原始碼與標頭檔
add_library(MyDXEngineSDK STATIC
core/graphics_engine.cpp
#...等等其他程式
)
# 為 MyDXEngineSDK 建立命名空間形式的別名,讓其他 Target 可以用 MyDXEngineSDK::MyDXEngineSDK 連結
add_library(MyDXEngineSDK::MyDXEngineSDK ALIAS MyDXEngineSDK)
# 指定 MyDXEngineSDK 使用 C++20,且這個需求會傳遞給連結它的 Target
target_compile_features(MyDXEngineSDK PUBLIC cxx_std_20)
# 指定 MyDXEngineSDK 使用 pch.h 作為預編譯標頭,以減少重複編譯時間
target_precompile_headers(MyDXEngineSDK PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/core/pch.h"
)
# 設定 MyDXEngineSDK 的 Header 搜尋路徑,並讓連結它的 Target 也能使用這些路徑
target_include_directories(MyDXEngineSDK PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/core
${CMAKE_CURRENT_SOURCE_DIR}/system
${CMAKE_CURRENT_SOURCE_DIR}
)
# 指定 MyDXEngineSDK 需要連結的系統函式庫
target_link_libraries(MyDXEngineSDK PUBLIC
Microsoft::DirectX-Headers
Microsoft::DirectXMath
Microsoft::DirectXShaderCompiler
Microsoft::DirectXTK12
assimp::assimp
glog::glog
d3d12
d3dcompiler
dxgi
dxguid
user32
)
這邊值得提一下的是 add_library 後面還加了 STATIC 的關鍵字,這是較簡單的部屬設定,別的專案只要簡單在 target_link_libraries 打上 add_library 裡設定的名字,他就可以抓到裡面設定的內容。
MyDXEngineSDK 資料夾裡的程式因為主要是只用在此專案的範例,就只需要設定成 STATIC 就好了,如果後面還有時間或許會帶過 SHARED 會需要額外改變的設定。
下表表示其差異
| 函式庫類型 | 靜態 Library(Static Library) | 動態 Library( Shared Library) |
|---|---|---|
| CMake 寫法 | add_library(Name STATIC ...) |
add_library(Name SHARED ...) |
| 連結時機 | 編譯/連結階段直接整合進執行檔 | 編譯時記錄依賴,執行時再載入動態 Library |
| 執行時是否需要額外 Library 檔案 | 通常不需要 | 通常需要 .dll / .so / .dylib |
| 部署難度 | 較簡單 | 需要確保動態 Library 存在於正確位置 |
| 多個程式共用 Library | 每個程式各自包含一份 | 多個程式可以共用同一份動態 Library |
| 更新 Library | 通常需要重新 Link,甚至重新 Build | 某些情況下只替換 DLL 即可,但必須維持 ABI 相容 |
| 適合用途 | 小型引擎、SDK、單一程式 | Plugin、共用 Runtime、大型模組化系統 |
最後則是最外面的 CMakeList 設定
cmake_minimum_required (VERSION 3.21)
if(POLICY CMP0162)
cmake_policy(SET CMP0162 NEW)
endif()
# 建立 SkylineEngine 專案,設定描述、使用 C++,並指定版本為 1.0.0
project (SkylineEngine
DESCRIPTION "CMake example for Direct3D 12 Game (Win32) w/ DeviceResources using VCPKG"
LANGUAGES CXX
VERSION 1.0.0)
# 提供是否建立測試範本的選項
option(BUILD_TEST_TEMPLATE "Ignore warnings related to TODOs" OFF)
# 提供是否在建置時啟用靜態程式碼分析的選項
option(ENABLE_CODE_ANALYSIS "Use Static Code Analysis on build" OFF)
set(CMAKE_CXX_STANDARD 17)
# 強制要求編譯器必須支援指定的 C++17 標準
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 關閉編譯器特有的 C++ 擴充功能,盡量使用標準 C++
set(CMAKE_CXX_EXTENSIONS OFF)
# 指定靜態 Library 的輸出目錄為 build/lib
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
# 指定動態 Library 的輸出目錄為 build/lib
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
# 指定執行檔與 Windows DLL 等 Runtime 檔案的輸出目錄為 build/bin
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# 載入自訂的 Compiler 與 Linker 相關 CMake 設定
include(CompilerAndLinker.cmake)
find_package(directxmath CONFIG REQUIRED)
find_package(directx-headers CONFIG REQUIRED)
find_package(directx-dxc CONFIG REQUIRED)
find_package(directxtk12 CONFIG REQUIRED)
find_package(assimp CONFIG REQUIRED)
# 指定 gflags 使用帶有 Namespace 的 CMake Target 名稱
set(GFLAGS_USE_TARGET_NAMESPACE ON)
find_package(gflags CONFIG REQUIRED)
find_package(glog CONFIG REQUIRED)
# 定義一個函式,在指定 Target 建置完成後自動複製其執行時需要的 DLL
function(copy_runtime_dependencies target)
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:${target}>
$<TARGET_FILE_DIR:${target}>
COMMAND_EXPAND_LISTS
COMMENT "Copying runtime dependencies for ${target}"
)
endfunction()
# 使用 add_subdirectory 來把各個資料夾底下的內容設定成子專案並處理其中的 CMakeLists.txt
add_subdirectory(MyDXEngineSDK)
add_subdirectory(iTHelpContest30/DayX_Triangle)
簡單來說,CMakePresets 是讓 CMake 知道要用什麼環境來執行程式。

以最一開始 Visual Studio 預設的設定來說,光是 Release 與 Debug 就有分成 x64 跟 x86。
如果沒有這些設定,以上面的 x64-debug 來說,就相當於每次都要手動輸入像是下面的內容
cmake -S . -B out/build/x64-debug `
-G Ninja `
-DCMAKE_BUILD_TYPE=Debug `
-DCMAKE_C_COMPILER=cl.exe `
-DCMAKE_CXX_COMPILER=cl.exe `
"-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$env:VSINSTALLDIR\Common7\IDE\CommonExtensions\Microsoft\CMake\cmake\Microsoft\SegmentHeap.cmake"
尤其如果今天你使用一個第三方函式庫,因為引擎的一些環境需要特別去微調裡面的設定時,如果每次都要打上面那串,既容易打錯還容易因為忘記輸出出不符合環境的函式庫。
這時候就可以把這些設定放進 CMakePresets 裡,以下是部分的設定
...
{
"name": "Debug", // Preset 名稱
"cacheVariables": { // 設定會傳給 CMake Cache 的變
"CMAKE_BUILD_TYPE": "Debug" // 指定為 Debug 建置模式
},
"hidden": true // 隱藏此 Preset,通常作為其他 Preset 繼承用,不直接讓使用者選擇
},
{
"name": "Release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo", // 使用最佳化的 Release 模式,同時保留除錯資訊
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": true // 啟用跨模組最佳化,例如 MSVC 的 LTCG 或 GCC/Clang 的 LTO
},
"hidden": true
}
...
當然可以先看看就好,畢竟這部分是真的動過一次後大概就不會再動的設定,但如果知道他是這樣使用的,以後需要客製化輸出環境就會方便許多。
CMake Documentation and Community