ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SDL3 官方示例集(examples/)完全指南:回调式应用架构、CMake 构建与示例素材生产流程

2026/9/14 14:11:13 拓冰建站 浏览量
SDL3 官方示例集(examples/)完全指南:回调式应用架构、CMake 构建与示例素材生产流程 SDL3 官方示例集examples/完全指南回调式应用架构、CMake 构建与示例素材生产流程【免费下载链接】SDLSimple DirectMedia Layer项目地址: https://gitcode.com/GitHub_Trending/sd/SDL本指南围绕 SDL3 仓库中 examples/README.md 展开系统讲解示例集的组织结构、SDL3 独有的主回调main callbacks应用架构、通过 CMake 构建与运行这些示例的完整方法以及官方缩略图/演示视频onmouseover素材的流水线式制作流程。读完本文你将掌握SDL_AppInit/SDL_AppIterate/SDL_AppEvent/SDL_AppQuit四个回调的正确用法能独立编译运行任意一个示例并能为自己的示例生成网页演示与宣传素材。一、这是什么一套跨平台独立的 SDL 应用示例examples/目录下汇集了一批独立的 SDL 应用示例程序。所谓独立是指每个示例只依赖 SDL3 本身含官方测试资源不依赖第三方库因此只要不特别说明它们应当能在 SDL 支持的所有平台上开箱即用。如果在某个平台上无法正常工作官方希望使用者将问题反馈给项目通过提交 bug 报告的方式见 examples/README.md。从目录结构看以 examples/categories.txt 声明的分类顺序为准示例按功能划分为 9 大类、30 余个独立程序分类目录示例内容代表性程序renderer/渲染管线入门到进阶01-clear逐帧变色清屏、02-primitives、05-rectangles、06-textures、19-affine-textures、20-blending等input/手柄/摇杆输入01-joystick-polling、02-joystick-events、03-gamepad-polling、04-gamepad-events、05-gamepad-rumbleaudio/音频播放与流处理01-simple-playback、03-load-wav、04-multiple-streams、05-planar-datastorage/用户数据目录存储01-usercamera/摄像头采集01-read-and-drawasyncio/异步 IO 与位图加载01-load-bitmapspen/手写笔输入01-drawing-linesmisc/杂项系统能力01-power、02-clipboard、03-localedemo/完整小游戏/演示01-snake贪吃蛇、02-woodeneye-008、03-infinite-monkeys、04-bytepusher这些示例从renderer/01-clear/clear.c这类 60 余行的最小可运行程序到demo/下完整可玩的小游戏形成了一条平滑的学习曲线非常适合作为 SDL3 的入门与参考代码库。二、SDL_AppIterate 是什么SDL3 主回调应用架构2.1 为什么示例采用回调而非 main传统 SDL 程序以main(argc, argv)为入口。但 SDL3 提供了另一种可选的应用结构将程序拆分为一组由 SDL 调用的回调函数。示例集统一采用这种格式有两个明确理由天然适配 Web 构建所有示例都以网页版本发布在官方示例站点上可直接在浏览器中运行观看效果。Emscripten 平台强制要求程序以分片方式运行——它不希望你的main里自己管理那个死循环而是要它自己来调度每一帧。回调结构让同一份源码无需堆砌大量#ifdef即可同时编译为桌面程序和 WebAssembly 程序。示范意义回调把程序干净地切分成所有应用都关心的四个逻辑部分——程序启动、事件处理、单帧工作、程序退出。2.2 传统 main 结构与回调结构的对比传统游戏程序的高层结构通常是int main(int argc, char **argv) { initialize(); while (keep_running()) { handle_new_events(); do_one_frame_of_stuff(); } deinitialize(); }问题在于iOS 希望main立即返回、由系统通知你何时更新下一帧Emscripten浏览器中的程序完全依赖这种被驱动的模式Wayland 等视频后端可以主动通知应用该画下一帧了以省电并与合成器协同。虽然你可以针对不同平台写特判代码但 SDL3 提供了一套由 SDL 在应用背后统一调度的回调系统你只需把顶层逻辑改写成几个回调就能在所有受支持平台上无差别运行无需任何#ifdef。需要强调的是回调入口不是 main 的替代品——官方预期的主要写法仍是SDL_main回调只是移除了一些平台特有的管理细节并为未来新平台降低接入门槛。对不支持回调的平台SDL 会在内部用简单的循环在常规SDL_main实现里模拟出回调行为因此回调方式在所有平台上都能工作。2.3 如何启用SDL_MAIN_USE_CALLBACKS在项目中唯一一个源文件里先定义宏再包含头文件#define SDL_MAIN_USE_CALLBACKS 1 #include SDL3/SDL.h #include SDL3/SDL_main.h一旦启用你不能再编写main函数否则很可能链接失败而是提供下面四个函数。仓库中的示例模板 examples/template.c 正是以这种方式组织源码骨架的。2.4 四个回调的完整契约以下内容继承自官方文档 docs/README-main-functions.md并结合头文件 include/SDL3/SDL_main.h 中的函数声明与注释展开。① 启动SDL_AppInitSDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]);在一切发生之前只调用一次argc/argv的语义与普通main完全一致。不要在里面进入无限主循环只做一次性启动工作初始化子系统、创建窗口/渲染器、加载资源然后返回。可以给*appstate赋一个指针该指针会在后续所有回调的appstate参数中回传给你从而避免使用全局变量完全可选不赋值则后续收到NULL。② 主循环体SDL_AppIterateSDL_AppResult SDL_AppIterate(void *appstate);被反复调用频率由平台决定可能是显示器刷新率也可能是在循环里尽量快地调用后台时可能降低频率甚至暂停。这是应用的核心所在做游戏更新、渲染一帧画面。应当尽量快速返回但不必苛刻到只够跑一次 memcpy。不要在这里检查事件队列——事件处理有专门的SDL_AppEvent。③ 事件处理SDL_AppEventSDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event);每当有 SDL 事件到达时被调用。你不应该再调用SDL_PollEvent、SDL_PumpEvents等SDL 会替你管理这一切。返回值语义与SDL_AppIterate相同因此你可以直接在收到SDL_EVENT_QUIT时返回SDL_APP_SUCCESS来优雅退出。④ 退出清理SDL_AppQuitvoid SDL_AppQuit(void *appstate, SDL_AppResult result);在进程终止前除非被强杀或崩溃调用一次作为最后的清理机会。返回后 SDL 会自行调用SDL_Quit应用自己再调一次也安全。进程随后按从 main 正常返回的方式终止因此atexit钩子等仍会执行。如果之前在SDL_AppInit里分配了*appstate数据在这里释放——此后这个指针不会再回传给你。result参数会告诉你这次运行是成功还是失败结束便于区分处理。2.5 返回值语义SDL_AppResult三个返回值定义于 include/SDL3/SDL_init.h第 109~114 行语义如下返回值含义SDL_APP_CONTINUE请求应用继续运行主循环继续下一帧SDL_APP_SUCCESS请求以成功状态终止SDL 会调用SDL_AppQuit并以报告成功的退出码结束进程SDL_APP_FAILURE请求以错误状态终止SDL 会调用SDL_AppQuit并以报告错误的退出码结束进程2.6 一个完整的最小示例以 examples/renderer/01-clear/clear.c 为例它演示了四回调 逐帧渲染的完整流程——每帧用正弦波计算 RGB 颜色并清屏窗口呈现平滑渐变的色彩#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ #include SDL3/SDL.h #include SDL3/SDL_main.h static SDL_Window *window NULL; static SDL_Renderer *renderer NULL; /* 启动只调用一次 */ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_SetAppMetadata(Example Renderer Clear, 1.0, com.example.renderer-clear); if (!SDL_Init(SDL_INIT_VIDEO)) { SDL_Log(Couldnt initialize SDL: %s, SDL_GetError()); return SDL_APP_FAILURE; } if (!SDL_CreateWindowAndRenderer(examples/renderer/clear, 640, 480, SDL_WINDOW_RESIZABLE, window, renderer)) { SDL_Log(Couldnt create window/renderer: %s, SDL_GetError()); return SDL_APP_FAILURE; } SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX); return SDL_APP_CONTINUE; /* carry on with the program! */ } /* 事件收到退出事件即成功结束 */ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { if (event-type SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; } return SDL_APP_CONTINUE; } /* 主循环体每帧做一次更新 渲染 */ SDL_AppResult SDL_AppIterate(void *appstate) { const double now ((double)SDL_GetTicks()) / 1000.0; const float red (float)(0.5 0.5 * SDL_sin(now)); const float green (float)(0.5 0.5 * SDL_sin(now SDL_PI_D * 2 / 3)); const float blue (float)(0.5 0.5 * SDL_sin(now SDL_PI_D * 4 / 3)); SDL_SetRenderDrawColorFloat(renderer, red, green, blue, SDL_ALPHA_OPAQUE_FLOAT); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); return SDL_APP_CONTINUE; } /* 退出SDL 会替我们清理窗口与渲染器 */ void SDL_AppQuit(void *appstate, SDL_AppResult result) { }注意SDL_AppInit中两个值得学习的细节SDL_SetAppMetadata设置应用的可读名称、版本号与反向域名标识符用于移动端/桌面端系统集成SDL_SetRenderLogicalPresentation设置 640×480 的逻辑分辨率并使用 letterbox 模式保证窗口缩放时画面等比适配。再看一个事件驱动型的例子 examples/input/01-joystick-polling/joystick-polling.c它在SDL_AppEvent中监听SDL_EVENT_JOYSTICK_ADDED/SDL_EVENT_JOYSTICK_REMOVED实现热插拔打开/关闭摇杆并在SDL_AppIterate中每帧轮询摇杆轴、按钮、帽的状态绘制可视化图形同时用注释说明了轮询 vs 事件两种输入策略各自的适用场景轮询适合希望因延迟而丢弃输入的情形事件方式则不会因系统卡顿漏掉按键。它还示范了appstate之外更常见的做法——用文件级静态变量保存窗口/渲染器/设备句柄。2.7 关于 SDL_main.h 的历史与注意事项从 docs/README-main-functions.md 可知SDL 一直在用宏技巧抹平不同平台的入口差异传统做法是应用总是写标准mainSDL 头文件在需要WinMain等非标准入口的平台上把main悄悄改名为SDL_main再由 SDL 提供自己的入口调用它。SDL3 的变化是取消了 SDLmain 静态库改为单头文件库方案——你#include SDL3/SDL_main.h该头文件会把少量代码直接嵌入包含它的源文件无需再为某些平台额外链接一个库。使用要点官方明确的最佳实践只在一个源文件里包含SDL_main.h伞形头文件SDL.h不会包含它它会把main宏替换掉若你在别处把main当变量名使用会引发意外问题。如果包含该头文件但不想生成平台特有入口代码请先定义#define SDL_MAIN_NOIMPL从 SDL2 迁移从构建系统中移除 SDLmain 静态库引用即可其余照旧。若你完全由外部控制进程入口如把 SDL 嵌入脚本语言解释器或插件SDL_main.h是完全可选的——SDL 的入口代码没有任何必需项随时直接调用 SDL API 即可。非 C/C 语言使用回调入口会更复杂需要查阅官方非标准启动文档NonstandardStartup。三、构建与运行示例的三种方式3.1 方式一随 SDL 一起用 CMake 构建推荐官方文档 docs/README-cmake.md 给出明确指引构建 SDL 时在 CMake 命令行加上-DSDL_EXAMPLESOn示例会随 SDL 一起构建cmake -S . -B build -DSDL_EXAMPLESON cmake --build build对应的开关定义于 CMakeLists.txt第 435 行附近的set_option(SDL_EXAMPLES Build the examples directory)并配套一个派生选项SDL_EXAMPLES_LINK_SHARED默认跟随SDL_SHARED为真时示例链接共享版 SDL3SDL3-shared为假时链接静态版SDL3-static。从 examples/CMakeLists.txt 的实现可以看到每个示例通过add_sdl_example_executable宏注册例如add_sdl_example_executable(renderer-clear SOURCES renderer/01-clear/clear.c)带资源的示例用DATAFILES声明依赖如renderer-textures依赖../test/sample.png、audio-load-wav依赖../test/sample.wav资源文件会由copy-sdl-example-resources目标从test/目录复制到输出目录。平台差异化处理Android 下编译为SHARED库Apple 平台打包为带 Bundle IDorg.libsdl.target的.appEmscripten 下输出.html并启用-sALLOW_MEMORY_GROWTH1PSP 生成 EBOOT、RISC OS 生成 AIF 等。若设置SDL_INSTALL_EXAMPLES示例会被安装到CMAKE_INSTALL_LIBEXECDIR/installed-examples/SDL3MSVC 下同时安装 PDB。在 Android 构建链中还会为每个示例自动生成AndroidManifest.xml、Java 启动 Activity、执行 javac/d8 打包、对齐并签名生成 APK并提供install-example、start-example、build-install-start-example等便捷目标。3.2 方式二把单个 .c 文件当作普通程序编译原文档明确指出这些示例中的大多数都可以作为单个 .c 文件直接编译——前提是让你的编译器能找到 SDL3 的头文件并链接 SDL。例如cc -o clear clear.c $(pkg-config --cflags --libs sdl3)这得益于回调架构的零依赖特性每个示例都自带#define SDL_MAIN_USE_CALLBACKS与完整回调实现不依赖示例集内部的其他源文件。注意少数带DATAFILES的示例运行时需要把资源文件放在工作目录或可执行文件目录下例如renderer-textures需要sample.png、audio-load-wav需要sample.wav——这些资源都来自 test/。3.3 方式三Visual Studio / Emscripten 等平台构建Visual StudioWindows 用户可打开 VisualC/SDL.sln其中VisualC/examples/下为每个示例预生成了.vcxproj工程由VisualC/examples/generate.py脚本生成。官方在 examples/CMakeLists.txt 末尾记录了新增示例时同步维护 VS 工程的完整流程在examples/添加新示例 → 运行python VisualC/examples/generate.py→ 必要时修改生成的.vcxproj如补充 PNG/WAV 数据文件→ 在 Visual Studio / JetBrains Rider 中打开解决方案并添加现有项目→ 验证后保存。Emscripten / WebAssembly官方文档 docs/README-emscripten.md 说明构建示例只需在emcmake cmake命令行加上-DSDL_EXAMPLESON产物即 HTML 页面可在浏览器直接运行——这正是所有示例得以在官方示例站点上线的原因。其他平台examples/CMakeLists.txt中针对 PSP、PS2、N3DS、NGAGE、RISC OS、AppleiOS/macOS等均有对应的打包逻辑说明示例集覆盖面与 SDL 支持平台一致。四、示例代码的许可证公共领域原文档明确声明examples 目录下的所有代码都属于公共领域public domain。你可以对它们做任何事复制粘贴进闭源项目、出售、甚至宣称是自己写的官方不要求署名但当然感谢署名。注意该声明仅适用于 examples 目录——SDL 其余部分遵循 zlib 许可证zlib license。五、网页版本生成与目录中的辅助文件官方示例站点使用本目录中的两个文件生成示例的网页版本examples/template.html网页版示例的 HTML 模板外壳。examples/highlight-plugin.lua网页上的代码高亮插件。普通使用者可以完全忽略它们若你想改进网页版生成效果官方欢迎提交改进。另外两个辅助文件examples/template.c编写新示例时的骨架模板用于保持所有示例风格一致。其内容即本文 2.6 节展示的四回调空实现SDL_AppInit创建窗口渲染器、SDL_AppEvent响应 QUIT、SDL_AppIterate空转、SDL_AppQuit留空新示例从它出发填入业务逻辑即可。examples/categories.txt定义示例主页上分类的展示顺序renderer → input → audio → storage → camera → asyncio → pen → misc → demo若该文件缺失网页生成器会把任意子目录当作分类并按字母序排列。六、缩略图 / onmouseover 演示媒体的制作流程原文档据作者自述这是每次都要重新回忆的流程完整记录了如何为示例生成thumbnail.png缩略图与onmouseover.webp悬停预览视频本仓库的 examples/save-rendering-to-bitmaps.h 正是该流程的关键工具。完整步骤如下清空旧帧文件rm -f frame*.png在示例程序所有 SDL include 之后临时加入一行#include ../../save-rendering-to-bitmaps.h该头文件的实现机制是用宏把SDL_RenderPresent重定义为SAVERENDERING_SDL_RenderPresent后者在每帧呈现前通过SDL_RenderReadPixels读回渲染结果、SDL_ConvertSurface转为 RGBX32 后SDL_SavePNG落盘生成frame00000.png、frame00001.png……的逐帧位图序列第 41 行用SDL_snprintf生成五位数帧名。运行示例应用与它交互让它运行几秒钟然后退出——此时会为渲染过的每一帧 dump 一个frameX.png。用 ffmpeg 把位图序列合成 webp 视频假设位图按 60fps 录制可能需要微调参数ffmpeg -framerate 60 -pattern_type glob -i frame*.png -loop 0 -quality 40 -r 10 -frames:v 40 onmouseover.webp可能需要从视频中段开始、或调整质量与生成帧数视效果而定ymmv即结果因人而异。挑选一帧作为缩略图转成 png 后用 pngquant 做大幅体积压缩几乎无肉眼可见的画质损失convert frame00000.png cvt.png ; pngquant cvt.png --output thumbnail.png ; rm -f cvt.png仓库中 examples/audio/05-planar-data/thumbnail.png 与 examples/audio/05-planar-data/onmouseover.webp 即该流程的成品样例各示例目录下的thumbnail.png/onmouseover.webp/description.txt示例文字简介共同构成了官方示例站点的展示素材。七、要点回顾与最佳实践结合 docs/README-main-functions.md 末尾的官方总结SDL_main.h 只在项目的一个源文件中包含多文件包含会导致冲突与未定义行为。不要自己定义main回调模式下SDL 会处理入口重命名重复定义会在链接阶段引发问题。需要精细控制应用流程时优先考虑回调系统定义好四个回调后SDL 自动接管初始化、事件处理与清理。平台入口差异交给 SDLWindows 的WinMain等由 SDL 自动处理无需编写平台特判代码。何时可以不用SDL_main.h将 SDL 嵌入既有应用或脚本环境时可不包含但会失去 SDL 对平台入口差异的抽象。示例代码可直接借鉴由于 examples 目录是公共领域本文 2.6 节的完整程序可直接复制进你自己的项目只需按需修改SDL_AppInit中的元数据、初始化标志与SDL_AppIterate里的渲染逻辑。延伸阅读仓库内docs/README-main-functions.md主回调机制的权威技术文档docs/README-cmake.mdCMake 构建选项总览含-DSDL_EXAMPLESONdocs/README-emscripten.mdEmscripten 网页构建指南include/SDL3/SDL_main.h 与 include/SDL3/SDL_init.h回调函数声明与SDL_AppResult枚举定义examples/template.c 与 examples/save-rendering-to-bitmaps.h示例骨架模板与逐帧截图工具【免费下载链接】SDLSimple DirectMedia Layer项目地址: https://gitcode.com/GitHub_Trending/sd/SDL创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考