SDL学习

Posted 狗蛋儿l

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SDL学习相关的知识,希望对你有一定的参考价值。

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内部的架构。

几个关键的文件夹如下所示:

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

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

  3. VisualC:存储VC解决方案的文件夹。从下图中可以看出,包含了VS2008,VS2010,VS2012,VS2013等各个版本的VC的解决方案。

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()的实现位于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”);

  1. 在每一个子系统真正初始化之前,都会调用一个函数SDL_PrivateShouldInitSubsystem()。该函数用于检查目标子系统是否需要初始化。

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

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

子系统名称函数
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

以上是关于SDL学习的主要内容,如果未能解决你的问题,请参考以下文章

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

音视频连载-007基础学习篇-SDL 播放 PCM 音频文件(上)

创建网格和启动窗口 SDL/GLEW C++ 时访问冲突

音视频连载-001基础学习篇- SDL 介绍以及工程配置

我的 C++ OpenGL SDL 程序没有按预期工作

使用 SDL 加载 OpenGL 纹理