1. 软渲染起步前,先把SDL这杆枪擦亮
很多人学图形学,一上来就想着写光栅化、写矩阵变换、写深度测试,结果第一步就卡在“窗口怎么弹出来”上。我当初也是这么过来的,C++ 标准库本身不带任何图形界面能力,你总不能每次调试都往控制台里 printf 一堆字符画吧。所以软渲染这条路的第一道门槛,不是数学,不是算法,而是环境配置。SDL 就是帮你把窗口、画布、事件循环、输入这些脏活累活全包了的那层壳,你只需要往一块像素缓冲区里写颜色,剩下的交给它。
这篇内容适合谁看?如果你正在用 C++ 手写软渲染器,或者准备从零搭一个光栅化管线,又或者你之前用过 OpenGL 但想回到 CPU 端把原理吃透,那 SDL 这套环境配置就是你绕不开的第一关。我会把 Windows 下用 Visual Studio 和 MinGW 两条路都讲清楚,顺带把 CMake 的写法、动态库和静态库的取舍、以及那些让人抓狂的链接错误一次性说明白。你照着做,半小时内应该能看到一个能响应键盘、能逐像素刷新的窗口。
先说结论:SDL 的配置本身不复杂,复杂的是编译器和库的匹配问题。你下载的 SDL 开发包分 VC 版和 MinGW 版,位数分 32 位和 64 位,链接方式分动态和静态,这四者只要有一个对不上,就是一堆 undefined reference 或者 LNK2019。我踩过的坑基本都集中在这里,所以下面会把每个选择背后的理由讲透,而不是甩给你一个“照做就行”。
2. 为什么软渲染偏偏选 SDL 而不是别的
2.1 软渲染对窗口库的真实需求是什么
软渲染的核心工作是在 CPU 上算好每一个像素的颜色,然后把这堆像素一次性或者分块送到屏幕上。它对窗口库的要求其实很朴素:能创建一个指定分辨率的窗口,能拿到一块可以逐像素写入的缓冲区,能把这块缓冲区的内容显示出来,能处理键盘鼠标事件,最好还能精确控制帧率。就这些,不需要 GPU 加速,不需要复杂的场景图,不需要着色器编译。
那为什么不直接用操作系统原生 API?Windows 上你可以用 Win32 API 创建窗口,用 GDI 或者 DirectDraw 把像素刷上去。理论上可行,但代码量会爆炸。创建一个 Win32 窗口要注册窗口类、写消息循环、处理 WM_PAINT,光是样板代码就上百行,而且跨平台基本没戏。你在 Windows 上写的一套,换到 Linux 就得用 X11 重写一遍,换到 macOS 又得用 Cocoa。对于学习软渲染来说,这些平台细节完全是噪音。
SDL 的价值就在于它把这层噪音屏蔽掉了。同一套代码,Windows、Linux、macOS 都能编译运行,你只需要关心你的像素缓冲区。而且 SDL 提供了一个非常关键的东西:SDL_Texture 配合 SDL_RenderCopy,或者更底层的SDL_UpdateTexture,让你能把一块内存里的像素数据高效地送到窗口上。对于软渲染来说,这就是最理想的接口形态。
2.2 SDL 和 GLFW、Qt 这些库的取舍逻辑
有人会问,GLFW 不是也能创建窗口吗?Qt 不是更强大吗?这里要说清楚定位差异。GLFW 的设计目标是配合 OpenGL,它创建窗口后会给你一个 OpenGL 上下文,重点在于管理 GPU 渲染环境。你用它做软渲染当然也行,但它的像素上传接口没有 SDL 那么直接,而且 GLFW 本身对 2D 像素操作的支持比较薄。
Qt 则是重型框架,功能全但体量大,编译慢,依赖多。你只是想弹个窗口画像素,引入 Qt 属于杀鸡用牛刀,而且 Qt 的事件循环和渲染机制有自己的抽象层,反而会增加理解成本。
SDL 的定位刚好卡在中间:足够轻量,API 是纯 C 的,容易理解;又足够完整,窗口、渲染、事件、音频、计时全都覆盖。对于软渲染这种“我要自己控制每一个像素”的场景,SDL 的SDL_Renderer配合SDL_Texture的流式更新模式,几乎是量身定做的。你可以创建一个SDL_TEXTUREACCESS_STREAMING的纹理,每帧把 CPU 算好的像素数据更新进去,然后一次SDL_RenderCopy刷到屏幕。这个流程简单直接,没有多余的抽象。
2.3 动态链接和静态链接到底怎么选
这是配置阶段第一个要做的决策。SDL 官方提供两种库文件:动态库(.dll + .lib 或 .so)和静态库(.lib 或 .a)。动态链接的好处是编译出来的 exe 小,多个程序共享一份 SDL 运行库;坏处是发布时要带上 dll,而且 dll 版本和 lib 版本必须严格对应,否则运行时直接崩。
静态链接的好处是编译出来的 exe 自包含,拷到别的机器上直接能跑,不用担心 dll 丢失;坏处是 exe 体积会大一些,而且如果多个库都静态链接了不同版本的 C 运行时,可能会有冲突。
对于软渲染学习项目,我的建议是优先用静态链接。原因很简单:你是在学习,不是在发布商业软件。静态链接省去了“dll 放哪”“运行时找不到库”这类破事,你编译完一个 exe,双击就能跑,换台电脑也能跑。等你以后要做正式项目了,再考虑动态链接的部署灵活性。
不过要注意,SDL 的静态库在 Windows 下需要额外链接一些系统库,比如winmm、gdi32、ole32等,这些在后面的链接器配置里会提到。如果你用 CMake,SDL 的配置文件会自动帮你处理这些依赖,这也是我推荐用 CMake 的原因之一。
3. 下载与目录结构:别小看这一步
3.1 选对开发包版本
去 SDL 的官方发布页,你会看到一堆文件。以 SDL2 为例,Windows 下通常有这几个:
| 文件名 | 适用场景 | 说明 |
|---|---|---|
| SDL2-devel-x.x.x-VC.zip | Visual Studio | 包含 VC 用的 .lib 和头文件 |
| SDL2-devel-x.x.x-mingw.zip | MinGW / GCC | 包含 GCC 用的 .a 和头文件 |
| SDL2-x.x.x-win32-x86.zip | 32 位运行库 | 只有 dll,给最终用户用 |
| SDL2-x.x.x-win32-x64.zip | 64 位运行库 | 只有 dll,给最终用户用 |
关键点:devel 包是给开发者用的,包含头文件和链接库;非 devel 包只有运行库。你配置开发环境要下 devel 包。然后根据你的编译器选 VC 版还是 MinGW 版,根据你的目标平台选 32 位还是 64 位。现在基本都是 64 位系统了,除非你有特殊需求,否则一律选 x64。
我见过有人下了 VC 版的库,却用 MinGW 编译,然后报一堆链接错误,折腾半天才发现是库格式不对。VC 的 .lib 是 COFF 格式,MinGW 的 .a 是 ar 格式,两者不通用。所以第一步就要确认你的工具链。
3.2 目录怎么放才不乱
下载解压后,你会看到一个类似这样的结构:
SDL2-2.30.0/ ├── include/ │ └── SDL2/ │ ├── SDL.h │ ├── SDL_render.h │ └── ... ├── lib/ │ ├── x64/ │ │ ├── SDL2.lib │ │ ├── SDL2main.lib │ │ └── ... │ └── x86/ │ └── ... └── ...我的习惯是在项目根目录下建一个third_party或者external文件夹,把 SDL 整个放进去,比如third_party/SDL2/。这样做的好处是项目自包含,换电脑或者传给别人的时候,整个文件夹拷过去就能编译,不依赖系统里装没装 SDL。
注意:不要把 SDL 的头文件和库文件散落到系统目录里。虽然那样配置一次以后省事,但一旦你同时维护多个项目,不同项目依赖不同版本的 SDL,就会互相打架。项目内自包含是更稳妥的做法。
如果你用 Visual Studio,还有一种做法是通过“属性管理器”配置全局的包含目录和库目录。但我个人不推荐,因为这样配置信息存在 VS 的全局设置里,项目文件本身不包含这些路径,换台机器或者换个人就编译不过。把路径写进项目属性或者 CMakeLists.txt 里,才是可复现的做法。
4. Visual Studio 下的完整配置流程
4.1 创建项目与基础设置
打开 VS,新建一个“空项目”(Empty Project),语言选 C++。不要选“控制台应用”模板,因为那个模板会给你生成一个带main的 cpp 文件,而 SDL 在 Windows 下对入口点有特殊处理,我们后面会讲。
项目建好后,右键项目 -> 属性,打开属性页。注意属性页左上角的“配置”和“平台”两个下拉框。配置一般选“所有配置”,平台选“x64”(或者你需要的平台)。这样改一次,Debug 和 Release 都生效,省得改两遍。
4.2 头文件路径与库路径配置
在“VC++ 目录”这一页,找到“包含目录”,添加 SDL 的 include 路径。注意要加到SDL2这一层,也就是third_party/SDL2/include,因为你的代码里会写#include <SDL.h>,而不是#include <SDL2/SDL.h>。如果你加到了include/SDL2这一层,那代码里就得写#include <SDL2/SDL.h>,两种写法都行,但要和你的 include 路径匹配。
然后在“库目录”里,添加third_party/SDL2/lib/x64。注意选对 x64 还是 x86,要和你的平台下拉框一致。
4.3 链接器输入的库名
切到“链接器” -> “输入” -> “附加依赖项”,添加:
SDL2.lib SDL2main.lib如果你用的是静态链接,还要加上SDL2-static.lib,并且可能需要额外加系统库。这里先说动态链接的情况,静态链接后面单独讲。
SDL2main.lib是干什么的?在 Windows 下,SDL 为了让你的main函数能正常工作,做了一个入口点的重定向。标准的 Windows 程序入口是WinMain,但 SDL 允许你写标准的int main(int argc, char* argv[]),它通过SDL2main.lib里的代码把WinMain转接到你的main。所以如果你不加SDL2main.lib,会报“无法解析的外部符号 WinMain”。
实操心得:如果你用的是 SDL2 的 CMake 配置,
SDL2::SDL2main这个 target 会自动处理入口点问题。但如果你手动配置,就必须显式链接SDL2main.lib,而且它必须放在SDL2.lib前面(链接顺序有讲究,后面会讲)。
4.4 子系统设置与入口点
在“链接器” -> “系统” -> “子系统”里,选“控制台”还是“Windows”?如果你希望程序运行时弹出一个控制台窗口方便看日志,就选“控制台”。如果你想要纯图形界面,就选“Windows”。对于学习阶段,我建议选“控制台”,因为你可以用printf或者std::cout输出调试信息,非常方便。
但这里有个坑:如果你选了“Windows”子系统,又用了SDL2main.lib,入口点会被重定向,main能正常工作。如果你选了“控制台”子系统,入口点本来就是main,SDL2main.lib也能兼容。两种组合都能跑,但如果你手动写了WinMain又链接了SDL2main.lib,就会冲突。所以最简单的做法是:写标准main,链接SDL2main.lib,子系统选控制台。
4.5 运行时 dll 的放置
动态链接的情况下,编译能过,但运行时需要SDL2.dll。这个 dll 在 devel 包的lib/x64目录下(有些版本在bin目录下)。你需要把它拷到 exe 所在的目录,也就是你的项目输出目录,通常是x64/Debug或x64/Release。
我见过太多人编译成功,一运行就报“找不到 SDL2.dll”,然后一脸懵。解决办法有两个:一是手动拷 dll 到输出目录;二是在 VS 的“生成事件” -> “生成后事件”里加一条命令,自动拷贝。比如:
xcopy /Y "$(SolutionDir)third_party\SDL2\lib\x64\SDL2.dll" "$(OutDir)"这样每次编译完自动拷贝,省心。如果你用静态链接,就没有这个问题。
5. MinGW 与 CMake 的配置路线
5.1 MinGW 下的库文件差异
MinGW 用的是 GCC 工具链,库文件是.a格式。SDL 的 MinGW 开发包里,lib目录下会有libSDL2.a、libSDL2main.a、libSDL2.dll.a等。这里要区分:
libSDL2.a:静态库,直接链接进 exe。libSDL2.dll.a:动态库的导入库,链接后运行时需要SDL2.dll。libSDL2main.a:入口点处理,和 VC 的SDL2main.lib作用相同。
如果你要静态链接,链接libSDL2.a和libSDL2main.a,并且要加一堆系统库。如果你要动态链接,链接libSDL2.dll.a和libSDL2main.a,运行时带上 dll。
MinGW 的链接命令大概长这样:
g++ main.cpp -o app.exe -Ithird_party/SDL2/include -Lthird_party/SDL2/lib/x64 -lSDL2main -lSDL2 -mwindows注意-lSDL2main要放在-lSDL2前面,因为链接器从左到右解析,SDL2main里引用了SDL2的符号,顺序反了会报未定义。-mwindows是告诉链接器生成 Windows 子系统程序,不弹控制台。如果你想要控制台,就去掉-mwindows。
5.2 用 CMake 统一管理依赖
手动配置 VS 属性或者写 g++ 命令,项目小的时候还行,一旦文件多了、依赖多了,就会很乱。CMake 的好处是用一份CMakeLists.txt描述整个项目的构建规则,跨平台、跨编译器都能用。而且 SDL2 提供了 CMake 的配置文件,可以很方便地通过find_package找到。
一个典型的CMakeLists.txt长这样:
cmake_minimum_required(VERSION 3.15) project(SoftRenderer CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 告诉 CMake 去哪里找 SDL2 的配置文件 set(SDL2_DIR "${CMAKE_SOURCE_DIR}/third_party/SDL2/cmake") find_package(SDL2 REQUIRED) add_executable(SoftRenderer src/main.cpp src/renderer.cpp ) target_include_directories(SoftRenderer PRIVATE ${SDL2_INCLUDE_DIRS} ) target_link_libraries(SoftRenderer PRIVATE SDL2::SDL2main SDL2::SDL2 )这里的关键是SDL2_DIR指向 SDL 包里的cmake目录,里面有个sdl2-config.cmake文件,find_package会读它。然后SDL2::SDL2main和SDL2::SDL2这两个 target 会自动带上包含路径、库路径、链接库和系统依赖。你不需要手动写-lSDL2或者SDL2.lib,CMake 全帮你处理了。
注意:SDL2 的 CMake 配置文件在不同版本里位置可能不同,有的在
lib/cmake/SDL2,有的在cmake。你解压后搜一下sdl2-config.cmake在哪,把SDL2_DIR指过去就行。
5.3 静态链接在 CMake 下的写法
如果你要静态链接,CMake 里可以这样写:
set(SDL2_DIR "${CMAKE_SOURCE_DIR}/third_party/SDL2/cmake") set(SDL2_STATIC ON) find_package(SDL2 REQUIRED) target_link_libraries(SoftRenderer PRIVATE SDL2::SDL2main SDL2::SDL2-static )注意 target 名字变成了SDL2::SDL2-static。有些版本的 SDL2 CMake 配置里,静态库的 target 名字是SDL2::SDL2-static,动态库是SDL2::SDL2。你可以在sdl2-config.cmake里看到具体定义了哪些 target。
静态链接时,SDL2 的 CMake 配置会自动帮你链接winmm、gdi32、ole32、oleaut32、imm32、version、uuid、setupapi这些 Windows 系统库。如果你手动配置,就得自己加,漏一个就是一堆未定义符号。
6. 第一个能跑的软渲染骨架
6.1 最小可运行代码
环境配好了,总得写点东西验证一下。下面这段代码创建一个窗口,拿到一个流式纹理,每帧往纹理里写渐变像素,然后刷到屏幕。这是软渲染最基础的骨架,你可以直接拿它当起点。
#include <SDL.h> #include <cstdint> #include <vector> int main(int argc, char* argv[]) { if (SDL_Init(SDL_INIT_VIDEO) != 0) { SDL_Log("SDL_Init failed: %s", SDL_GetError()); return -1; } const int WIDTH = 800; const int HEIGHT = 600; SDL_Window* window = SDL_CreateWindow( "Soft Renderer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN ); if (!window) { SDL_Log("CreateWindow failed: %s", SDL_GetError()); SDL_Quit(); return -1; } SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); if (!renderer) { SDL_Log("CreateRenderer failed: %s", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); return -1; } SDL_Texture* texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT ); if (!texture) { SDL_Log("CreateTexture failed: %s", SDL_GetError()); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return -1; } std::vector<uint32_t> pixels(WIDTH * HEIGHT); bool running = true; SDL_Event event; while (running) { while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { running = false; } if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) { running = false; } } // 填充像素:简单的水平渐变 for (int y = 0; y < HEIGHT; ++y) { for (int x = 0; x < WIDTH; ++x) { uint8_t r = static_cast<uint8_t>(x * 255 / WIDTH); uint8_t g = static_cast<uint8_t>(y * 255 / HEIGHT); uint8_t b = 128; pixels[y * WIDTH + x] = (0xFF << 24) | (r << 16) | (g << 8) | b; } } SDL_UpdateTexture(texture, nullptr, pixels.data(), WIDTH * sizeof(uint32_t)); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, nullptr, nullptr); SDL_RenderPresent(renderer); } SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }6.2 关键参数与像素格式解析
这段代码里有几个关键点值得展开。首先是SDL_PIXELFORMAT_ARGB8888,这是纹理的像素格式。ARGB 表示每个像素 32 位,内存布局是蓝、绿、红、透明度(小端序下)。你往pixels数组里写的uint32_t值,要按照这个格式组装。上面代码里(0xFF << 24)是 alpha,(r << 16)是红色,(g << 8)是绿色,b是蓝色。如果你写反了,颜色就会不对。
其次是SDL_TEXTUREACCESS_STREAMING,这个标志告诉 SDL 这块纹理会频繁更新,SDL 会为它选择合适的内存位置(通常是系统内存而不是显存),以便快速上传。如果你用SDL_TEXTUREACCESS_STATIC,更新会慢很多,因为每次都要走一次完整的上传流程。
SDL_UpdateTexture的第四个参数是 pitch,也就是一行像素占多少字节。这里WIDTH * sizeof(uint32_t)就是 800 * 4 = 3200 字节。如果你在像素缓冲区里加了行对齐(比如为了 SIMD 对齐到 16 字节),pitch 就要相应调整。SDL 允许 pitch 大于实际宽度,这样你可以有 padding。
6.3 帧率控制与事件循环
上面这个循环是“能跑多快跑多快”,CPU 会满载。实际软渲染项目里,你需要控制帧率,否则风扇狂转。最简单的做法是用SDL_GetTicks算时间差,然后SDL_Delay补足到目标帧时间。比如目标 60 帧,每帧 16.67 毫秒,如果这一帧算了 5 毫秒,就 delay 11 毫秒。
uint32_t frameStart = SDL_GetTicks(); // ... 渲染逻辑 ... uint32_t frameTime = SDL_GetTicks() - frameStart; if (frameTime < 16) { SDL_Delay(16 - frameTime); }更精确的做法是用SDL_GetPerformanceCounter和SDL_GetPerformanceFrequency,这两个函数提供高精度计时,适合做性能分析。但要注意,SDL_Delay的精度受操作系统调度影响,Windows 下大概有 1 到 15 毫秒的误差。如果你需要更精确的帧率控制,可以用忙等待(busy wait)配合SDL_Delay,但会浪费 CPU。
事件循环这里用的是SDL_PollEvent,非阻塞地取出所有待处理事件。如果你用SDL_WaitEvent,程序会阻塞在那里等事件,不适合需要持续渲染的场景。软渲染通常需要每帧都重绘,所以用SDL_PollEvent轮询。
7. 那些让人抓狂的链接错误与排查实录
7.1 常见错误速查表
| 错误信息 | 根本原因 | 解决方法 |
|---|---|---|
| LNK2019: 无法解析的外部符号 WinMain | 没有链接 SDL2main.lib | 在附加依赖项里加 SDL2main.lib |
| LNK2019: 无法解析的外部符号 SDL_Init | 没有链接 SDL2.lib 或库路径不对 | 检查库目录和附加依赖项 |
| LNK1112: 模块计算机类型 x64 与目标计算机类型 x86 冲突 | 库的位数和项目平台不一致 | 统一用 x64 或 x86 |
| 运行时提示找不到 SDL2.dll | 动态链接但 dll 不在 exe 目录 | 拷贝 dll 到输出目录 |
| undefined reference to `SDL_Init' | MinGW 下链接顺序错误 | 把 -lSDL2main 放在 -lSDL2 前面 |
| 编译报错 SDL.h not found | 包含目录配置错误 | 检查包含路径是否指向 include 目录 |
7.2 链接顺序为什么这么重要
链接器处理库文件的顺序是从左到右,遇到未解析的符号时,会去后面的库里找。如果SDL2main引用了SDL2里的符号,而SDL2在SDL2main前面,链接器处理SDL2时还不知道后面有符号需要它,就会跳过,导致未解析。所以顺序必须是SDL2main在前,SDL2在后。
这个规则在 GCC 和 MSVC 下都适用,但 MSVC 的链接器稍微宽容一些,有时候顺序反了也能过。不过为了保险,还是按正确顺序写。CMake 的 target 依赖关系会自动处理顺序,这也是用 CMake 的一个好处。
7.3 静态链接时的系统库依赖
静态链接 SDL2 时,除了SDL2-static.lib,还需要链接一堆 Windows 系统库。VC 下大概需要:
winmm.lib gdi32.lib ole32.lib oleaut32.lib imm32.lib version.lib uuid.lib setupapi.libMinGW 下对应的库名是-lwinmm -lgdi32 -lole32 -loleaut32 -limm32 -lversion -luuid -lsetupapi。漏掉任何一个,都会报一堆未定义符号。这些库分别提供多媒体计时、图形设备接口、COM 支持、输入法、版本信息、UUID 生成、设备安装等功能,SDL 内部会用到。
如果你用 CMake 的SDL2::SDL2-statictarget,这些依赖会自动加上,不用手动写。这也是我推荐 CMake 的原因之一。
7.4 运行时崩溃与 dll 版本不匹配
动态链接时,如果 exe 目录下的SDL2.dll版本和编译时用的SDL2.lib版本不一致,可能会在运行时崩溃,而且崩溃位置往往很奇怪,比如在SDL_Init里挂掉。这种问题最难查,因为编译链接都没问题,一运行就崩。
解决办法是确保 dll 和 lib 来自同一个开发包。你下载的 devel 包里,lib/x64下有SDL2.lib,lib/x64或者bin下有SDL2.dll,这两个必须配套。不要从别的地方随便拷一个 dll 过来用。
实操心得:我习惯在项目里建一个
post_build脚本,编译后自动把 dll 拷到输出目录,并且打印 dll 的版本号(用SDL_GetVersion在程序启动时打印)。这样一旦版本不对,启动日志里就能看到。
8. 从环境配置到软渲染管线,下一步怎么走
环境配好、窗口能弹、像素能刷,这只是万里长征第一步。接下来你要做的是把像素缓冲区的填充逻辑从“渐变测试”换成真正的光栅化。你会需要一个Framebuffer类来管理像素数组和深度缓冲,需要一个Renderer类来封装 SDL 的纹理更新和显示,还需要一个数学库来处理向量、矩阵、齐次坐标。
我的建议是先把Framebuffer和Renderer的接口设计好,让主循环只负责调用renderer->present(),具体的像素操作全在Framebuffer里。这样你后面加三角形光栅化、纹理采样、深度测试的时候,主循环不用改,只改Framebuffer的填充逻辑就行。
另外,SDL 的SDL_Renderer其实还支持直接画线、画矩形,但那些是 GPU 加速的,和你的软渲染管线不是一回事。你要做的是完全绕过这些,只把SDL_Renderer当成一个“把纹理刷到屏幕”的工具,所有几何计算、像素填充都在 CPU 端自己完成。这样才能真正理解光栅化的每一个细节。
最后分享一个我踩过的坑:早期我用SDL_TEXTUREACCESS_STATIC创建纹理,然后每帧SDL_UpdateTexture,结果帧率只有个位数。换成SDL_TEXTUREACCESS_STREAMING之后,帧率直接翻了好几倍。原因是 streaming 模式下 SDL 会用更高效的上传路径,而 static 模式每次更新都要重新分配和拷贝。这个细节在 SDL 文档里写得很清楚,但很容易被忽略。