Lesson 1: 编写Hello World
在这一节课,我们将学习如何创建一个窗口,创建一个着色正文(rendering context)和绘制一个图像文件到屏幕上。下载下面的这个bmp图像到项目中去,让我们开始吧。
开始使用SDL
为了使用SDL,我们先初始化我们需要使用的SDL子系统。下面的操作是通过设置SDL_Init的参数(SDL_INIT_VIDEO)获得需要的子系统。当前我们只需要video子系统,但是还有更多更多类似的参数我们将来可能会用到。请注意,默认情况下文件I/O和线程系统被初始化,如果video系统没有显示请求,则会自动化初始化事件处理系统。如果一切顺利,SDL_Init返回0,如果不是,我们希望打印错误并返回。
if (SDL_Init(SDL_INIT_VIDEO) != 0){
std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl;
return 1;
}
创建窗口
程序需要一个窗口来显示渲染的内容,我们使用SDL_CreateWindow来创建一个窗口。这个方法的参数有“窗口标题”、“x(左上角的横坐标)”、“y(左上角的纵坐标)”、”窗口宽度“、”窗口高度"、“标志位”。创建窗口出现错误了,会返回一个NULL的指针。如果出现错误则需要在退出程序之前清理SDL。
SDL_Window *win = SDL_CreateWindow("Hello World!", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
if (win == nullptr){
std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
SDL_Quit();
return 1;
}
创建渲染器
我们需要使用SDL_CreateRenderer创建一个渲染器(renderer)去为窗口绘制内容。这个函数将窗口、窗口渲染器、要使用的渲染驱动程序的索引和其他各种标志相关联。在这里。我们要求硬件加速启用了垂直同步的渲染器。如果出现什么异常情况,我们会获得一个空的SDL_Renderer指针。如果出现错误则需要在退出程序之前清理SDL。
SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (ren == nullptr){
SDL_DestroyWindow(win);
std::cout << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
SDL_Quit();
return 1;
}
载入位图
加载一个bmp格式的图像,我们首先需要将它加载进内存,然后再将它加载到我们使用的渲染平台上(在本例中是GPU)。我们使用SDL_LoadBMP加载图像会获得一个SDL_Surface的指针。我们可以把这个指针加载到SDL_Texture,使得渲染器能够使用图像。
SDL_LoadBMP需要图像的路径,你需要根据项目的结果修改源码中的数据,否则出错会返回一个空指针。
std::string imagePath = getResourcePath("Lesson1") + "hello.bmp";
SDL_Surface *bmp = SDL_LoadBMP(imagePath.c_str());
if (bmp == nullptr){
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
std::cout << "SDL_LoadBMP Error: " << SDL_GetError() << std::endl;
SDL_Quit();
return 1;
}
图像加载后,我们现在可以使用SDL_CreateTextureFromSurface加载图像到渲染器。我们通过在渲染上下文中上传和内存中的图像 (SDL_Surface), 并得到加载的纹理, 如果有什么错误, 我们会得到返回 NULL。如果出错,我们需要释放内存。
SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, bmp);
SDL_FreeSurface(bmp);
if (tex == nullptr){
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
std::cout << "SDL_CreateTextureFromSurface Error: " << SDL_GetError() << std::endl;
SDL_Quit();
return 1;
}
纹理绘制
最后,我们需要做的是将纹理(Texture)绘制到屏幕上!
首先我们需要先清除渲染器,然后渲染我们的纹理,使屏幕更新以呈现新的结果。当我们想要渲染整个图像并使其伸展以填充屏幕,我们将SDL_RenderCopy的源和目标矩阵设置为NULL。最后我们调用SDL_Delay去保持窗口的显示状态,以便看到结果。
我们将全部的渲染代码放进程序的主循环中(一个简单的for循环)。每次迭代通过我们的循环,程序会睡眠1秒钟。我们也可以通过修改睡眠的时间来修改程序的运行时长。当我们得到事件处理,例如用户是否要退出我们的程序 (eg. 单击了窗口中的 X), 我们将跟踪一个布尔值并修改, 然后退出循环。
//A sleepy rendering loop, wait for 3 seconds and render and present the screen each time
for (int i = 0; i < 3; ++i){
//First clear the renderer
SDL_RenderClear(ren);
//Draw the texture
SDL_RenderCopy(ren, tex, NULL, NULL);
//Update the screen
SDL_RenderPresent(ren);
//Take a quick break after all that hard work
SDL_Delay(1000);
}
清理数据
在退出之前,我们必须要使用SDL_DestroyX销毁我们创建的全部对象。错误处理需要注意:以前在程序中我们可能遇到了错误并提前退出, 在这种情况下, 我们必须销毁我们创建的任何 sdl 对象, 并退出 sdl 以便在退出之前进行正确清理。在这小节的代码中,我们为了让代码的长短不至于太长,所以错误处理被省略了。但在正常的编程环境中正确的错误处理和清理是绝对必要的。
SDL_DestroyTexture(tex);
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
SDL_Quit();
结束
如果一切顺利, 你应该看到你加载的图像渲染整个窗口, 等待 2s, 然后退出。如果出现问题, 请确保已安装了 sdl, 并且您的项目配置正确。