SDL video子系统学习



掌叔
2008-06-10 09:19:17

摘自:[url]http://blog.sina.com.cn/vcbear[/url]
作者:vcbear

上次了解了SDL的框架,这次我们来看看SDL的video子系统。
Video是SDL中使用很普遍的一类元素,有一个完整的体系结构,下面就是一些示例:
首先是初始化视频显示,这部分是基本上所有的SDL程序都要做的。

--------------------------------------------------------------

#include "libnge.h"
#include "stdio.h"



extern "C"
int main(int argc, char* argv[])
{
SDL_Surface *screen;

/* Initialize the SDL library */
if( SDL_Init(SDL_INIT_VIDEO) < 0 )
{
fprintf(stderr,"Couldn't initialize SDL: %s
", SDL_GetError());
exit(1);
}

/* Clean up on exit */
atexit(SDL_Quit);

/*
* Initialize the display in a 640x480 8-bit palettized mode,
* requesting a software surface
*/
screen = SDL_SetVideoMode(640, 480, 8, SDL_SWSURFACE|SDL_ANYFORMAT);
if ( screen == NULL )
{
fprintf(stderr, "Couldn't set 640x480x8 video mode: %s
",SDL_GetError());
exit(1);
}
else
printf("Set 640x480 at %d bits-per-pixel mode
",screen->format->BitsPerPixel);

exit(0);
}

---------------------------------------------------------------

首先,还是利用SDL_Init来初始化SDL,成功返回0否则返回-1。以初始化对象作为参数,多个参数之间用“|”隔开,SDL初始化对象有以下这些:

SDL_INIT_TIMER 用来初始化TIMER子系统
SDL_INIT_AUDIO 用来初始化AUDIO子系统
SDL_INIT_VIDEO 用来初始化VIDEO子系统
SDL_INIT_CDROM 用来初始化CDROM子系统
SDL_INIT_JOYSTICK 用来初始化JOYSTICK子系统
SDL_INIT_NOPARACHUATE 用来防止SDL接收错误数据
SDL_INIT_EVENTTHREAD 用来初始化EVENT子系统
SDL_INIT_EVERYTHING 用来初始化以上所有

然后,利用atexit函数来设置程序正常结束前调用的函数,这里我们需要调用SDL_Quit,这样的话就我们就不需要在main函数中每个return语句前都加入SDL_Quit()了。

下面,我们就定义了一个指向SDL_Surface结构的指针screen,在SDL中,每件物体都是一个surface,你可以拥有多个surface。可以在一个surface上进行绘图或者在其他surface上绘制另外一个surface。这里,我们需要对screen所指向的surface进行绘图,就可以使用函数SDL_SetVideoMode()来设置绘图模式。

SDL_SetVideoMode函数原型,如果操作成功,则返回一个指向SDL_Surface的指针,否则的话返回NULL。

SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);

前三个参数分别为屏幕宽度,高度和屏幕上的每象素包含的位数(bits per pixel, BPP)。如果填入0则SDL会自动选择最合适的BPP。第四个参数是一些特殊标志位的集合。有以下这些:

SDL_SWSURFACE 在系统内存中创建Surface。

SDL_HWSURFACE 在视频内存中创建Surface。

SDL_ASYNCBLIT 允许在显示surface上使用异步更新。在单CPU机器上会变慢,但在SMP系统上会有显著的性能提升。

SDL_ANYFORMAT 如果指定位数的bpp不可用,那么SDL就会模拟使用阴影surface,使用SDL_ANYFORMAT则会避免,不管色深强制SDL使用视频surface。

SDL_HWPALETTE 给予SDL特许的调色板访问权,如果设定这个标志位,就不需要总是使用SDL_SetColors或者SDL_SetPalette来获取所需的颜色。

SDL_DOUBLEBUF 允许硬件双缓冲,但必须同时设置SDL_HWSURFACE,调用SDL_Flip将会flip整个缓冲并且更新屏幕,所有的绘制将会在当前未显示的surface上发生。如果双缓冲被允许,那么SDL_Flip将会对整个屏幕进行SDL_UpdateRect操作。

SDL_FULLSCREEN SDL将会尝试使用全屏模式,如果硬件分辨率的调整由于某种情况无法完成,那么下一个稍高的分辨率将会被使用,并且显示窗口将会处于一个黑色背景的中央。

SDL_OPENGL 创建一个OPENGL渲染设备上下文,但使用前必须调用函数SDL_GL_SetAttribute对OpenGL进行视频属性设置。

SDL_OPENGLBLIT 和上一个选项一样创建一个OPENGL渲染设备上下文, 但是允许使用正常的blitting操作。2D屏幕surface将会拥有一个alpha通道,而且必须调用函数SDL_UpdateRects来更新屏幕变化。

SDL_RESIZABLE 创建一个可伸缩大小的窗口,当用户调整窗口大小时,将会触发一个SDL_VIDEORESIZE事件,SDL_SetVideoMode将会使用新大小作为参数再次被调用。

SDL_NOFRAME SDL_NOFRAME将会创建出一个没有标题栏和边界修饰的窗口,SDL_FULLSCREEN方式将自动设置此标志位。

同样,我们可以调用SDL_GetError来获取发生错误的详细信息。

下面是程序的运行截图
[attach]85[/attach]
如果对上面的程序进行编译运行,那么只能得到一闪而过的一个黑色的窗口。若要绘制真正的图形,我们则需要对窗口进行绘制,并且对基本的键盘鼠标事件进行处理。

下面就是一个完整的屏幕绘图的程序:

----------------------------------------------------------------------

#include "libnge.h"
#include "stdio.h"

void Slock(SDL_Surface *screen)
{
if( SDL_MUSTLOCK(screen) )
{
if( SDL_LockSurface(screen) < 0 )
{
return;
}
}
}


void Sulock(SDL_Surface *screen)
{
if ( SDL_MUSTLOCK(screen) )
{
SDL_UnlockSurface(screen);
}
}


void DrawPixel(SDL_Surface *screen, int x, int y,Uint8 R, Uint8 G, Uint8 B)
{
Uint32 color = SDL_MapRGB(screen->format, R, G, B);
switch (screen->format->BytesPerPixel)
{
case 1: // Assuming 8-bpp
{
Uint8 *bufp;
bufp = (Uint8 *)screen->pixels + y*screen->pitch + x;
*bufp = color;
}
break;
case 2: // Probably 15-bpp or 16-bpp
{
Uint16 *bufp;
bufp = (Uint16 *)screen->pixels + y*screen->pitch/2 + x;
*bufp = color;
}
break;
case 3: // Slow 24-bpp mode, usually not used
{
Uint8 *bufp;
bufp = (Uint8 *)screen->pixels + y*screen->pitch + x * 3;
if(SDL_BYTEORDER == SDL_LIL_ENDIAN)
{
bufp[0] = color;
bufp[1] = color >> 8;
bufp[2] = color >> 16;
}
else
{
bufp[2] = color;
bufp[1] = color >> 8;
bufp[0] = color >> 16;
}
}
break;
case 4: // Probably 32-bpp
{
Uint32 *bufp;
bufp = (Uint32 *)screen->pixels + y*screen->pitch/4 + x;
*bufp = color;
}
break;
}
}


void DrawScene(SDL_Surface *screen)
{
Slock(screen);
for(int x=0;x<640;x++)
{
for(int y=0;y<480;y++)
{
DrawPixel(screen, x,y,y/2,y/2,x/3);
}
}
Sulock(screen);
SDL_Flip(screen);
}


extern "C"
int main(int argc, char* argv[])
{
SDL_Surface *screen;

/* Initialize the SDL library */
if( SDL_Init(SDL_INIT_VIDEO) < 0 )
{
fprintf(stderr,"Couldn't initialize SDL: %s
", SDL_GetError());
exit(1);
}

/* Clean up on exit */
atexit(SDL_Quit);

/*
* Initialize the display in a 640x480 8-bit palettized mode,
* requesting a software surface
*/
screen = SDL_SetVideoMode(640, 480, 8, SDL_SWSURFACE|SDL_ANYFORMAT);
if ( screen == NULL )
{
fprintf(stderr, "Couldn't set 640x480x8 video mode: %s
",SDL_GetError());
exit(1);
}
else
printf("Set 640x480 at %d bits-per-pixel mode
",screen->format->BitsPerPixel);

int done=0;
while(done == 0)
{
SDL_Event event;
while ( SDL_PollEvent(&event) )
{
if ( event.type == SDL_QUIT )

{

done = 1;

}
if ( event.type == SDL_KEYDOWN )
{
if ( event.key.keysym.sym == SDLK_ESCAPE )

{

done = 1;

}
}
}
DrawScene(screen);
}

exit(0);
}

---------------------------------------------------------------



蓝色代码是在前一份代码上添加的部分,主要是完成了绘图锁定,绘图,及消息处理。

由于绘制的屏幕不能同时接受两个函数的同时操作,我们需要其他两个辅助函数,用于在绘制前对屏幕进行锁定,以及在绘制完成之后解除锁定。这两个工作一般由SDL_MUSTLOK(SDL_Surface *screen)和SDL_UnlockSurface(SDL_Surface *screen)完成。上面使用了两个自定义的函数,简单一些。

然后就是绘制了,绘制的基本原理是,先在缓冲区绘制,再一次性将缓冲区绘制到屏幕上。这样比起一次一个象素点在屏幕上绘图的方式效率更高,速度更快,也不易出错。首先使用循环在screen所指向的surface(缓冲区)上绘制,然后调用SDL_Flip函数将screen surface绘制到真实电脑屏幕上。SDL_Flip函数的作用是:在支持双缓冲(double-buffering)的硬件上,建立flip并返回。硬件将等待vertical retrace,然后在下一个视频surface blit或者执行锁定返回前交换视频缓冲区。如果硬件不支持双缓冲,那么等同于调用SDL_UpdateRect(screen, 0, 0, 0, 0),即对整个screen的绘制区域进行刷新。

接下来是消息处理,在SDL中,采用了结构SDL_Event来描述事件,并采用循环的机制对事件进行处理,程序中使用一个SDL_Event的对象 event来检查事件的发生,这里用一个while循环,不断的调用函数SDL_PollEvent来获取事件,得到的消息将会自动填充event对象,在循环内部,通过判断不同的消息类型,从而做出不同的处理。

下面是程序运行的截图:
[attach]86[/attach]