27、图形与UI库:使用FetchContent下载Dear ImGui、SFML、SDL2
做C++图形开发,最头疼的事是什么?我个人觉得,不是算法难,也不是API复杂,而是——依赖管理。
你想想看,一个图形项目,少说要依赖两三个库。Dear ImGui做调试界面,SFML处理窗口和音频,SDL2做底层输入管理。每个库都有自己的编译方式、版本号、头文件路径。手动下载?太累了。用系统包管理器?版本对不上。用git submodule?更新起来又麻烦。
嗯,这时候FetchContent就派上用场了。它能把依赖管理这件事,变得像写几行CMake代码一样简单。
为什么选这三个库?
我先说说我的选择逻辑。Dear ImGui、SFML、SDL2,这三个库在图形开发里各司其职:
| 库名 | 主要用途 | 为什么用FetchContent |
|---|---|---|
| Dear ImGui | 即时模式GUI,调试工具 | 版本迭代快,需要精确控制 |
| SFML | 多媒体库,窗口/音频/网络 | 跨平台编译配置复杂 |
| SDL2 | 底层图形和输入抽象 | 很多项目需要特定版本 |
我在项目中遇到过好几次这样的情况:用系统自带的SDL2,结果版本太老,不支持某个新特性。用FetchContent指定版本,就再也没出过这种问题。
核心思路:一次配置,到处编译
说白了,FetchContent做的事情就是:在CMake配置阶段,自动从GitHub或任意URL下载源码,然后把它当成你项目的一部分来编译。你不需要手动下载、解压、配置路径。一切自动化。
为什么会这么方便?因为FetchContent会把下载的库通过add_subdirectory集成进来,所有目标(target)和头文件路径都自动可用。你只需要在CMakeLists.txt里写几行声明,剩下的交给CMake。
实战:用FetchContent下载Dear ImGui
先来个最简单的。Dear ImGui是一个纯头文件加少量cpp的库,下载和集成非常直接。
include(FetchContent)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.90.0
)
FetchContent_MakeAvailable(imgui)
# 注意:Dear ImGui需要你自己添加实现文件
target_sources(myapp PRIVATE
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
)
target_include_directories(myapp PRIVATE
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
这里有个坑,我曾经踩过:Dear ImGui本身不提供CMakeLists.txt,所以FetchContent_MakeAvailable不会自动创建任何target。你需要手动把源文件加到你的项目里。嗯,这一点和SFML、SDL2不一样,后两者是完整的CMake项目。
file(GLOB)来收集,但要注意CMake的GLOB不会自动检测新文件。我个人习惯显式列出,这样更可控。
实战:用FetchContent下载SFML
SFML就省心多了。它是一个完整的CMake项目,下载后直接可用。
FetchContent_Declare(
sfml
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.1
)
# 关闭SFML的音频模块,减少编译时间
set(SFML_BUILD_AUDIO OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_NETWORK OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_DOC OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(sfml)
target_link_libraries(myapp PRIVATE sfml-graphics sfml-window sfml-system)
你想想看,如果没有FetchContent,你要手动去GitHub下载SFML的源码,然后编译,再配置路径。现在呢?三行声明,一个MakeAvailable,搞定。
我记得第一次用这个方式集成SFML时,编译通过的那一刻,我差点以为自己在做梦。以前配置SFML至少得花半小时,现在不到一分钟。
FetchContent_MakeAvailable之前设置,否则不会生效。因为MakeAvailable会立即执行下载和配置,之后的变量设置就晚了。
实战:用FetchContent下载SDL2
SDL2的情况稍微特殊一点。它的官方仓库用的是Mercurial,不是Git。不过好在GitHub上有镜像,我们可以直接用。
FetchContent_Declare(
sdl2
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-2.28.5
)
# SDL2默认会编译很多测试和示例,关掉它们
set(SDL_TEST OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(sdl2)
target_link_libraries(myapp PRIVATE SDL2::SDL2)
这里要注意的是,SDL2的target名称是SDL2::SDL2,不是sdl2。如果你用错了名字,链接时会报找不到target的错误。我曾经在这个问题上卡了半小时,后来才发现是大小写的问题。
整合:一个项目同时使用三个库
现在我们把三个库整合到一个项目里。这是实际开发中最常见的情况。
cmake_minimum_required(VERSION 3.20)
project(MyGraphicsApp)
include(FetchContent)
# ---- Dear ImGui ----
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.90.0
)
# ---- SFML ----
FetchContent_Declare(
sfml
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.1
)
set(SFML_BUILD_AUDIO OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_NETWORK OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
# ---- SDL2 ----
FetchContent_Declare(
sdl2
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-2.28.5
)
set(SDL_TEST OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
# 一次性下载所有依赖
FetchContent_MakeAvailable(imgui sfml sdl2)
# 创建可执行文件
add_executable(myapp main.cpp)
# 添加Dear ImGui的源文件
target_sources(myapp PRIVATE
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
)
target_include_directories(myapp PRIVATE
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
target_link_libraries(myapp PRIVATE
sfml-graphics sfml-window sfml-system
SDL2::SDL2
)
这段代码看起来长,但结构很清晰。三个库的声明放在一起,然后一次性调用FetchContent_MakeAvailable。CMake会自动处理依赖顺序,不会出现A等B、B等A的死锁情况。
build/_deps目录,否则CMake可能不会重新下载。
知识体系:FetchContent管理图形库的核心逻辑
下面这张图,是我总结的整个流程。你看一眼就能明白,为什么FetchContent能解决图形库依赖的痛点。
从这张图你可以看到,整个流程是线性的:声明 → 配置 → 下载 → 使用。每个库独立声明,但最终都通过MakeAvailable统一集成。这样做的好处是,你可以在一个地方管理所有依赖的版本,不会出现版本冲突。
避坑指南:我踩过的三个坑
最后,分享几个我实际踩过的坑,希望能帮你省点时间。
- 版本号写错导致编译失败:我曾经把Dear ImGui的版本号写成
v1.90,但实际标签是v1.90.0。CMake不会报错,只会下载一个空仓库。排查了半天才发现是版本号的问题。建议每次写版本号前,先去GitHub上确认一下标签名。 - 忘记关闭测试和示例:SFML和SDL2默认会编译大量测试和示例代码。我第一次集成SDL2时,编译时间从2分钟变成了15分钟。后来才发现是
SDL_TEST没关。记住,生产环境一定要关掉这些选项。 - 缓存导致版本不更新:有一次我改了SDL2的版本号,但重新运行cmake时,它还是用旧版本。后来发现
build/_deps目录里有缓存。删除这个目录后重新配置,问题解决。
URL代替GIT_REPOSITORY,指向内网的镜像地址。或者用GIT_SHALLOW选项只下载最新版本,减少数据量。
好了,关于用FetchContent下载Dear ImGui、SFML、SDL2,就讲到这里。这三个库的集成方式各有特点,但核心思路是一样的:用CMake的声明式语法,把依赖管理自动化。你只要把版本号锁定好,剩下的交给FetchContent就行。
公众号:蓝海资料掘金营,微信deep3321