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] rougelike\extern\SDL3\src\cpuinfo\SDL_cpuinfo.c: In function 'SDL_GetSystemPageSize_REAL':
[build] rougelike\extern\SDL3\src\cpuinfo\SDL_cpuinfo.c:1257:34: error: implicit declaration of function 'getpagesize' [-Wimplicit-function-declaration]
[build]  1257 |             SDL_SystemPageSize = getpagesize();
[build]       |                                  ^~~~~~~~~~~
[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();
}

接下来编译就没什么大问题了。

posted @ 2025-10-14 21:36  ChickenRice  阅读(10)  评论(0)    收藏  举报