SDL3和其附属的编译记录
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 的 GetSystemInfo
set(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_ttf
SDL3_mixer::SDL3_mixer
SDL3_image::SDL3_image
SDL3::SDL3
EnTT::EnTT
)
# 自动复制 DLL 文件到可执行文件目录
if(WIN32)
# 查找所有依赖的 DLL 文件
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${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 ALL
COMMAND ${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_3DS
SDL_SystemPageSize = 4096; // It's an ARM11 CPU; I assume this is 4K.
#endif
#ifdef SDL_PLATFORM_VITA
SDL_SystemPageSize = 4096; // It's an ARMv7 CPU; I assume this is 4K.
#endif
#ifdef SDL_PLATFORM_PS2
SDL_SystemPageSize = 4096; // It's a MIPS R5900 CPU; I assume this is 4K.
#endif
// 这里还有一大堆 省略了
#ifdef HAVE_GETPAGESIZE
if (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 的 GetSystemInfo
set(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 TTF
if (not TTF_Init()) {
return SDL_Fail();
}
// init Mixer
if (not MIX_Init()) {
return SDL_Fail();
}
// create a window
SDL_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 renderer
SDL_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.
#else
auto basePathPtr = SDL_GetBasePath();
if (not basePathPtr){
return SDL_Fail();
}
const std::filesystem::path basePath = basePathPtr;
#endif
const 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 surface
const 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 surface
SDL_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 SVG
auto 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 it
auto 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 Mixer
MIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
if (mixer == nullptr) {
return SDL_Fail();
}
auto mixerTrack = MIX_CreateTrack(mixer);
// load the music
auto 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 window
SDL_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 vysnc
SDL_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 color
auto 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 fading
SDL_CloseAudioDevice(app->audioDevice);
delete app;
}
TTF_Quit();
MIX_Quit();
SDL_Log("Application quit successfully!");
SDL_Quit();
}
接下来编译就没什么大问题了。