SDL2源码分析1:初始化(SDL_Init())

时间:2024-05-13 13:33:50

=====================================================

SDL源码分析系列文章列表:

SDL2源码分析1:初始化(SDL_Init())

SDL2源码分析2:窗体(SDL_Window)

SDL2源码分析3:渲染器(SDL_Renderer)

SDL2源码分析4:纹理(SDL_Texture)

SDL2源码分析5:更新纹理(SDL_UpdateTexture())

SDL2源码分析6:拷贝到渲染器(SDL_RenderCopy())

SDL2源码分析7:显示(SDL_RenderPresent())

SDL2源码分析8:视频显示总结

=====================================================

打算花一段时间研究一下SDL的内部代码。前面几篇文章《最简单的视音频播放演示样例1:总述》中记录了视频、音频播放的技术,文中提及了SDL实际上封装了Direct3D,DirectSound这类的底层API。可是SDL到底是怎样封装的呢?这次打算深入其源码一探到底,看看它是怎样封装这些API的。

SDL2源码分析1:初始化(SDL_Init())

SDL简单介绍

有关SDL的简单介绍在《最简单的视音频播放演示样例7:SDL2播放RGB/YUV》以及《最简单的视音频播放演示样例9:SDL2播放PCM》中已经叙述过了。不再反复。这两篇文章中也提到了一张SDL的原理图,例如以下所看到的:

SDL2源码分析1:初始化(SDL_Init())

从这个图中能够看出,SDL依据系统的不同调用不同的API完毕对应的功能。至于它是怎样实现的,将会在后文中具体叙述。

以下不再罗嗦。直接进入正题。

使用SDL播放一个视频代码流程大体例如以下
初始化: 

SDL_Init(): 初始化SDL。 
SDL_CreateWindow(): 创建窗体(Window)。

SDL_CreateRenderer(): 基于窗体创建渲染器(Render)。 
SDL_CreateTexture(): 创建纹理(Texture)。

循环渲染数据: 

SDL_UpdateTexture(): 设置纹理的数据。 
SDL_RenderCopy(): 纹理复制给渲染器。 
SDL_RenderPresent(): 显示。

本文分析这个流程中最主要的一个函数SDL_Init()。SDL_Init()是SDL执行的初始,通过分析该函数,能够了解到SDL内部的架构。

获取源码

SDL的源码获取十分简单。

訪问SDL的官方站点(http://www.libsdl.org/),单击左側的“Download”进入下载页面,然后下载“SourceCode”栏目下的文件就能够了。

SDL2源码分析1:初始化(SDL_Init())

下载下来的文件仅仅有4MB左右大小,可是解压缩之后居然有50MB左右大小,确实不可思议。

解压缩之后,源码目录例如以下图所看到的。

SDL2源码分析1:初始化(SDL_Init())

几个关键的目录例如以下所看到的:

1. include:存储SDL的头文件的目录。

2. src:存储SDL源码文件的目录。SDL依据功能模块的不同,将源码分成了非常多的目录。下图中标出了存储SDL几个子系统的源码的目录。

SDL2源码分析1:初始化(SDL_Init())

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpeGlhb2h1YTEwMjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

3. VisualC:存储VC解决方式的目录。从下图中能够看出,包括了VS2008,VS2010,VS2012,VS2013等各个版本号的VC的解决方式。

SDL2源码分析1:初始化(SDL_Init())

实际上从文件名我们能够看出,其他几个目录中。“Xcode,Xcode-iOS”包括了Xcode的项目文件。“test”包括了一些測试样例程序。“android-project”包括了Android下的项目文件。

因为我们临时不研究这些文件,就不具体分析了。

SDL_Init()


函数简单介绍

以下这一部分进入正题。分析SDL的初始化函数SDL_Init()。该函数能够确定希望激活的子系统。SDL_Init()函数原型例如以下:

int SDLCALL SDL_Init(Uint32 flags)

当中。flags能够取下列值:

SDL_INIT_TIMER:定时器
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_HAPTIC:触摸屏
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_EVENTS:事件
SDL_INIT_NOPARACHUTE:不捕获关键信号(这个不理解)
SDL_INIT_EVERYTHING:包括上述全部选项

函数调用关系图

SDL_Init()关键函数的调用关系能够用下图表示。

SDL2源码分析1:初始化(SDL_Init())

上面的函数调用关系图本来是一张高清大图。可是博客不支持这么大尺寸的图片。因此把图片缩小了,看上去比較模糊。

相冊里面上传了一份原始的大图片:

http://my.****.net/leixiaohua1020/album/detail/1792993

选择上述相冊里面的图片,右键选择“另存为”就可以保存原始大图片。

源码分析

SDL_Init()的实现位于SDL.c中。

定义例如以下。

int SDL_Init(Uint32 flags)
{
return SDL_InitSubSystem(flags);
}

能够看出其代码仅仅有一句,即调用了SDL_InitSubSystem(),以下我们看一下SDL_InitSubSystem()的定义。

int SDL_InitSubSystem(Uint32 flags)
{
if (!SDL_MainIsReady) {
SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?");
return -1;
} /* Clear the error message */
SDL_ClearError(); #if SDL_VIDEO_DRIVER_WINDOWS
if ((flags & (SDL_INIT_HAPTIC|SDL_INIT_JOYSTICK))) {
if (SDL_HelperWindowCreate() < 0) {
return -1;
}
}
#endif #if !SDL_TIMERS_DISABLED
SDL_TicksInit();
#endif if ((flags & SDL_INIT_GAMECONTROLLER)) {
/* game controller implies joystick */
flags |= SDL_INIT_JOYSTICK;
} if ((flags & (SDL_INIT_VIDEO|SDL_INIT_JOYSTICK))) {
/* video or joystick implies events */
flags |= SDL_INIT_EVENTS;
} /* Initialize the event subsystem */
if ((flags & SDL_INIT_EVENTS)) {
#if !SDL_EVENTS_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_EVENTS)) {
if (SDL_StartEventLoop() < 0) {
return (-1);
}
SDL_QuitInit();
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_EVENTS);
#else
return SDL_SetError("SDL not built with events support");
#endif
} /* Initialize the timer subsystem */
if ((flags & SDL_INIT_TIMER)){
#if !SDL_TIMERS_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_TIMER)) {
if (SDL_TimerInit() < 0) {
return (-1);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_TIMER);
#else
return SDL_SetError("SDL not built with timer support");
#endif
} /* Initialize the video subsystem */
if ((flags & SDL_INIT_VIDEO)){
#if !SDL_VIDEO_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_VIDEO)) {
if (SDL_VideoInit(NULL) < 0) {
return (-1);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_VIDEO);
#else
return SDL_SetError("SDL not built with video support");
#endif
} /* Initialize the audio subsystem */
if ((flags & SDL_INIT_AUDIO)){
#if !SDL_AUDIO_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_AUDIO)) {
if (SDL_AudioInit(NULL) < 0) {
return (-1);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_AUDIO);
#else
return SDL_SetError("SDL not built with audio support");
#endif
} /* Initialize the joystick subsystem */
if ((flags & SDL_INIT_JOYSTICK)){
#if !SDL_JOYSTICK_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_JOYSTICK)) {
if (SDL_JoystickInit() < 0) {
return (-1);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_JOYSTICK);
#else
return SDL_SetError("SDL not built with joystick support");
#endif
} if ((flags & SDL_INIT_GAMECONTROLLER)){
#if !SDL_JOYSTICK_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_GAMECONTROLLER)) {
if (SDL_GameControllerInit() < 0) {
return (-1);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_GAMECONTROLLER);
#else
return SDL_SetError("SDL not built with joystick support");
#endif
} /* Initialize the haptic subsystem */
if ((flags & SDL_INIT_HAPTIC)){
#if !SDL_HAPTIC_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_HAPTIC)) {
if (SDL_HapticInit() < 0) {
return (-1);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_HAPTIC);
#else
return SDL_SetError("SDL not built with haptic (force feedback) support");
#endif
} return (0);
}

SDL_InitSubSystem()函数的定义看上去非常长,实际上却并不复杂。以下简单阐述一下它的一些关键点:

1. 通过将传入的flag与子系统的宏定义(比如SDL_INIT_VIDEO。SDL_INIT_AUDIO等)相与,推断是否须要初始化该子系统。

2. 有非常多的提前定义的宏。用于推断SDL是否支持这些子系统。比如SDL_EVENTS_DISABLED。SDL_TIMERS_DISABLED。SDL_VIDEO_DISABLED。SDL_AUDIO_DISABLED。SDL_JOYSTICK_DISABLED,SDL_HAPTIC_DISABLED等。这些宏的定义位于SDL_config_minimal.h文件里。例如以下所看到的。

/* Enable the dummy audio driver (src/audio/dummy/\*.c) */
#define SDL_AUDIO_DRIVER_DUMMY 1 /* Enable the stub joystick driver (src/joystick/dummy/\*.c) */
#define SDL_JOYSTICK_DISABLED 1 /* Enable the stub haptic driver (src/haptic/dummy/\*.c) */
#define SDL_HAPTIC_DISABLED 1 /* Enable the stub shared object loader (src/loadso/dummy/\*.c) */
#define SDL_LOADSO_DISABLED 1 /* Enable the stub thread support (src/thread/generic/\*.c) */
#define SDL_THREADS_DISABLED 1 /* Enable the stub timer support (src/timer/dummy/\*.c) */
#define SDL_TIMERS_DISABLED 1 /* Enable the dummy video driver (src/video/dummy/\*.c) */
#define SDL_VIDEO_DRIVER_DUMMY 1 /* Enable the dummy filesystem driver (src/filesystem/dummy/\*.c) */
#define SDL_FILESYSTEM_DUMMY 1

假设这些定义取值不为0,代表该子系统已经被disable了,就不编译指定子系统的源码了。初始化的时候会调用SDL_SetError()函数输出错误信息。

比如SDL_VIDEO_DISABLED假设设置为1的话,初始化视频子系统的时候会执行以下代码。

SDL_SetError("SDL not built with video support");

3. 在每一个子系统真正初始化之前,都会调用一个函数SDL_PrivateShouldInitSubsystem()。

该函数用于检查目标子系统是否须要初始化。

4. 在一个子系统初始化之后。都会调用一个函数SDL_PrivateSubsystemRefCountIncr()。该函数用于添加子系统的引用计数。

5. 下表列出各个子系统的初始化函数。

子系统名称

函数

AUDIO(音频)

SDL_AudioInit()

VIDEO(视频)

SDL_VideoInit()

TIMERS(定时器)

SDL_TicksInit()。SDL_TimerInit()

EVENTS(事件)

SDL_StartEventLoop()

JOYSTICK(摇杆)

SDL_GameControllerInit()

HAPTIC(触摸屏)

SDL_HapticInit()

我们先不看JOYSTICK(摇杆),HAPTIC(触摸屏)这些方面的代码,专门关注AUDIO(音频),VIDEO(视频)这两个方面的代码。

1. VIDEO(视频)

视频子系统的初始化函数是SDL_VideoInit()。

它的源码位于video\SDL_video.c文件里。例如以下所看到的。

/*
* Initialize the video and event subsystems -- determine native pixel format
*/
int SDL_VideoInit(const char *driver_name)
{
SDL_VideoDevice *video;
const char *hint;
int index;
int i;
SDL_bool allow_screensaver; /* Check to make sure we don't overwrite '_this' */
if (_this != NULL) {
SDL_VideoQuit();
} #if !SDL_TIMERS_DISABLED
SDL_TicksInit();
#endif /* Start the event loop */
if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0 ||
SDL_KeyboardInit() < 0 ||
SDL_MouseInit() < 0 ||
SDL_TouchInit() < 0) {
return -1;
} /* Select the proper video driver */
index = 0;
video = NULL;
if (driver_name == NULL) {
driver_name = SDL_getenv("SDL_VIDEODRIVER");
}
if (driver_name != NULL) {
for (i = 0; bootstrap[i]; ++i) {
if (SDL_strncasecmp(bootstrap[i]->name, driver_name, SDL_strlen(driver_name)) == 0) {
if (bootstrap[i]->available()) {
video = bootstrap[i]->create(index);
break;
}
}
}
} else {
for (i = 0; bootstrap[i]; ++i) {
if (bootstrap[i]->available()) {
video = bootstrap[i]->create(index);
if (video != NULL) {
break;
}
}
}
}
if (video == NULL) {
if (driver_name) {
return SDL_SetError("%s not available", driver_name);
}
return SDL_SetError("No available video device");
}
_this = video;
_this->name = bootstrap[i]->name;
_this->next_object_id = 1; /* Set some very sane GL defaults */
_this->gl_config.driver_loaded = 0;
_this->gl_config.dll_handle = NULL;
SDL_GL_ResetAttributes(); _this->current_glwin_tls = SDL_TLSCreate();
_this->current_glctx_tls = SDL_TLSCreate(); /* Initialize the video subsystem */
if (_this->VideoInit(_this) < 0) {
SDL_VideoQuit();
return -1;
} /* Make sure some displays were added */
if (_this->num_displays == 0) {
SDL_VideoQuit();
return SDL_SetError("The video driver did not add any displays");
} /* Add the renderer framebuffer emulation if desired */
if (ShouldUseTextureFramebuffer()) {
_this->CreateWindowFramebuffer = SDL_CreateWindowTexture;
_this->UpdateWindowFramebuffer = SDL_UpdateWindowTexture;
_this->DestroyWindowFramebuffer = SDL_DestroyWindowTexture;
} /* Disable the screen saver by default. This is a change from <= 2.0.1,
but most things using SDL are games or media players; you wouldn't
want a screensaver to trigger if you're playing exclusively with a
joystick, or passively watching a movie. Things that use SDL but
function more like a normal desktop app should explicitly reenable the
screensaver. */
hint = SDL_GetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER);
if (hint) {
allow_screensaver = SDL_atoi(hint) ? SDL_TRUE : SDL_FALSE;
} else {
allow_screensaver = SDL_FALSE;
}
if (!allow_screensaver) {
SDL_DisableScreenSaver();
} /* If we don't use a screen keyboard, turn on text input by default,
otherwise programs that expect to get text events without enabling
UNICODE input won't get any events. Actually, come to think of it, you needed to call SDL_EnableUNICODE(1)
in SDL 1.2 before you got text input events. Hmm...
*/
if (!SDL_HasScreenKeyboardSupport()) {
SDL_StartTextInput();
} /* We're ready to go! */
return 0;
}

以下简单阐述一下它的大致步骤:
1. 初始化一些子系统,比方EVENTS(事件)子系统。也就是说,就算在调用SDL_Init()的时候不指定初始化EVENTS子系统,在初始化VIDEO子系统的时候。相同也会初始化EVENTS子系统。
2. 选择一个合适的SDL_VideoDevice。
在这里,涉及到两个重要的结构体:SDL_VideoDevice以及VideoBootStrap。当中SDL_VideoDevice代表了一个视频驱动程序。

VideoBootStrap从字面上理解是“视频驱动程序的引导程序”。即用于创建一个SDL_VideoDevice。

因此。我们先来看看VideoBootStrap这个结构体。

它的定义例如以下(位于video\SDL_sysvideo.h)。

typedef struct VideoBootStrap
{
const char *name;
const char *desc;
int (*available) (void);
SDL_VideoDevice *(*create) (int devindex);
} VideoBootStrap;

能够看出它的定义比較简单,每一个字段的含义例如以下:

name:驱动名称
desc:描写叙述
available():检查是否可用的一个函数指针
create():创建SDL_VideoDevice的函数指针

SDL中有一个VideoBootStrap类型的静态数组bootstrap。用于存储SDL支持的视频驱动程序。

注意这是SDL“跨平台”特性中非常重要的一部分。该静态数组定义例如以下(位于video\SDL_video.c)。

/* Available video drivers */
static VideoBootStrap *bootstrap[] = {
#if SDL_VIDEO_DRIVER_COCOA
&COCOA_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_X11
&X11_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_MIR
&MIR_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_DIRECTFB
&DirectFB_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_WINDOWS
&WINDOWS_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_WINRT
&WINRT_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_HAIKU
&HAIKU_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_PANDORA
&PND_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_UIKIT
&UIKIT_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_ANDROID
&Android_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_PSP
&PSP_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_RPI
&RPI_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_WAYLAND
&Wayland_bootstrap,
#endif
#if SDL_VIDEO_DRIVER_DUMMY
&DUMMY_bootstrap,
#endif
NULL
};

从代码中能够看出,SDL通过预编译宏取值是否非0来推断是否支持该视频驱动。我们能够看一下Windows的视频设备驱动的定义。该设备驱动相同是一个静态变量,名称为WINDOWS_bootstrap(位于video\windows\SDL_windowsvideo.c)。

VideoBootStrap WINDOWS_bootstrap = {
"windows", "SDL Windows video driver", WIN_Available, WIN_CreateDevice
};

能够看出该视频驱动名称为“windows”。描写叙述为“SDL Windows video driver”,检查是否可用的函数为“WIN_Available()”。创建SDL_VideoDevice的函数为“WIN_CreateDevice()”。
相同, Android的视频设备驱动的名称为Android_bootstrap;PSP的视频设备驱动为PSP_bootstrap;X11的视频设备驱动为X11_bootstrap。不再一一例举。
以下看一下Windows视频驱动中那两个函数的定义。

WIN_Available()定义例如以下。

static int WIN_Available(void)
{
return (1);
}

可见该函数没有做不论什么的处理。WIN_CreateDevice()定义例如以下。

static SDL_VideoDevice *
WIN_CreateDevice(int devindex)
{
SDL_VideoDevice *device;
SDL_VideoData *data; SDL_RegisterApp(NULL, 0, NULL); /* Initialize all variables that we clean on shutdown */
device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
if (device) {
data = (struct SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
} else {
data = NULL;
}
if (!data) {
SDL_free(device);
SDL_OutOfMemory();
return NULL;
}
device->driverdata = data; data->userDLL = SDL_LoadObject("USER32.DLL");
if (data->userDLL) {
data->CloseTouchInputHandle = (BOOL (WINAPI *)( HTOUCHINPUT )) SDL_LoadFunction(data->userDLL, "CloseTouchInputHandle");
data->GetTouchInputInfo = (BOOL (WINAPI *)( HTOUCHINPUT, UINT, PTOUCHINPUT, int )) SDL_LoadFunction(data->userDLL, "GetTouchInputInfo");
data->RegisterTouchWindow = (BOOL (WINAPI *)( HWND, ULONG )) SDL_LoadFunction(data->userDLL, "RegisterTouchWindow");
} /* Set the function pointers */
device->VideoInit = WIN_VideoInit;
device->VideoQuit = WIN_VideoQuit;
device->GetDisplayBounds = WIN_GetDisplayBounds;
device->GetDisplayModes = WIN_GetDisplayModes;
device->SetDisplayMode = WIN_SetDisplayMode;
device->PumpEvents = WIN_PumpEvents; #undef CreateWindow
device->CreateWindow = WIN_CreateWindow;
device->CreateWindowFrom = WIN_CreateWindowFrom;
device->SetWindowTitle = WIN_SetWindowTitle;
device->SetWindowIcon = WIN_SetWindowIcon;
device->SetWindowPosition = WIN_SetWindowPosition;
device->SetWindowSize = WIN_SetWindowSize;
device->ShowWindow = WIN_ShowWindow;
device->HideWindow = WIN_HideWindow;
device->RaiseWindow = WIN_RaiseWindow;
device->MaximizeWindow = WIN_MaximizeWindow;
device->MinimizeWindow = WIN_MinimizeWindow;
device->RestoreWindow = WIN_RestoreWindow;
device->SetWindowBordered = WIN_SetWindowBordered;
device->SetWindowFullscreen = WIN_SetWindowFullscreen;
device->SetWindowGammaRamp = WIN_SetWindowGammaRamp;
device->GetWindowGammaRamp = WIN_GetWindowGammaRamp;
device->SetWindowGrab = WIN_SetWindowGrab;
device->DestroyWindow = WIN_DestroyWindow;
device->GetWindowWMInfo = WIN_GetWindowWMInfo;
device->CreateWindowFramebuffer = WIN_CreateWindowFramebuffer;
device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer;
device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer;
device->OnWindowEnter = WIN_OnWindowEnter; device->shape_driver.CreateShaper = Win32_CreateShaper;
device->shape_driver.SetWindowShape = Win32_SetWindowShape;
device->shape_driver.ResizeWindowShape = Win32_ResizeWindowShape; #if SDL_VIDEO_OPENGL_WGL
device->GL_LoadLibrary = WIN_GL_LoadLibrary;
device->GL_GetProcAddress = WIN_GL_GetProcAddress;
device->GL_UnloadLibrary = WIN_GL_UnloadLibrary;
device->GL_CreateContext = WIN_GL_CreateContext;
device->GL_MakeCurrent = WIN_GL_MakeCurrent;
device->GL_SetSwapInterval = WIN_GL_SetSwapInterval;
device->GL_GetSwapInterval = WIN_GL_GetSwapInterval;
device->GL_SwapWindow = WIN_GL_SwapWindow;
device->GL_DeleteContext = WIN_GL_DeleteContext;
#endif
device->StartTextInput = WIN_StartTextInput;
device->StopTextInput = WIN_StopTextInput;
device->SetTextInputRect = WIN_SetTextInputRect; device->SetClipboardText = WIN_SetClipboardText;
device->GetClipboardText = WIN_GetClipboardText;
device->HasClipboardText = WIN_HasClipboardText; device->free = WIN_DeleteDevice; return device;
}

该函数首先通过SDL_calloc()的方法为创建的SDL_VideoDevice分配了一块内存,接下来为创建的SDL_VideoDevice结构体中的函数指针赋了一大堆的值。这也是SDL“跨平台”特性的一个特点:通过调用SDL_VideoDevice中的接口函数,就能够调用不同平台的具体实现功能的函数。

PS:在这里补充一个SDL中内存分配函数的知识。在SDL中分配内存使用SDL_malloc()。SDL_calloc()。这些函数实际上就是malloc()。calloc()。

它们的定义位于stdlib\SDL_malloc.c文件里。例如以下所看到的:

#define memset  SDL_memset
#define memcpy SDL_memcpy
#define malloc SDL_malloc
#define calloc SDL_calloc
#define realloc SDL_realloc
#define free SDL_free

以下来看一下SDL_VideoDevice这个结构体的定义(位于video\SDL_sysvideo.h)。

struct SDL_VideoDevice
{
/* * * */
/* The name of this video driver */
const char *name; /* * * */
/* Initialization/Query functions */ /*
* Initialize the native video subsystem, filling in the list of
* displays for this driver, returning 0 or -1 if there's an error.
*/
int (*VideoInit) (_THIS); /*
* Reverse the effects VideoInit() -- called if VideoInit() fails or
* if the application is shutting down the video subsystem.
*/
void (*VideoQuit) (_THIS); /* * * */
/*
* Display functions
*/ /*
* Get the bounds of a display
*/
int (*GetDisplayBounds) (_THIS, SDL_VideoDisplay * display, SDL_Rect * rect); /*
* Get a list of the available display modes for a display.
*/
void (*GetDisplayModes) (_THIS, SDL_VideoDisplay * display); /*
* Setting the display mode is independent of creating windows, so
* when the display mode is changed, all existing windows should have
* their data updated accordingly, including the display surfaces
* associated with them.
*/
int (*SetDisplayMode) (_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode); /* * * */
/*
* Window functions
*/
int (*CreateWindow) (_THIS, SDL_Window * window);
int (*CreateWindowFrom) (_THIS, SDL_Window * window, const void *data);
void (*SetWindowTitle) (_THIS, SDL_Window * window);
void (*SetWindowIcon) (_THIS, SDL_Window * window, SDL_Surface * icon);
void (*SetWindowPosition) (_THIS, SDL_Window * window);
void (*SetWindowSize) (_THIS, SDL_Window * window);
void (*SetWindowMinimumSize) (_THIS, SDL_Window * window);
void (*SetWindowMaximumSize) (_THIS, SDL_Window * window);
void (*ShowWindow) (_THIS, SDL_Window * window);
void (*HideWindow) (_THIS, SDL_Window * window);
void (*RaiseWindow) (_THIS, SDL_Window * window);
void (*MaximizeWindow) (_THIS, SDL_Window * window);
void (*MinimizeWindow) (_THIS, SDL_Window * window);
void (*RestoreWindow) (_THIS, SDL_Window * window);
void (*SetWindowBordered) (_THIS, SDL_Window * window, SDL_bool bordered);
void (*SetWindowFullscreen) (_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen);
int (*SetWindowGammaRamp) (_THIS, SDL_Window * window, const Uint16 * ramp);
int (*GetWindowGammaRamp) (_THIS, SDL_Window * window, Uint16 * ramp);
void (*SetWindowGrab) (_THIS, SDL_Window * window, SDL_bool grabbed);
void (*DestroyWindow) (_THIS, SDL_Window * window);
int (*CreateWindowFramebuffer) (_THIS, SDL_Window * window, Uint32 * format, void ** pixels, int *pitch);
int (*UpdateWindowFramebuffer) (_THIS, SDL_Window * window, const SDL_Rect * rects, int numrects);
void (*DestroyWindowFramebuffer) (_THIS, SDL_Window * window);
void (*OnWindowEnter) (_THIS, SDL_Window * window); /* * * */
/*
* Shaped-window functions
*/
SDL_ShapeDriver shape_driver; /* Get some platform dependent window information */
SDL_bool(*GetWindowWMInfo) (_THIS, SDL_Window * window,
struct SDL_SysWMinfo * info); /* * * */
/*
* OpenGL support
*/
int (*GL_LoadLibrary) (_THIS, const char *path);
void *(*GL_GetProcAddress) (_THIS, const char *proc);
void (*GL_UnloadLibrary) (_THIS);
SDL_GLContext(*GL_CreateContext) (_THIS, SDL_Window * window);
int (*GL_MakeCurrent) (_THIS, SDL_Window * window, SDL_GLContext context);
void (*GL_GetDrawableSize) (_THIS, SDL_Window * window, int *w, int *h);
int (*GL_SetSwapInterval) (_THIS, int interval);
int (*GL_GetSwapInterval) (_THIS);
void (*GL_SwapWindow) (_THIS, SDL_Window * window);
void (*GL_DeleteContext) (_THIS, SDL_GLContext context); /* * * */
/*
* Event manager functions
*/
void (*PumpEvents) (_THIS); /* Suspend the screensaver */
void (*SuspendScreenSaver) (_THIS); /* Text input */
void (*StartTextInput) (_THIS);
void (*StopTextInput) (_THIS);
void (*SetTextInputRect) (_THIS, SDL_Rect *rect); /* Screen keyboard */
SDL_bool (*HasScreenKeyboardSupport) (_THIS);
void (*ShowScreenKeyboard) (_THIS, SDL_Window *window);
void (*HideScreenKeyboard) (_THIS, SDL_Window *window);
SDL_bool (*IsScreenKeyboardShown) (_THIS, SDL_Window *window); /* Clipboard */
int (*SetClipboardText) (_THIS, const char *text);
char * (*GetClipboardText) (_THIS);
SDL_bool (*HasClipboardText) (_THIS); /* MessageBox */
int (*ShowMessageBox) (_THIS, const SDL_MessageBoxData *messageboxdata, int *buttonid); /* * * */
/* Data common to all drivers */
SDL_bool suspend_screensaver;
int num_displays;
SDL_VideoDisplay *displays;
SDL_Window *windows;
Uint8 window_magic;
Uint32 next_object_id;
char * clipboard_text; /* * * */
/* Data used by the GL drivers */
struct
{
int red_size;
int green_size;
int blue_size;
int alpha_size;
int depth_size;
int buffer_size;
int stencil_size;
int double_buffer;
int accum_red_size;
int accum_green_size;
int accum_blue_size;
int accum_alpha_size;
int stereo;
int multisamplebuffers;
int multisamplesamples;
int accelerated;
int major_version;
int minor_version;
int flags;
int profile_mask;
int share_with_current_context;
int framebuffer_srgb_capable;
int retained_backing;
int driver_loaded;
char driver_path[256];
void *dll_handle;
} gl_config; /* * * */
/* Cache current GL context; don't call the OS when it hasn't changed. */
/* We have the global pointers here so Cocoa continues to work the way
it always has, and the thread-local storage for the general case.
*/
SDL_Window *current_glwin;
SDL_GLContext current_glctx;
SDL_TLSID current_glwin_tls;
SDL_TLSID current_glctx_tls; /* * * */
/* Data private to this driver */
void *driverdata;
struct SDL_GLDriverData *gl_data; #if SDL_VIDEO_OPENGL_EGL
struct SDL_EGL_VideoData *egl_data;
#endif #if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
struct SDL_PrivateGLESData *gles_data;
#endif /* * * */
/* The function used to dispose of this structure */
void (*free) (_THIS);
};

这个结构体包括了一大堆的函数指针。这些指针在前文所说的VideoBootStrap的create()方法调用的时候会被赋值。

SDL通过调用这些函数指针,完毕视频显示的各项内容。

3. 调用选中的SDL_VideoDevice的VideoInit()方法。
选择了合适的SDL_VideoDevice之后。调用该SDL_VideoDevice的VideoInit()就能够真正的初始化视频驱动了。以Windows系统为例。

从前文的函数中能够看出,Windows系统的VideoInit()接口实际上调用了WIN_VideoInit()函数。我们来看一下WIN_VideoInit()函数的定义(位于video\windows\SDL_windowsvideo.c)。

int WIN_VideoInit(_THIS)
{
if (WIN_InitModes(_this) < 0) {
return -1;
} WIN_InitKeyboard(_this);
WIN_InitMouse(_this); return 0;
}

当中有3个函数:WIN_InitModes(),WIN_InitKeyboard()。WIN_InitMouse()。后两个函数用于初始化键盘和鼠标,我们暂且不研究。看一下WIN_InitModes()的函数。

int WIN_InitModes(_THIS)
{
int pass;
DWORD i, j, count;
DISPLAY_DEVICE device; device.cb = sizeof(device); /* Get the primary display in the first pass */
for (pass = 0; pass < 2; ++pass) {
for (i = 0; ; ++i) {
TCHAR DeviceName[32]; if (!EnumDisplayDevices(NULL, i, &device, 0)) {
break;
}
if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
continue;
}
if (pass == 0) {
if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {
continue;
}
} else {
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
continue;
}
}
SDL_memcpy(DeviceName, device.DeviceName, sizeof(DeviceName));
#ifdef DEBUG_MODES
printf("Device: %s\n", WIN_StringToUTF8(DeviceName));
#endif
count = 0;
for (j = 0; ; ++j) {
if (!EnumDisplayDevices(DeviceName, j, &device, 0)) {
break;
}
if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
continue;
}
if (pass == 0) {
if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {
continue;
}
} else {
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
continue;
}
}
count += WIN_AddDisplay(device.DeviceName);
}
if (count == 0) {
WIN_AddDisplay(DeviceName);
}
}
}
if (_this->num_displays == 0) {
return SDL_SetError("No displays available");
}
return 0;
}

该函数的作用就是获得系统中显示设备的信息。

眼下还没有深入研究,待有时间再补上该函数的分析。

2. AUDIO(音频)

音频子系统的初始化函数是SDL_ AudioInit ()。

它的源码位于audio\SDL_audio.c文件里,例如以下所看到的。

int SDL_AudioInit(const char *driver_name)
{
int i = 0;
int initialized = 0;
int tried_to_init = 0; if (SDL_WasInit(SDL_INIT_AUDIO)) {
SDL_AudioQuit(); /* shutdown driver if already running. */
} SDL_memset(¤t_audio, '\0', sizeof(current_audio));
SDL_memset(open_devices, '\0', sizeof(open_devices)); /* Select the proper audio driver */
if (driver_name == NULL) {
driver_name = SDL_getenv("SDL_AUDIODRIVER");
} for (i = 0; (!initialized) && (bootstrap[i]); ++i) {
/* make sure we should even try this driver before doing so... */
const AudioBootStrap *backend = bootstrap[i];
if ((driver_name && (SDL_strncasecmp(backend->name, driver_name, SDL_strlen(driver_name)) != 0)) ||
(!driver_name && backend->demand_only)) {
continue;
} tried_to_init = 1;
SDL_memset(¤t_audio, 0, sizeof(current_audio));
current_audio.name = backend->name;
current_audio.desc = backend->desc;
initialized = backend->init(¤t_audio.impl);
} if (!initialized) {
/* specific drivers will set the error message if they fail... */
if (!tried_to_init) {
if (driver_name) {
SDL_SetError("Audio target '%s' not available", driver_name);
} else {
SDL_SetError("No available audio device");
}
} SDL_memset(¤t_audio, 0, sizeof(current_audio));
return (-1); /* No driver was available, so fail. */
} finalize_audio_entry_points(); return (0);
}

音频初始化和视频非常相似,比视频简单一些,关键在于选择一个合适的SDL_AudioDriver。

在这里。涉及到两个重要的结构体:SDL_AudioDriver以及AudioBootStrap。当中SDL_AudioDriver代表了一个音频驱动程序。

AudioBootStrap从字面上理解是“音频驱动程序的引导程序”,即用于创建一个SDL_AudioDriver。能够看出音频子系统中的结构体和视频子系统中的结构体的格式基本上是一模一样的。我们先来看看AudioBootStrap这个结构体。它的定义例如以下(位于audio\SDL_sysaudio.h)。

typedef struct AudioBootStrap
{
const char *name;
const char *desc;
int (*init) (SDL_AudioDriverImpl * impl);
int demand_only; /* 1==request explicitly, or it won't be available. */
} AudioBootStrap;

能够看出它的定义比較简单,每一个字段的含义例如以下:

name:驱动名称
desc:描写叙述
init():创建SDL_AudioDriver的函数指针
demand_only:没有研究过。

SDL中有一个AudioBootStrap类型的静态数组bootstrap。

用于存储SDL支持的音频驱动程序。

该静态数组定义例如以下(位于audio\SDL_audio.c)。

/* Available audio drivers */
static const AudioBootStrap *const bootstrap[] = {
#if SDL_AUDIO_DRIVER_PULSEAUDIO
&PULSEAUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_ALSA
&ALSA_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_SNDIO
&SNDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_BSD
&BSD_AUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_OSS
&DSP_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_QSA
&QSAAUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_SUNAUDIO
&SUNAUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_ARTS
&ARTS_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_ESD
&ESD_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_NAS
&NAS_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_XAUDIO2
&XAUDIO2_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_DSOUND
&DSOUND_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_WINMM
&WINMM_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_PAUDIO
&PAUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_HAIKU
&HAIKUAUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_COREAUDIO
&COREAUDIO_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_DISK
&DISKAUD_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_DUMMY
&DUMMYAUD_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_FUSIONSOUND
&FUSIONSOUND_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_ANDROID
&ANDROIDAUD_bootstrap,
#endif
#if SDL_AUDIO_DRIVER_PSP
&PSPAUD_bootstrap,
#endif
NULL
};

在这里我们能够看一下DirectSound的AudioBootStrap的变量DSOUND_bootstrap(audio\directsound\SDL_directsound.c)。

AudioBootStrap DSOUND_bootstrap = {
"directsound", "DirectSound", DSOUND_Init, 0
};

能够看出该音频驱动名称为“directsound”,描写叙述为“DirectSound”。创建SDL_AudioDriver的函数为“DSOUND_Init()”。
以下看一下DirectSound初始化函数DSOUND_Init()的定义。

static int DSOUND_Init(SDL_AudioDriverImpl * impl)
{
if (!DSOUND_Load()) {
return 0;
} /* Set the function pointers */
impl->DetectDevices = DSOUND_DetectDevices;
impl->OpenDevice = DSOUND_OpenDevice;
impl->PlayDevice = DSOUND_PlayDevice;
impl->WaitDevice = DSOUND_WaitDevice;
impl->WaitDone = DSOUND_WaitDone;
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
impl->CloseDevice = DSOUND_CloseDevice;
impl->Deinitialize = DSOUND_Deinitialize; return 1; /* this audio target is available. */
}

和视频驱动的初始化一样。音频驱动初始化也是对SDL_AudioDriver的接口指针进行赋值。在这里涉及到了一个DirectSound的载入函数DSOUND_Load()。我们能够看一下它的代码。

static int DSOUND_Load(void)
{
int loaded = 0; DSOUND_Unload(); DSoundDLL = SDL_LoadObject("DSOUND.DLL");
if (DSoundDLL == NULL) {
SDL_SetError("DirectSound: failed to load DSOUND.DLL");
} else {
/* Now make sure we have DirectX 8 or better... */
#define DSOUNDLOAD(f) { \
p##f = (fn##f) SDL_LoadFunction(DSoundDLL, #f); \
if (!p##f) loaded = 0; \
}
loaded = 1; /* will reset if necessary. */
DSOUNDLOAD(DirectSoundCreate8);
DSOUNDLOAD(DirectSoundEnumerateW);
DSOUNDLOAD(DirectSoundCaptureEnumerateW);
#undef DSOUNDLOAD if (!loaded) {
SDL_SetError("DirectSound: System doesn't appear to have DX8.");
}
} if (!loaded) {
DSOUND_Unload();
} return loaded;
}

从代码中能够看出,该函数载入了“DSOUND.DLL”的DirectSoundCreate8(),DirectSoundEnumerateW(),DirectSoundCaptureEnumerateW()函数。