SDL3和其附屬的編譯記錄
SDL3的構(gòu)建記錄
環(huán)境
windows11 + msys2 + gcc + cmake
編輯器使用vscode,插件為cmake tool,c++和clangd。
子模塊
神奇的 sdl3-mixer 還在設(shè)計(jì)階段 vcpkg 沒(méi)有,如果從0構(gòu)建需要的版本 vcpkg 也不支持。
正常情況下可以選擇relase導(dǎo)入include和dll進(jìn)行構(gòu)建,這是最方便的手法。
mixer在本文寫(xiě)的時(shí)候是2.8.1。
不過(guò)看commit修復(fù)了很多bug,因此我決定直接子模塊構(gòu)建。
本文寫(xiě)完之后,發(fā)現(xiàn) Conan 有現(xiàn)成的庫(kù)可以導(dǎo)入,不需要我這么麻煩。
首先初始化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 的附屬模塊有大量的子模塊,我們需要把子模塊也拉取進(jìn)來(lái)。
git submodule update --init --recursive
無(wú)盡的等待之后,子模塊也拉去結(jié)束了。
Cmake 構(gòu)建
在項(xiàng)目根目錄下創(chuàng)建一個(gè) CMakeLists.txt,開(kāi)始寫(xiě)這個(gè)折磨人的過(guò)程。
cmake_minimum_required(VERSION 3.16...3.25)
# 抑制第三方庫(kù)的 CMake 棄用警告
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ON CACHE BOOL "" FORCE)
# clangd 使用
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# 設(shè)置構(gòu)建目錄
# 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 語(yǔ)言構(gòu)建,即使用 C++,這里也需要 C。
project(RougeLike VERSION 1.0.0 LANGUAGES C CXX)
# 設(shè)置標(biāo)準(zhǔn)
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 平臺(tái)定義被設(shè)置
add_compile_definitions(SDL_PLATFORM_WINDOWS)
endif()
# SDL3 庫(kù)構(gòu)建
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 頭文件庫(kù)
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 # 項(xiàng)目頭文件路徑
)
# 鏈接 SDL 庫(kù)
target_link_libraries(${PROJECT_NAME} PUBLIC
SDL3_ttf::SDL3_ttf
SDL3_mixer::SDL3_mixer
SDL3_image::SDL3_image
SDL3::SDL3
EnTT::EnTT
)
# 自動(dòng)復(fù)制 DLL 文件到可執(zhí)行文件目錄
if(WIN32)
# 查找所有依賴(lài)的 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}")
# 使用自定義目標(biāo)確保目錄創(chuàng)建和文件復(fù)制
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}
)
# 確保主目標(biāo)在資源復(fù)制之前構(gòu)建
add_dependencies(copy_assets ${PROJECT_NAME})
else()
message(WARNING "Assets directory not found: ${ASSETS_SOURCE_DIR}")
endif()
之后編譯就基本能用了。
踩坑記錄-神奇的錯(cuò)誤
寫(xiě)了個(gè)程序,編譯之后發(fā)現(xiàn)了這個(gè)。
[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 沒(méi)有遇到過(guò)這個(gè)問(wèn)題。
注意到錯(cuò)誤里面有 getpagesize(),這不是 windows 下能編譯的東西,說(shuō)明函數(shù)執(zhí)行錯(cuò)了。
查找 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(); // 問(wèn)題在這里
}
#endif
#if defined(SDL_PLATFORM_WINDOWS)
if (SDL_SystemPageSize <= 0) {
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
SDL_SystemPageSize = (int) sysinfo.dwPageSize;
}
#endif
問(wèn)題在 HAVE_GETPAGESIZE 宏上, MSYS2 是模擬的 unix 環(huán)境,這個(gè)宏會(huì)通過(guò),但 windows 下執(zhí)行 cmake 可不會(huì)通過(guò)。
在cmake禁止這個(gè)宏就可以了。
if(WIN32)
# 禁用 getpagesize,讓 SDL3 使用 Windows 的 GetSystemInfo
set(HAVE_GETPAGESIZE OFF CACHE BOOL "" FORCE)
set(HAVE_SYSCONF OFF CACHE BOOL "" FORCE)
# 確保 Windows 平臺(tái)定義被設(shè)置
add_compile_definitions(SDL_PLATFORM_WINDOWS)
endif()
# SDL3 庫(kù)構(gòu)建
add_subdirectory(extern/SDL3 EXCLUDE_FROM_ALL)
MP3問(wèn)題
是的,問(wèn)題不止一個(gè)。
[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
這就很簡(jiǎn)單了,是 MPG123 的問(wèn)題,我們開(kāi)啟了替代品 DRMP3,就不需要編譯這個(gè)了。
set(SDLMIXER_MP3_MPG123 OFF CACHE BOOL "" FORCE)
add_subdirectory(extern/SDL3_mixer EXCLUDE_FROM_ALL)
編譯結(jié)果
之后就編譯成功了。
代碼使用 sdl3-sample 的代碼,做了點(diǎn)修改。
#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();
}
接下來(lái)編譯就沒(méi)什么大問(wèn)題了。

浙公網(wǎng)安備 33010602011771號(hào)