本章内容我们来讲解一下在 Windows 窗口上如何使用 SDL 渲染图像。在原来的文章中我曾介绍过如何使用 Direct2D 进行图形渲染,SDL 内部原理和它类似,只不过 SDL 经过二次封装,使用起来更加的简单。在 Direct2D 中存在设备相关资源和设备无关资源之说,而 SDL 中则直接将它们统一封装到 SDL_Renderer 结构体中,所有的渲染操作都是在它基础之上完成的。

创建 SDL_Renderer 结构体的方法和创建 Windows 窗口类似:

SDL_Renderer* SDL_CreateRenderer(SDL_Window* window,
                                 int         index,
                                 Uint32      flags)

因为图形渲染是在窗口之上的,所以这个函数的第一个参数 window 用来指定渲染的目标窗口。第二个参数是指定渲染设备的索引标识,第三个参数 flags 代表一些常用的渲染标记位。

SDL 是对现有图形接口的二次封装,所以在整合的过程中肯定存在一些常见的取舍,而不同平台的渲染接口又不相同,所以在现有平台上选择什么样的渲染接口成为了一个需要抉择的问题,在 SDL 的源码中,可以看到这样一个静态数组成员:

static const SDL_RenderDriver *render_drivers[] = {
#if SDL_VIDEO_RENDER_D3D
    &D3D_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_D3D11
    &D3D11_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_METAL
    &METAL_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_OGL
    &GL_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_OGL_ES2
    &GLES2_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_OGL_ES
    &GLES_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_DIRECTFB
    &DirectFB_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_PSP
    &PSP_RenderDriver,
#endif
    &SW_RenderDriver
};

数组的每一项代表一种可用的渲染设备,只不过在不同的平台上支持的程度略有差异,例如在 Linux 平台上就不支持 Direct3D 的相关接口。在 SDL_CreateRenderer 第二个参数指定的就是这个数组的索引值,事实上在程序中大部分情况下可以直接将 index 的值设为 -1,让 SDL 自动选择当前操作系统最合适的渲染设备即可。

创建渲染设备的代码非常简单:

SDL_Renderer* pRenderer = SDL_CreateRenderer(pWin, -1, SDL_RENDERER_ACCELERATED);

如果函数成功,则会返回一个有效的 SDL_Renderer 指针。函数的第三个参数 SDL_RENDERER_ACCELERATED 代表在创建渲染器的时候优先选择支持硬件加速的渲染设备。如果你将这个参数设为 0, SDL 默认同样会优先选择支持硬件加速的设备,所以你可以认为 SDL_RENDERER_ACCELERATED 标记代表默认值。

程序退出之前不要忘记释放已经创建的渲染设备:

if (pRenderer) {
	SDL_DestroyRenderer(pRenderer);
}

仅仅知道创建销毁渲染器显然是无意义的,你需要在程序中使用这些渲染器绘制一些图形图像才算是成功。因为游戏的图像大多是实时显示的,所以每次渲染的过程都会有一个标准的流程:

  1. 清除背景
  2. 渲染图像
  3. 显示图像

这个流程几乎适合任何一款游戏的渲染。在 SDL 中你可以通过函数 SDL_RenderClear 清除当前的渲染目标:

SDL_RenderClear(pRenderer);

默认的情况下,SDL_RenderClear 会使用黑色来清空,你可以通过函数 SDL_SetRenderDrawColor 设置默认的渲染颜色。

如果你只执行了清空动作,在窗口下你是看不到任何颜色变化的,因为任何的渲染改变都需要执行 SDL_RenderPresent 函数来呈现结果。在 SDL 中使用的是一种叫做“双缓冲”的技术,所有的渲染都是在后台缓冲中进行的,当渲染完成后,通过调用 SDL_RenderPresent 函数可以将后台缓冲的结果渲染到前台窗口上,当然这种缓冲技术并不局限于“一双”,你可以有一系列的缓冲区进行绘制,只不过原理都和双缓冲类似,只是缓冲区的个数不同而已。

除了清除背景和呈现渲染结果外,渲染的核心就是如何渲染各种各样的图形图像,具体的内容留在下一篇文章单独阐述。

下面是图形渲染的基本流程代码:

//main.c

#include <Windows.h>
#include "SDL.h"

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR lpCmdLine, INT nCmdShow)
{
	int ret = SDL_Init(SDL_INIT_EVERYTHING);
	if (ret != 0) {
		return -1;
	}

	SDL_Window* pWin = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
	if (pWin == NULL) {
		return -2;
	}

	SDL_Renderer* pRenderer = SDL_CreateRenderer(pWin, -1, SDL_RENDERER_ACCELERATED);
	if (pRenderer == NULL) {
		SDL_DestroyWindow(pWin);
		return -3;
	}

	int quit = 0;
	SDL_Event evt;

	while (!quit) {
		if (SDL_PollEvent(&evt)) {
			if (evt.type == SDL_QUIT) {
				quit = 1;
			}
		}
		else {
			//清除背景
			SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
			SDL_RenderClear(pRenderer);

			//TODO:渲染图像


			//显示图像
			SDL_RenderPresent(pRenderer);
		}
	}
	
	if (pRenderer) {
		SDL_DestroyRenderer(pRenderer);
	}

	if (pWin) {
		SDL_DestroyWindow(pWin);
	}
	
	SDL_Quit();

	return 0;
}