SDL3的构建记录
环境
windows11 + msys2 + gcc + cmake
编辑器使用vscode,插件为cmake tool,c++和clangd。
子模块
神奇的 sdl3-mixer 还在设计阶段 vcpkg 没有,如果从0构建需要的版本 vcpkg 也不支持。
正常情况下可以选择relase导入include和dll进行构建,这是最方便的手法。
mixer在本文写的时候是2.8.1。
不过看commit修复了很多bug,因此我决定直接子模块构建。
本文写完之后,发现 Conan 有现成的库可以导入,不需要我这么麻烦。
首先初始化git,然后把所有的子模块放在 extern
文件夹里。
git submodule add https://github.com/libsdl-org/SDL.git extern/SDL3
git submodule add https://github.com/libsdl-org/SDL_image.git extern/SDL3_image
git submodule add https://github.com/libsdl-org/SDL_mixer.git extern/SDL3_mixer
git submodule add https://github.com/libsdl-org/SDL_ttf.git extern/SDL3_ttf
sdl3
的附属模块有大量的子模块,我们需要把子模块也拉取进来。
git submodule update --init --recursive
无尽的等待之后,子模块也拉去结束了。
Cmake 构建
在项目根目录下创建一个 CMakeLists.txt
,开始写这个折磨人的过程。
cmake_minimum_required(VERSION 3.16...3.25)# 抑制第三方库的 CMake 弃用警告
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ON CACHE BOOL "" FORCE)# clangd 使用
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)# 设置构建目录
# set(CMAKE_BINARY_DIR "${CMAKE_SOURCE_DIR}/build" CACHE PATH "Build directory")# 输出目录配置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}" CACHE INTERNAL "")# SDL 是 C 语言构建,即使用 C++,这里也需要 C。
project(RougeLike VERSION 1.0.0 LANGUAGES C CXX)# 设置标准
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)if(WIN32)# 禁用 getpagesize,让 SDL3 使用 Windows 的 GetSystemInfoset(HAVE_GETPAGESIZE OFF CACHE BOOL "" FORCE)set(HAVE_SYSCONF OFF CACHE BOOL "" FORCE)# 确保 Windows 平台定义被设置add_compile_definitions(SDL_PLATFORM_WINDOWS)
endif()# SDL3 库构建
add_subdirectory(extern/SDL3 EXCLUDE_FROM_ALL)# SDL3-ttf
set(SDLTTF_VENDORED ON CACHE BOOL "" FORCE)
add_subdirectory(extern/SDL3_ttf EXCLUDE_FROM_ALL)# SDL3_mixer
set(SDLMIXER_VENDORED ON CACHE BOOL "" FORCE)
set(SDLMIXER_MP3_DRMP3 ON CACHE BOOL "" FORCE)
set(SDLMIXER_VORBIS_STB ON CACHE BOOL "" FORCE)
set(SDLMIXER_FLAC_DRFLAC ON CACHE BOOL "" FORCE)
set(SDLMIXER_OPUS ON CACHE BOOL "" FORCE)set(SDLMIXER_MP3_MPG123 OFF CACHE BOOL "" FORCE) # windows 下禁用了add_subdirectory(extern/SDL3_mixer EXCLUDE_FROM_ALL)# SDL_image
set(SDLIMAGE_VENDORED ON CACHE BOOL "" FORCE)
set(SDLIMAGE_AVIF OFF CACHE BOOL "" FORCE)
set(SDLIMAGE_BMP OFF CACHE BOOL "" FORCE)
set(SDLIMAGE_JPEG OFF CACHE BOOL "" FORCE)
set(SDLIMAGE_WEBP OFF CACHE BOOL "" FORCE)
add_subdirectory(extern/SDL3_image EXCLUDE_FROM_ALL)# EnTT 头文件库
add_subdirectory(extern/entt EXCLUDE_FROM_ALL)add_executable(${PROJECT_NAME})target_sources(${PROJECT_NAME}
PRIVATE src/main.cpp
)# 显示决定目录
target_include_directories(${PROJECT_NAME} PRIVATE${CMAKE_CURRENT_SOURCE_DIR}/src # 项目头文件路径
)# 链接 SDL 库
target_link_libraries(${PROJECT_NAME} PUBLIC SDL3_ttf::SDL3_ttfSDL3_mixer::SDL3_mixerSDL3_image::SDL3_imageSDL3::SDL3EnTT::EnTT
)# 自动复制 DLL 文件到可执行文件目录
if(WIN32)# 查找所有依赖的 DLL 文件add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy_if_different$<TARGET_FILE:SDL3::SDL3>$<TARGET_FILE_DIR:${PROJECT_NAME}>COMMAND ${CMAKE_COMMAND} -E copy_if_different$<TARGET_FILE:SDL3_image::SDL3_image>$<TARGET_FILE_DIR:${PROJECT_NAME}>COMMAND ${CMAKE_COMMAND} -E copy_if_different$<TARGET_FILE:SDL3_mixer::SDL3_mixer>$<TARGET_FILE_DIR:${PROJECT_NAME}>COMMAND ${CMAKE_COMMAND} -E copy_if_different$<TARGET_FILE:SDL3_ttf::SDL3_ttf>$<TARGET_FILE_DIR:${PROJECT_NAME}>COMMENT "Copying DLL files to output directory")
endif()# 配置资源文件目录
set(ASSETS_SOURCE_DIR "${CMAKE_SOURCE_DIR}/assets")
set(ASSETS_DEST_DIR "$<TARGET_FILE_DIR:${PROJECT_NAME}>/assets")# 检查资源目录是否存在
if(EXISTS ${ASSETS_SOURCE_DIR})message(STATUS "Found assets directory: ${ASSETS_SOURCE_DIR}")# 使用自定义目标确保目录创建和文件复制add_custom_target(copy_assets ALLCOMMAND ${CMAKE_COMMAND} -E make_directory "${ASSETS_DEST_DIR}"COMMAND ${CMAKE_COMMAND} -E copy_directory"${ASSETS_SOURCE_DIR}" "${ASSETS_DEST_DIR}"COMMENT "Copying assets to output directory"DEPENDS ${PROJECT_NAME})# 确保主目标在资源复制之前构建add_dependencies(copy_assets ${PROJECT_NAME})else()message(WARNING "Assets directory not found: ${ASSETS_SOURCE_DIR}")
endif()
之后编译就基本能用了。
踩坑记录-神奇的错误
写了个程序,编译之后发现了这个。
[build] [ 6%] Building C object extern/SDL3/CMakeFiles/SDL3-shared.dir/src/cpuinfo/SDL_cpuinfo.c.obj
[build] [01m[Krougelike\extern\SDL3\src\cpuinfo\SDL_cpuinfo.c:[m[K In function '[01m[KSDL_GetSystemPageSize_REAL[m[K':
[build] [01m[Krougelike\extern\SDL3\src\cpuinfo\SDL_cpuinfo.c:1257:34:[m[K [01;31m[Kerror: [m[Kimplicit declaration of function '[01m[Kgetpagesize[m[K' [[01;31m[K-Wimplicit-function-declaration[m[K]
[build] 1257 | SDL_SystemPageSize = [01;31m[Kgetpagesize[m[K();
[build] | [01;31m[K^~~~~~~~~~~[m[K
[build] mingw32-make[3]: *** [extern\SDL3\CMakeFiles\SDL3-shared.dir\build.make:487: extern/SDL3/CMakeFiles/SDL3-shared.dir/src/cpuinfo/SDL_cpuinfo.c.obj] Error 1
[build] mingw32-make[2]: *** [CMakeFiles\Makefile2:958: extern/SDL3/CMakeFiles/SDL3-shared.dir/all] Error 2
[build] mingw32-make[1]: *** [CMakeFiles\Makefile2:933: CMakeFiles/RougeLike.dir/rule] Error 2
[build] mingw32-make: *** [Makefile:189: RougeLike] Error 2
[proc] 命令“msys64\ucrt64\bin\cmake.EXE --build rougelike/out/build --target RougeLike --”已退出,代码为 2
[driver] 生成完毕: 00:00:15.460
[build] 生成已完成,退出代码为 2
可能是 mingW 的原因,我在 MSVC 没有遇到过这个问题。
注意到错误里面有 getpagesize()
,这不是 windows 下能编译的东西,说明函数执行错了。
查找 SDL3/src/cpuinfo/SDL_cpuinfo.c
,有下面的段落:
```static int SDL_SystemPageSize = -1;int SDL_GetSystemPageSize(void)
{if (SDL_SystemPageSize == -1) {
#ifdef SDL_PLATFORM_SYSTEM_PAGE_SIZE_PRIVATE // consoles will define this in a platform-specific internal header.SDL_SystemPageSize = SDL_PLATFORM_SYSTEM_PAGE_SIZE_PRIVATE;
#endif
#ifdef SDL_PLATFORM_3DSSDL_SystemPageSize = 4096; // It's an ARM11 CPU; I assume this is 4K.
#endif
#ifdef SDL_PLATFORM_VITASDL_SystemPageSize = 4096; // It's an ARMv7 CPU; I assume this is 4K.
#endif
#ifdef SDL_PLATFORM_PS2SDL_SystemPageSize = 4096; // It's a MIPS R5900 CPU; I assume this is 4K.
#endif// 这里还有一大堆 省略了
#ifdef HAVE_GETPAGESIZEif (SDL_SystemPageSize <= 0) {SDL_SystemPageSize = getpagesize(); // 问题在这里}
#endif
#if defined(SDL_PLATFORM_WINDOWS)if (SDL_SystemPageSize <= 0) {SYSTEM_INFO sysinfo;GetSystemInfo(&sysinfo);SDL_SystemPageSize = (int) sysinfo.dwPageSize;}
#endif
问题在 HAVE_GETPAGESIZE
宏上, MSYS2 是模拟的 unix 环境,这个宏会通过,但 windows 下执行 cmake 可不会通过。
在cmake禁止这个宏就可以了。
if(WIN32)# 禁用 getpagesize,让 SDL3 使用 Windows 的 GetSystemInfoset(HAVE_GETPAGESIZE OFF CACHE BOOL "" FORCE)set(HAVE_SYSCONF OFF CACHE BOOL "" FORCE)# 确保 Windows 平台定义被设置add_compile_definitions(SDL_PLATFORM_WINDOWS)
endif()# SDL3 库构建
add_subdirectory(extern/SDL3 EXCLUDE_FROM_ALL)
MP3问题
是的,问题不止一个。
[build] [ 93%] Building C object extern/SDL3_mixer/external/libmpg123-build/ports/cmake/src/libmpg123/CMakeFiles/libmpg123.dir/__/__/__/__/src/libmpg123/lfs_wrap.c.obj
[build]
rougelike\extern\SDL3_mixer\external\mpg123\src\libmpg123\lfs_wrap.c:62:23: error: size of array 'MPG123_STATIC_ASSERT' is negative
[build] 62 | typedef unsigned char MPG123_STATIC_ASSERT[(SIZEOF_OFF_T == sizeof(off_t)) ? 1 : -1];
[build] | ^~~~~~~~~~~~~~~~~~~~
[build] mingw32-make[3]: *** [extern\SDL3_mixer\external\libmpg123-build\ports\cmake\src\libmpg123\CMakeFiles\libmpg123.dir\build.make:424: extern/SDL3_mixer/external/libmpg123-build/ports/cmake/src/libmpg123/CMakeFiles/libmpg123.dir/__/__/__/__/src/libmpg123/lfs_wrap.c.obj] Error 1
[build] mingw32-make[2]: *** [CMakeFiles\Makefile2:1840: extern/SDL3_mixer/external/libmpg123-build/ports/cmake/src/libmpg123/CMakeFiles/libmpg123.dir/all] Error 2
[build] mingw32-make[1]: *** [CMakeFiles\Makefile2:933: CMakeFiles/RougeLike.dir/rule] Error 2
[build] mingw32-make: *** [Makefile:189: RougeLike] Error 2
[proc] 命令“\msys64\ucrt64\bin\cmake.EXE --build
rougelike/out/build --target RougeLike --”已退出,代码为 2
[driver] 生成完毕: 00:03:24.208
[build] 生成已完成,退出代码为 2
这就很简单了,是 MPG123
的问题,我们开启了替代品 DRMP3
,就不需要编译这个了。
set(SDLMIXER_MP3_MPG123 OFF CACHE BOOL "" FORCE)
add_subdirectory(extern/SDL3_mixer EXCLUDE_FROM_ALL)
编译结果
之后就编译成功了。
代码使用 sdl3-sample 的代码,做了点修改。
#define SDL_MAIN_USE_CALLBACKS // This is necessary for the new callbacks API. To use the legacy API, don't define this.
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_init.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <SDL3_image/SDL_image.h>
#include <cmath>
#include <string_view>
#include <filesystem>
#include <thread>constexpr uint32_t windowStartWidth = 400;
constexpr uint32_t windowStartHeight = 400;struct AppContext {SDL_Window* window;SDL_Renderer* renderer;SDL_Texture* messageTex, *imageTex;SDL_FRect messageDest;SDL_AudioDeviceID audioDevice;MIX_Track* track;SDL_AppResult app_quit = SDL_APP_CONTINUE;
};SDL_AppResult SDL_Fail(){SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError());return SDL_APP_FAILURE;
}SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {// init the library, here we make a window so we only need the Video capabilities.if (not SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)){return SDL_Fail();}// init TTFif (not TTF_Init()) {return SDL_Fail();}// init Mixerif (not MIX_Init()) {return SDL_Fail();}// create a windowSDL_Window* window = SDL_CreateWindow("SDL Minimal Sample", windowStartWidth, windowStartHeight, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);if (not window){return SDL_Fail();}// create a rendererSDL_Renderer* renderer = SDL_CreateRenderer(window, NULL);if (not renderer){return SDL_Fail();}// load the font
#if __ANDROID__std::filesystem::path basePath = ""; // on Android we do not want to use basepath. Instead, assets are available at the root directory.
#elseauto basePathPtr = SDL_GetBasePath();if (not basePathPtr){return SDL_Fail();}const std::filesystem::path basePath = basePathPtr;
#endifconst auto fontPath = basePath / "assets/fonts/Inter-VariableFont.ttf";TTF_Font* font = TTF_OpenFont(fontPath.string().c_str(), 36);if (not font) {return SDL_Fail();}// render the font to a surfaceconst std::string_view text = "Hello SDL!";SDL_Surface* surfaceMessage = TTF_RenderText_Solid(font, text.data(), text.length(), { 255,255,255 });// make a texture from the surfaceSDL_Texture* messageTex = SDL_CreateTextureFromSurface(renderer, surfaceMessage);// we no longer need the font or the surface, so we can destroy those now.TTF_CloseFont(font);SDL_DestroySurface(surfaceMessage);// load the SVGauto svg_surface = IMG_Load((basePath / "assets/images/gs_tiger.svg").string().c_str());SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, svg_surface);SDL_DestroySurface(svg_surface);// get the on-screen dimensions of the text. this is necessary for rendering itauto messageTexProps = SDL_GetTextureProperties(messageTex);SDL_FRect text_rect{.x = 0,.y = 0,.w = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0)),.h = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0))};// init SDL MixerMIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);if (mixer == nullptr) {return SDL_Fail();}auto mixerTrack = MIX_CreateTrack(mixer);// load the musicauto musicPath = basePath / "assets/sounds/the_entertainer.ogg";auto music = MIX_LoadAudio(mixer,musicPath.string().c_str(),false);if (not music) {return SDL_Fail();}// play the music (does not loop)SDL_PropertiesID props = SDL_CreateProperties();MIX_SetTrackAudio(mixerTrack, music);MIX_PlayTrack(mixerTrack, props);SDL_DestroyProperties(props);// print some information about the windowSDL_ShowWindow(window);{int width, height, bbwidth, bbheight;SDL_GetWindowSize(window, &width, &height);SDL_GetWindowSizeInPixels(window, &bbwidth, &bbheight);SDL_Log("Window size: %ix%i", width, height);SDL_Log("Backbuffer size: %ix%i", bbwidth, bbheight);if (width != bbwidth){SDL_Log("This is a highdpi environment.");}}// set up the application data*appstate = new AppContext{.window = window,.renderer = renderer,.messageTex = messageTex,.imageTex = tex,.messageDest = text_rect,.track = mixerTrack,};SDL_SetRenderVSync(renderer, -1); // enable vysncSDL_Log("Application started successfully!");return SDL_APP_CONTINUE;
}SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event* event) {auto* app = (AppContext*)appstate;if (event->type == SDL_EVENT_QUIT) {app->app_quit = SDL_APP_SUCCESS;}return SDL_APP_CONTINUE;
}SDL_AppResult SDL_AppIterate(void *appstate) {auto* app = (AppContext*)appstate;// draw a colorauto time = SDL_GetTicks() / 1000.f;auto red = (std::sin(time) + 1) / 2.0 * 255;auto green = (std::sin(time / 2) + 1) / 2.0 * 255;auto blue = (std::sin(time) * 2 + 1) / 2.0 * 255;SDL_SetRenderDrawColor(app->renderer, red, green, blue, SDL_ALPHA_OPAQUE);SDL_RenderClear(app->renderer);// Renderer uses the painter's algorithm to make the text appear above the image, we must render the image first.SDL_RenderTexture(app->renderer, app->imageTex, NULL, NULL);SDL_RenderTexture(app->renderer, app->messageTex, NULL, &app->messageDest);SDL_RenderPresent(app->renderer);return app->app_quit;
}void SDL_AppQuit(void* appstate, SDL_AppResult result) {auto* app = (AppContext*)appstate;if (app) {SDL_DestroyRenderer(app->renderer);SDL_DestroyWindow(app->window);// prevent the music from abruptly ending.MIX_StopTrack(app->track, MIX_TrackMSToFrames(app->track, 1000));std::this_thread::sleep_for(std::chrono::milliseconds(1000));//Mix_FreeMusic(app->music); // this call blocks until the music has finished fadingSDL_CloseAudioDevice(app->audioDevice);delete app;}TTF_Quit();MIX_Quit();SDL_Log("Application quit successfully!");SDL_Quit();
}
接下来编译就没什么大问题了。