1:初始化(SDL_Init())

SDL简介

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

从这个图中可以看出,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”栏目下的文件就可以了。

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

解压缩之后,源代码目录如下图所示。

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

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

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

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

实际上从文件名称我们可以看出,其它几个文件夹中,“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()关键函数的调用关系可以用下图表示。

源代码分析

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 -;
} /* Clear the error message */
SDL_ClearError(); #if SDL_VIDEO_DRIVER_WINDOWS
if ((flags & (SDL_INIT_HAPTIC|SDL_INIT_JOYSTICK))) {
if (SDL_HelperWindowCreate() < ) {
return -;
}
}
#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() < ) {
return (-);
}
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() < ) {
return (-);
}
}
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) < ) {
return (-);
}
}
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) < ) {
return (-);
}
}
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() < ) {
return (-);
}
}
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() < ) {
return (-);
}
}
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() < ) {
return (-);
}
}
SDL_PrivateSubsystemRefCountIncr(SDL_INIT_HAPTIC);
#else
return SDL_SetError("SDL not built with haptic (force feedback) support");
#endif
} return ();
}

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) < ||
SDL_KeyboardInit() < ||
SDL_MouseInit() < ||
SDL_TouchInit() < ) {
return -;
} /* Select the proper video driver */
index = ;
video = NULL;
if (driver_name == NULL) {
driver_name = SDL_getenv("SDL_VIDEODRIVER");
}
if (driver_name != NULL) {
for (i = ; bootstrap[i]; ++i) {
if (SDL_strncasecmp(bootstrap[i]->name, driver_name, SDL_strlen(driver_name)) == ) {
if (bootstrap[i]->available()) {
video = bootstrap[i]->create(index);
break;
}
}
}
} else {
for (i = ; 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 = ; /* Set some very sane GL defaults */
_this->gl_config.driver_loaded = ;
_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) < ) {
SDL_VideoQuit();
return -;
} /* Make sure some displays were added */
if (_this->num_displays == ) {
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 ;
}

下面简单阐述一下它的大致步骤:
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 ();
}

可见该函数没有做任何的处理。WIN_CreateDevice()定义如下。

static SDL_VideoDevice *
WIN_CreateDevice(int devindex)
{
SDL_VideoDevice *device;
SDL_VideoData *data; SDL_RegisterApp(NULL, , NULL); /* Initialize all variables that we clean on shutdown */
device = (SDL_VideoDevice *) SDL_calloc(, sizeof(SDL_VideoDevice));
if (device) {
data = (struct SDL_VideoData *) SDL_calloc(, 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[];
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) < ) {
return -;
} WIN_InitKeyboard(_this);
WIN_InitMouse(_this); return ;
}

其中有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 = ; pass < ; ++pass) {
for (i = ; ; ++i) {
TCHAR DeviceName[]; if (!EnumDisplayDevices(NULL, i, &device, )) {
break;
}
if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
continue;
}
if (pass == ) {
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 = ;
for (j = ; ; ++j) {
if (!EnumDisplayDevices(DeviceName, j, &device, )) {
break;
}
if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
continue;
}
if (pass == ) {
if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {
continue;
}
} else {
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
continue;
}
}
count += WIN_AddDisplay(device.DeviceName);
}
if (count == ) {
WIN_AddDisplay(DeviceName);
}
}
}
if (_this->num_displays == ) {
return SDL_SetError("No displays available");
}
return ;
}

该函数的作用就是获得系统中显示设备的信息。目前还没有深入研究,待有时间再补上该函数的分析。

2. AUDIO(音频)

音频子系统的初始化函数是SDL_ AudioInit ()。它的源代码位于audio\SDL_audio.c文件中,如下所示。

int SDL_AudioInit(const char *driver_name)
{
int i = ;
int initialized = ;
int tried_to_init = ; 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 = ; (!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)) != )) ||
(!driver_name && backend->demand_only)) {
continue;
} tried_to_init = ;
SDL_memset(¤t_audio, , 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, , sizeof(current_audio));
return (-); /* No driver was available, so fail. */
} finalize_audio_entry_points(); return ();
}

音频初始化和视频很类似,比视频简单一些,关键在于选择一个合适的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,
};

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

static int DSOUND_Init(SDL_AudioDriverImpl * impl)
{
if (!DSOUND_Load()) {
return ;
} /* 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 ; /* this audio target is available. */
}

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

static int DSOUND_Load(void)
{
int loaded = ; 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 = ; \
}
loaded = ; /* 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()函数。

2:窗口(SDL_Window)

SDL播放视频的代码流程如下所示。

初始化:

SDL_Init(): 初始化SDL。

SDL_CreateWindow(): 创建窗口(Window)。

SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。

SDL_CreateTexture(): 创建纹理(Texture)。

循环渲染数据:

SDL_UpdateTexture(): 设置纹理的数据。

SDL_RenderCopy(): 纹理复制给渲染器。

SDL_RenderPresent(): 显示。

上篇文章分析了该流程中的第一个函数SDL_Init()。本文继续分析该流程中的第二个函数SDL_CreateWindow()

SDL_Window

SDL_Window结构体定义了一个SDL2中的窗口。如果直接使用SDL2编译好的SDK的话,是看不到它的内部结构的。有关它的定义在头文件中只有一行代码,但是这一行定义前面的注释非常之多,如下所示:

/**
* \brief The type used to identify a window
*
* \sa SDL_CreateWindow()
* \sa SDL_CreateWindowFrom()
* \sa SDL_DestroyWindow()
* \sa SDL_GetWindowData()
* \sa SDL_GetWindowFlags()
* \sa SDL_GetWindowGrab()
* \sa SDL_GetWindowPosition()
* \sa SDL_GetWindowSize()
* \sa SDL_GetWindowTitle()
* \sa SDL_HideWindow()
* \sa SDL_MaximizeWindow()
* \sa SDL_MinimizeWindow()
* \sa SDL_RaiseWindow()
* \sa SDL_RestoreWindow()
* \sa SDL_SetWindowData()
* \sa SDL_SetWindowFullscreen()
* \sa SDL_SetWindowGrab()
* \sa SDL_SetWindowIcon()
* \sa SDL_SetWindowPosition()
* \sa SDL_SetWindowSize()
* \sa SDL_SetWindowBordered()
* \sa SDL_SetWindowTitle()
* \sa SDL_ShowWindow()
*/
typedef struct SDL_Window SDL_Window;

在源代码工程中可以看到它的定义,位于video\SDL_sysvideo.h文件中。它的定义如下。

/* Define the SDL window structure, corresponding to toplevel windows */
struct SDL_Window
{
const void *magic;
Uint32 id;
char *title;
SDL_Surface *icon;
int x, y;
int w, h;
int min_w, min_h;
int max_w, max_h;
Uint32 flags;
Uint32 last_fullscreen_flags; /* Stored position and size for windowed mode */
SDL_Rect windowed; SDL_DisplayMode fullscreen_mode; float brightness;
Uint16 *gamma;
Uint16 *saved_gamma; /* (just offset into gamma) */ SDL_Surface *surface;
SDL_bool surface_valid; SDL_bool is_destroying; SDL_WindowShaper *shaper; SDL_WindowUserData *data; void *driverdata; SDL_Window *prev;
SDL_Window *next;
};

可以看出其中包含了一个“窗口”应该包含的各种属性。这个结构体中的各个变量还没有深入研究,暂不详细分析。下面来看看如何创建这个SDL_Window。

SDL_CreateWindow()


函数简介

SDL_CreateWindow()用于创建一个视频播放的窗口。SDL_CreateWindow()的原型如下。

SDL_Window * SDLCALL SDL_CreateWindow(const char *title,
int x, int y, int w,
int h, Uint32 flags);

参数含义如下。
title :窗口标题

x :窗口位置x坐标。也可以设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。

y :窗口位置y坐标。同上。

w :窗口的宽

h :窗口的高

flags :支持下列标识。包括了窗口的是否最大化、最小化,能否调整边界等等属性。

::SDL_WINDOW_FULLSCREEN,    ::SDL_WINDOW_OPENGL,

::SDL_WINDOW_HIDDEN,        ::SDL_WINDOW_BORDERLESS,

::SDL_WINDOW_RESIZABLE,     ::SDL_WINDOW_MAXIMIZED,

::SDL_WINDOW_MINIMIZED,     ::SDL_WINDOW_INPUT_GRABBED,

::SDL_WINDOW_ALLOW_HIGHDPI.

返回创建完成的窗口的ID。如果创建失败则返回0。



函数调用关系图

SDL_ CreateWindow ()关键函数的调用关系可以用下图表示。

源代码分析

SDL_CreateWindow()的源代码位于video\SDL_video.c中,如下所示。

SDL_Window * SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags)
{
SDL_Window *window;
const char *hint; if (!_this) {
/* Initialize the video system if needed */
if (SDL_VideoInit(NULL) < ) {
return NULL;
}
} /* Some platforms can't create zero-sized windows */
if (w < ) {
w = ;
}
if (h < ) {
h = ;
} /* Some platforms have OpenGL enabled by default */
#if (SDL_VIDEO_OPENGL && __MACOSX__) || __IPHONEOS__ || __ANDROID__
flags |= SDL_WINDOW_OPENGL;
#endif
if (flags & SDL_WINDOW_OPENGL) {
if (!_this->GL_CreateContext) {
SDL_SetError("No OpenGL support in video driver");
return NULL;
}
if (SDL_GL_LoadLibrary(NULL) < ) {
return NULL;
}
} /* Unless the user has specified the high-DPI disabling hint, respect the
* SDL_WINDOW_ALLOW_HIGHDPI flag.
*/
if (flags & SDL_WINDOW_ALLOW_HIGHDPI) {
hint = SDL_GetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED);
if (hint && SDL_atoi(hint) > ) {
flags &= ~SDL_WINDOW_ALLOW_HIGHDPI;
}
} window = (SDL_Window *)SDL_calloc(, sizeof(*window));
if (!window) {
SDL_OutOfMemory();
return NULL;
}
window->magic = &_this->window_magic;
window->id = _this->next_object_id++;
window->x = x;
window->y = y;
window->w = w;
window->h = h;
if (SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISUNDEFINED(y) ||
SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISCENTERED(y)) {
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
int displayIndex;
SDL_Rect bounds; displayIndex = SDL_GetIndexOfDisplay(display);
SDL_GetDisplayBounds(displayIndex, &bounds);
if (SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISCENTERED(x)) {
window->x = bounds.x + (bounds.w - w) / ;
}
if (SDL_WINDOWPOS_ISUNDEFINED(y) || SDL_WINDOWPOS_ISCENTERED(y)) {
window->y = bounds.y + (bounds.h - h) / ;
}
}
window->flags = ((flags & CREATE_FLAGS) | SDL_WINDOW_HIDDEN);
window->last_fullscreen_flags = window->flags;
window->brightness = 1.0f;
window->next = _this->windows;
window->is_destroying = SDL_FALSE; if (_this->windows) {
_this->windows->prev = window;
}
_this->windows = window; if (_this->CreateWindow && _this->CreateWindow(_this, window) < ) {
SDL_DestroyWindow(window);
return NULL;
} if (title) {
SDL_SetWindowTitle(window, title);
}
SDL_FinishWindowCreation(window, flags); /* If the window was created fullscreen, make sure the mode code matches */
SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window)); return window;
}

下面总结一下SDL_CreateWindow()的大致流程。

1. 一些为了保证各个平台的兼容性的初始化工作。各个平台创建窗口的条件不同。例如,某些平台不支持创建大小为0的窗口。再例如,某些平台默认开启OpenGL。

2. 调用SDL_calloc()为SDL_Window结构体分配一块内存。同时设置一些基本属性,例如窗口的宽高,位置等等。

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

3. 调用VideoDevice的CreateWindow()方法创建窗口。这是创建窗口这个函数中最关键的一环。在这里有一点需要注意,SDL中有一个SDL_VideoDevice类型的静态全局变量_this。SDL调用视频驱动的功能都是通过调用该指针完成的。定义如下。

static SDL_VideoDevice *_this = NULL;

该_this变量代表了当前视频驱动设备。该变量在SDL_Init()中被赋值。如果是Windows下使用,则会被赋值为“Windows视频驱动”;Android下使用,则会被赋值为“Android视频驱动”。这是上篇文章中的内容,不再重复记录。

下面我们以“Windows视频驱动”为例,看看CreateWindow()都会执行哪些函数。

首先回顾一下上篇文章中的一个知识。从上一篇文章的SDL_Init()函数的分析中我们可以得知,Windows视频驱动初始化的时候会给SDL_VideoDevice一系列的函数指针赋值,如下所示。

static SDL_VideoDevice *WIN_CreateDevice(int devindex)
{
SDL_VideoDevice *device;
SDL_VideoData *data; SDL_RegisterApp(NULL, , NULL); /* Initialize all variables that we clean on shutdown */
device = (SDL_VideoDevice *) SDL_calloc(, sizeof(SDL_VideoDevice));
if (device) {
data = (struct SDL_VideoData *) SDL_calloc(, 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;
}

从上文中可以看出,“Windows视频驱动”初始化之后,调用该SDL_VideoDevice的CreateWindow()实际上就等同于调用 WIN_CreateWindow()这个函数。因此,我们来看一下WIN_CreateWindow()这个函数的定义(位于 video\windows\SDL_windowswindow.c)。

int WIN_CreateWindow(_THIS, SDL_Window * window)
{
HWND hwnd;
RECT rect;
DWORD style = STYLE_BASIC;
int x, y;
int w, h; style |= GetWindowStyle(window); /* Figure out what the window area will be */
rect.left = window->x;
rect.top = window->y;
rect.right = window->x + window->w;
rect.bottom = window->y + window->h;
AdjustWindowRectEx(&rect, style, FALSE, );
x = rect.left;
y = rect.top;
w = (rect.right - rect.left);
h = (rect.bottom - rect.top); hwnd =
CreateWindow(SDL_Appname, TEXT(""), style, x, y, w, h, NULL, NULL,
SDL_Instance, NULL);
if (!hwnd) {
return WIN_SetError("Couldn't create window");
} WIN_PumpEvents(_this); if (SetupWindowData(_this, window, hwnd, SDL_TRUE) < ) {
DestroyWindow(hwnd);
return -;
} #if SDL_VIDEO_OPENGL_WGL
/* We need to initialize the extensions before deciding how to create ES profiles */
if (window->flags & SDL_WINDOW_OPENGL) {
WIN_GL_InitExtensions(_this);
}
#endif #if SDL_VIDEO_OPENGL_ES2
if ((window->flags & SDL_WINDOW_OPENGL) &&
_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES
#if SDL_VIDEO_OPENGL_WGL
&& (!_this->gl_data || !_this->gl_data->HAS_WGL_EXT_create_context_es2_profile)
#endif
) {
#if SDL_VIDEO_OPENGL_EGL
if (WIN_GLES_SetupWindow(_this, window) < ) {
WIN_DestroyWindow(_this, window);
return -;
}
#else
return SDL_SetError("Could not create GLES window surface (no EGL support available)");
#endif /* SDL_VIDEO_OPENGL_EGL */
} else
#endif /* SDL_VIDEO_OPENGL_ES2 */ #if SDL_VIDEO_OPENGL_WGL
if (window->flags & SDL_WINDOW_OPENGL) {
if (WIN_GL_SetupWindow(_this, window) < ) {
WIN_DestroyWindow(_this, window);
return -;
}
}
#endif return ;
}

从该函数的代码中我们可以看到很多的Win32的API。最核心的函数只有一个,就是CreateWindow()。正是这个Win32的API最终创建了 SDL的窗口。当然,为了创建出来的窗口更“优质”,包含了一些初始化的工作,例如AdjustWindowRectEx();以及一些收尾工作,例如 SetupWindowData()(该函数主要用于设置SDL_Window的参数)。
4. 完成一些收尾工作。例如设置窗口的标题,如果是“全屏模式”则设置全屏显示等等。在这里简单介绍几个函数。

SDL_SetWindowTitle()用于设置窗口的标题,它的定义如下。

void SDL_SetWindowTitle(SDL_Window * window, const char *title)
{
CHECK_WINDOW_MAGIC(window, ); if (title == window->title) {
return;
}
SDL_free(window->title);
if (title && *title) {
window->title = SDL_strdup(title);
} else {
window->title = NULL;
} if (_this->SetWindowTitle) {
_this->SetWindowTitle(_this, window);
}
}

该函数调用了SDL_VideoDevice的SetWindowTitle()。在“Windows视频驱动”中,实际的执行函数是WIN_SetWindowTitle()。该函数的定义如下。

void WIN_SetWindowTitle(_THIS, SDL_Window * window)
{
HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
LPTSTR title; if (window->title) {
title = WIN_UTF8ToString(window->title);
} else {
title = NULL;
}
SetWindowText(hwnd, title ? title : TEXT(""));
SDL_free(title);
}

从代码中可以看出,该函数调用了Win32的API函数SetWindowText()设置窗口的标题。
SDL_FinishWindowCreation()完成一些窗口的收尾工作。该函数的定义如下。

static void SDL_FinishWindowCreation(SDL_Window *window, Uint32 flags)
{
window->windowed.x = window->x;
window->windowed.y = window->y;
window->windowed.w = window->w;
window->windowed.h = window->h; if (flags & SDL_WINDOW_MAXIMIZED) {
SDL_MaximizeWindow(window);
}
if (flags & SDL_WINDOW_MINIMIZED) {
SDL_MinimizeWindow(window);
}
if (flags & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowFullscreen(window, flags);
}
if (flags & SDL_WINDOW_INPUT_GRABBED) {
SDL_SetWindowGrab(window, SDL_TRUE);
}
if (!(flags & SDL_WINDOW_HIDDEN)) {
SDL_ShowWindow(window);
}
}

从代码中可以看出,如果创建窗口的时候:

指定了“最大化”,则会执行SDL_MaximizeWindow();

指定了“最小化”,则会执行SDL_MinimizeWindow();

指定了“全屏”,则会执行SDL_SetWindowFullscreen();

指定了“抓取”(这个没有试过),则会执行SDL_SetWindowGrab();

指定了“隐藏”,则会执行SDL_ShowWindow()。

下面分别看一下SDL_MaximizeWindow(),SDL_MinimizeWindow(),SDL_SetWindowFullscreen(),SDL_ShowWindow()的代码。


SDL_MaximizeWindow()定义如下。

void SDL_MaximizeWindow(SDL_Window * window)
{
CHECK_WINDOW_MAGIC(window, ); if (window->flags & SDL_WINDOW_MAXIMIZED) {
return;
} /* !!! FIXME: should this check if the window is resizable? */ if (_this->MaximizeWindow) {
_this->MaximizeWindow(_this, window);
}
}

从代码中可以看出,SDL_MaximizeWindow()调用了SDL_VideoDevice的MaximizeWindow()函数。在“Windows视频驱动”下,实际上调用了WIN_MaximizeWindow()函数,该函数的定义如下。

void WIN_MaximizeWindow(_THIS, SDL_Window * window)
{
SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
HWND hwnd = data->hwnd;
data->expected_resize = TRUE;
ShowWindow(hwnd, SW_MAXIMIZE);
data->expected_resize = FALSE;
}

从上述代码中可以看出WIN_MaximizeWindow()调用了Win32的API函数ShowWindow()。


SDL_MinimizeWindow()定义如下。

void SDL_MinimizeWindow(SDL_Window * window)
{
CHECK_WINDOW_MAGIC(window, ); if (window->flags & SDL_WINDOW_MINIMIZED) {
return;
} SDL_UpdateFullscreenMode(window, SDL_FALSE); if (_this->MinimizeWindow) {
_this->MinimizeWindow(_this, window);
}
}

从代码中可以看出,SDL_MinimizeWindow()调用了SDL_VideoDevice的MinimizeWindow()函数。在“Windows视频驱动”下,实际上调用了WIN_MinimizeWindow()函

void WIN_MinimizeWindow(_THIS, SDL_Window * window)
{
HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
ShowWindow(hwnd, SW_MINIMIZE);
}

数,该函数的定义如下。

从上述代码中可以看出WIN_MinimizeWindow()调用了Win32的API函数ShowWindow()。

SDL_SetWindowFullscreen()定义如下。

int SDL_SetWindowFullscreen(SDL_Window * window, Uint32 flags)
{
CHECK_WINDOW_MAGIC(window, -); flags &= FULLSCREEN_MASK; if ( flags == (window->flags & FULLSCREEN_MASK) ) {
return ;
} /* clear the previous flags and OR in the new ones */
window->flags &= ~FULLSCREEN_MASK;
window->flags |= flags; SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window)); return ;
}

从代码中可以看出,SDL_SetWindowFullscreen()调用了SDL_UpdateFullscreenMode()函数,该函数的定义如下。

static void SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
{
SDL_VideoDisplay *display;
SDL_Window *other; #ifdef __MACOSX__
if (Cocoa_SetWindowFullscreenSpace(window, fullscreen)) {
window->last_fullscreen_flags = window->flags;
return;
}
#endif display = SDL_GetDisplayForWindow(window); if (fullscreen) {
/* Hide any other fullscreen windows */
if (display->fullscreen_window &&
display->fullscreen_window != window) {
SDL_MinimizeWindow(display->fullscreen_window);
}
} /* See if anything needs to be done now */
if ((display->fullscreen_window == window) == fullscreen) {
if ((window->last_fullscreen_flags & FULLSCREEN_MASK) == (window->flags & FULLSCREEN_MASK)) {
return;
}
} /* See if there are any fullscreen windows */
for (other = _this->windows; other; other = other->next) {
SDL_bool setDisplayMode = SDL_FALSE; if (other == window) {
setDisplayMode = fullscreen;
} else if (FULLSCREEN_VISIBLE(other) &&
SDL_GetDisplayForWindow(other) == display) {
setDisplayMode = SDL_TRUE;
} if (setDisplayMode) {
SDL_DisplayMode fullscreen_mode; if (SDL_GetWindowDisplayMode(other, &fullscreen_mode) == ) {
SDL_bool resized = SDL_TRUE; if (other->w == fullscreen_mode.w && other->h == fullscreen_mode.h) {
resized = SDL_FALSE;
} /* only do the mode change if we want exclusive fullscreen */
if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) {
SDL_SetDisplayModeForDisplay(display, &fullscreen_mode);
} else {
SDL_SetDisplayModeForDisplay(display, NULL);
} if (_this->SetWindowFullscreen) {
_this->SetWindowFullscreen(_this, other, display, SDL_TRUE);
}
display->fullscreen_window = other; /* Generate a mode change event here */
if (resized) {
SDL_SendWindowEvent(other, SDL_WINDOWEVENT_RESIZED,
fullscreen_mode.w, fullscreen_mode.h);
} else {
SDL_OnWindowResized(other);
} SDL_RestoreMousePosition(other); window->last_fullscreen_flags = window->flags;
return;
}
}
} /* Nope, restore the desktop mode */
SDL_SetDisplayModeForDisplay(display, NULL); if (_this->SetWindowFullscreen) {
_this->SetWindowFullscreen(_this, window, display, SDL_FALSE);
}
display->fullscreen_window = NULL; /* Generate a mode change event here */
SDL_OnWindowResized(window); /* Restore the cursor position */
SDL_RestoreMousePosition(window); window->last_fullscreen_flags = window->flags;
}

SDL_UpdateFullscreenMode() 代码很长,在这里我们只选择最关键的代码进行分析。SDL_UpdateFullscreenMode()最关键的地方在于它调用了 SDL_VideoDevice的SetWindowFullscreen()函数。在“Windows视频驱动”下,实际上调用了 WIN_SetWindowFullscreen()函数,该函数的定义如下。

void WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
{
SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
HWND hwnd = data->hwnd;
RECT rect;
SDL_Rect bounds;
DWORD style;
HWND top;
BOOL menu;
int x, y;
int w, h; if (SDL_ShouldAllowTopmost() && (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_INPUT_FOCUS)) == (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_INPUT_FOCUS)) {
top = HWND_TOPMOST;
} else {
top = HWND_NOTOPMOST;
} style = GetWindowLong(hwnd, GWL_STYLE);
style &= ~STYLE_MASK;
style |= GetWindowStyle(window); WIN_GetDisplayBounds(_this, display, &bounds); if (fullscreen) {
x = bounds.x;
y = bounds.y;
w = bounds.w;
h = bounds.h;
} else {
rect.left = ;
rect.top = ;
rect.right = window->windowed.w;
rect.bottom = window->windowed.h;
menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
AdjustWindowRectEx(&rect, style, menu, );
w = (rect.right - rect.left);
h = (rect.bottom - rect.top);
x = window->windowed.x + rect.left;
y = window->windowed.y + rect.top;
}
SetWindowLong(hwnd, GWL_STYLE, style);
data->expected_resize = TRUE;
SetWindowPos(hwnd, top, x, y, w, h, SWP_NOCOPYBITS | SWP_NOACTIVATE);
data->expected_resize = FALSE;
}

从代码中可以看出,该函数通过WIN_GetDisplayBounds()获得屏幕的尺寸,然后通过SetWindowPos()函数设置全屏窗口的大小和位置。
SDL_ShowWindow()的定义如下。

void SDL_ShowWindow(SDL_Window * window)
{
CHECK_WINDOW_MAGIC(window, ); if (window->flags & SDL_WINDOW_SHOWN) {
return;
} if (_this->ShowWindow) {
_this->ShowWindow(_this, window);
}
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_SHOWN, , );
}

SDL_ShowWindow ()调用了SDL_VideoDevice的ShowWindow()函数。在“Windows视频驱动”下,实际上调用了WIN_ShowWindow()函数,该函数的定义如下。

void WIN_ShowWindow(_THIS, SDL_Window * window)
{
HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
ShowWindow(hwnd, SW_SHOW);
}

该函数比较简单,直接调用了Win32中的ShowWindow()方法。

3:渲染器(SDL_Renderer)

上篇文章分析了该流程中的第2个函数SDL_CreateWindow()。本文继续分析该流程中的第3个函数SDL_CreateRenderer()。

SDL_Renderer

SDL_Renderer结构体定义了一个SDL2中的渲染器。如果直接使用SDL2编译好的SDK的话,是看不到它的内部结构的。有关它的定义在头文件中只有一行代码,如下所示。

/**
* \brief A structure representing rendering state
*/
struct SDL_Renderer;
typedef struct SDL_Renderer SDL_Renderer;

在源代码工程中可以看到SDL_Renderer的定义,位于render\SDL_sysrender.h文件中。它的定义如下。

/* Define the SDL renderer structure */
struct SDL_Renderer
{
const void *magic; void (*WindowEvent) (SDL_Renderer * renderer, const SDL_WindowEvent *event);
int (*GetOutputSize) (SDL_Renderer * renderer, int *w, int *h);
int (*CreateTexture) (SDL_Renderer * renderer, SDL_Texture * texture);
int (*SetTextureColorMod) (SDL_Renderer * renderer,
SDL_Texture * texture);
int (*SetTextureAlphaMod) (SDL_Renderer * renderer,
SDL_Texture * texture);
int (*SetTextureBlendMode) (SDL_Renderer * renderer,
SDL_Texture * texture);
int (*UpdateTexture) (SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, const void *pixels,
int pitch);
int (*UpdateTextureYUV) (SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect,
const Uint8 *Yplane, int Ypitch,
const Uint8 *Uplane, int Upitch,
const Uint8 *Vplane, int Vpitch);
int (*LockTexture) (SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, void **pixels, int *pitch);
void (*UnlockTexture) (SDL_Renderer * renderer, SDL_Texture * texture);
int (*SetRenderTarget) (SDL_Renderer * renderer, SDL_Texture * texture);
int (*UpdateViewport) (SDL_Renderer * renderer);
int (*UpdateClipRect) (SDL_Renderer * renderer);
int (*RenderClear) (SDL_Renderer * renderer);
int (*RenderDrawPoints) (SDL_Renderer * renderer, const SDL_FPoint * points,
int count);
int (*RenderDrawLines) (SDL_Renderer * renderer, const SDL_FPoint * points,
int count);
int (*RenderFillRects) (SDL_Renderer * renderer, const SDL_FRect * rects,
int count);
int (*RenderCopy) (SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_FRect * dstrect);
int (*RenderCopyEx) (SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcquad, const SDL_FRect * dstrect,
const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
int (*RenderReadPixels) (SDL_Renderer * renderer, const SDL_Rect * rect,
Uint32 format, void * pixels, int pitch);
void (*RenderPresent) (SDL_Renderer * renderer);
void (*DestroyTexture) (SDL_Renderer * renderer, SDL_Texture * texture); void (*DestroyRenderer) (SDL_Renderer * renderer); int (*GL_BindTexture) (SDL_Renderer * renderer, SDL_Texture *texture, float *texw, float *texh);
int (*GL_UnbindTexture) (SDL_Renderer * renderer, SDL_Texture *texture); /* The current renderer info */
SDL_RendererInfo info; /* The window associated with the renderer */
SDL_Window *window;
SDL_bool hidden; /* The logical resolution for rendering */
int logical_w;
int logical_h;
int logical_w_backup;
int logical_h_backup; /* The drawable area within the window */
SDL_Rect viewport;
SDL_Rect viewport_backup; /* The clip rectangle within the window */
SDL_Rect clip_rect;
SDL_Rect clip_rect_backup; /* The render output coordinate scale */
SDL_FPoint scale;
SDL_FPoint scale_backup; /* The list of textures */
SDL_Texture *textures;
SDL_Texture *target; Uint8 r, g, b, a; /**< Color for drawing operations values */
SDL_BlendMode blendMode; /**< The drawing blend mode */ void *driverdata;
};

通过代码可以看出其中包含了一个“渲染器”应该包含的各种属性。这个结构体中的各个变量还没有深入研究,暂不详细分析。下面来看看如何创建这个SDL_Renderer。

SDL_CreateRenderer()

函数简介

SDL中使用SDL_CreateRenderer()基于窗口创建渲染器。SDL_CreateRenderer()原型如下。

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

参数含义如下。

window : 渲染的目标窗口。

index :打算初始化的渲染设备的索引。设置“-1”则初始化默认的渲染设备。

flags :支持以下值(位于SDL_RendererFlags定义中)

SDL_RENDERER_SOFTWARE :使用软件渲染

SDL_RENDERER_ACCELERATED :使用硬件加速

SDL_RENDERER_PRESENTVSYNC:和显示器的刷新率同步

SDL_RENDERER_TARGETTEXTURE :不太懂

返回创建完成的渲染器的ID。如果创建失败则返回NULL。


函数调用关系图

SDL_CreateRenderer()关键函数的调用关系可以用下图表示。

源码分析

SDL_CreateRenderer()的源代码位于render\SDL_render.c中,如下所示。

SDL_Renderer * SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
{
#if !SDL_RENDER_DISABLED
SDL_Renderer *renderer = NULL;
int n = SDL_GetNumRenderDrivers();
const char *hint; if (!window) {
SDL_SetError("Invalid window");
return NULL;
} if (SDL_GetRenderer(window)) {
SDL_SetError("Renderer already associated with window");
return NULL;
} hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC);
if (hint) {
if (*hint == '') {
flags &= ~SDL_RENDERER_PRESENTVSYNC;
} else {
flags |= SDL_RENDERER_PRESENTVSYNC;
}
} if (index < ) {
hint = SDL_GetHint(SDL_HINT_RENDER_DRIVER);
if (hint) {
for (index = ; index < n; ++index) {
const SDL_RenderDriver *driver = render_drivers[index]; if (SDL_strcasecmp(hint, driver->info.name) == ) {
/* Create a new renderer instance */
renderer = driver->CreateRenderer(window, flags);
break;
}
}
} if (!renderer) {
for (index = ; index < n; ++index) {
const SDL_RenderDriver *driver = render_drivers[index]; if ((driver->info.flags & flags) == flags) {
/* Create a new renderer instance */
renderer = driver->CreateRenderer(window, flags);
if (renderer) {
/* Yay, we got one! */
break;
}
}
}
}
if (index == n) {
SDL_SetError("Couldn't find matching render driver");
return NULL;
}
} else {
if (index >= SDL_GetNumRenderDrivers()) {
SDL_SetError("index must be -1 or in the range of 0 - %d",
SDL_GetNumRenderDrivers() - );
return NULL;
}
/* Create a new renderer instance */
renderer = render_drivers[index]->CreateRenderer(window, flags);
} if (renderer) {
renderer->magic = &renderer_magic;
renderer->window = window;
renderer->scale.x = 1.0f;
renderer->scale.y = 1.0f; if (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN|SDL_WINDOW_MINIMIZED)) {
renderer->hidden = SDL_TRUE;
} else {
renderer->hidden = SDL_FALSE;
} SDL_SetWindowData(window, SDL_WINDOWRENDERDATA, renderer); SDL_RenderSetViewport(renderer, NULL); SDL_AddEventWatch(SDL_RendererEventWatch, renderer); SDL_LogInfo(SDL_LOG_CATEGORY_RENDER,
"Created renderer: %s", renderer->info.name);
}
return renderer;
#else
SDL_SetError("SDL not built with rendering support");
return NULL;
#endif
}

SDL_CreateRenderer() 中最重要的一个函数就是它调用了SDL_RenderDriver的CreateRenderer()方法。通过该方法可以创建一个渲染器。围绕着这个方法,包含了一些初始化工作以及一些收尾工作。下面针对这个最核心的函数进行分析。
我们首先来看一下SDL_RenderDriver这个结构体。从字面的意思可以看出它代表了“渲染器的驱动程序”。这个结构体的定义如下。

/* Define the SDL render driver structure */
struct SDL_RenderDriver
{
SDL_Renderer *(*CreateRenderer) (SDL_Window * window, Uint32 flags); /* Info about the renderer capabilities */
SDL_RendererInfo info;
};

从代码中可以看出,这个结构体的成员比较简单,包含了一个函数指针CreateRenderer()和一个存储信息的SDL_RendererInfo类型的结构体info。CreateRenderer()是用于创建渲染器的函数,而SDL_RendererInfo则包含了该结构体的一些信息,可以看一下SDL_RendererInfo的定义。

/**
* \brief Information on the capabilities of a render driver or context.
*/
typedef struct SDL_RendererInfo
{
const char *name; /**< The name of the renderer */
Uint32 flags; /**< Supported ::SDL_RendererFlags */
Uint32 num_texture_formats; /**< The number of available texture formats */
Uint32 texture_formats[]; /**< The available texture formats */
int max_texture_width; /**< The maximimum texture width */
int max_texture_height; /**< The maximimum texture height */
} SDL_RendererInfo;

在SDL中有一个全局的SDL_RenderDriver类型的静态数组render_drivers,其中存储了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_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
};

从render_drivers数组的定义可以看出,其中包含了Direct3D,OpenGL,OpenGL ES等各种渲染器的驱动程序。我们可以选择几个看一下。
例如Direct3D的渲染器驱动程序D3D_RenderDriver的定义如下(位于render\direct3d\SDL_render_d3d.c)。

SDL_RenderDriver D3D_RenderDriver = {
D3D_CreateRenderer,
{
"direct3d",
(SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
,
{SDL_PIXELFORMAT_ARGB8888},
,
}
};

可以看出创建Direct3D渲染器的函数是D3D_CreateRenderer()。
OpenGL的渲染器驱动程序GL_RenderDriver的定义如下(位于render\opengl\SDL_render_gl.c)。

SDL_RenderDriver GL_RenderDriver = {
GL_CreateRenderer,
{
"opengl",
(SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
,
{SDL_PIXELFORMAT_ARGB8888},
,
}
};

可以看出创建OpenGL渲染器的函数是GL_CreateRenderer()。
软件渲染器驱动程序SW_RenderDriver的定义如下(位于render\software\SDL_render_sw.c)。

SDL_RenderDriver SW_RenderDriver = {
SW_CreateRenderer,
{
"software",
SDL_RENDERER_SOFTWARE | SDL_RENDERER_TARGETTEXTURE,
,
{
SDL_PIXELFORMAT_RGB555,
SDL_PIXELFORMAT_RGB565,
SDL_PIXELFORMAT_RGB888,
SDL_PIXELFORMAT_BGR888,
SDL_PIXELFORMAT_ARGB8888,
SDL_PIXELFORMAT_RGBA8888,
SDL_PIXELFORMAT_ABGR8888,
SDL_PIXELFORMAT_BGRA8888
},
,
}
};

可以看出创建软件渲染器的函数是SW_CreateRenderer ()。
有关SDL_RenderDriver这个结构体就不再多说了。下面分别看一下Direct3D,OpenGL,Software这三种最常见的渲染器的创建方法。

1. Direct3D

Direct3D 的渲染器在创建函数是D3D_CreateRenderer()。该函数位于render\direct3d\SDL_render_d3d.c文件中。首先看一下它的代码。

SDL_Renderer * D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
{
SDL_Renderer *renderer;
D3D_RenderData *data;
SDL_SysWMinfo windowinfo;
HRESULT result;
const char *hint;
D3DPRESENT_PARAMETERS pparams;
IDirect3DSwapChain9 *chain;
D3DCAPS9 caps;
DWORD device_flags;
Uint32 window_flags;
int w, h;
SDL_DisplayMode fullscreen_mode;
int displayIndex; renderer = (SDL_Renderer *) SDL_calloc(, sizeof(*renderer));
if (!renderer) {
SDL_OutOfMemory();
return NULL;
} data = (D3D_RenderData *) SDL_calloc(, sizeof(*data));
if (!data) {
SDL_free(renderer);
SDL_OutOfMemory();
return NULL;
} if (!D3D_LoadDLL(&data->d3dDLL, &data->d3d)) {
SDL_free(renderer);
SDL_free(data);
SDL_SetError("Unable to create Direct3D interface");
return NULL;
} renderer->WindowEvent = D3D_WindowEvent;
renderer->CreateTexture = D3D_CreateTexture;
renderer->UpdateTexture = D3D_UpdateTexture;
renderer->UpdateTextureYUV = D3D_UpdateTextureYUV;
renderer->LockTexture = D3D_LockTexture;
renderer->UnlockTexture = D3D_UnlockTexture;
renderer->SetRenderTarget = D3D_SetRenderTarget;
renderer->UpdateViewport = D3D_UpdateViewport;
renderer->UpdateClipRect = D3D_UpdateClipRect;
renderer->RenderClear = D3D_RenderClear;
renderer->RenderDrawPoints = D3D_RenderDrawPoints;
renderer->RenderDrawLines = D3D_RenderDrawLines;
renderer->RenderFillRects = D3D_RenderFillRects;
renderer->RenderCopy = D3D_RenderCopy;
renderer->RenderCopyEx = D3D_RenderCopyEx;
renderer->RenderReadPixels = D3D_RenderReadPixels;
renderer->RenderPresent = D3D_RenderPresent;
renderer->DestroyTexture = D3D_DestroyTexture;
renderer->DestroyRenderer = D3D_DestroyRenderer;
renderer->info = D3D_RenderDriver.info;
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
renderer->driverdata = data; SDL_VERSION(&windowinfo.version);
SDL_GetWindowWMInfo(window, &windowinfo); window_flags = SDL_GetWindowFlags(window);
SDL_GetWindowSize(window, &w, &h);
SDL_GetWindowDisplayMode(window, &fullscreen_mode); SDL_zero(pparams);
pparams.hDeviceWindow = windowinfo.info.win.window;
pparams.BackBufferWidth = w;
pparams.BackBufferHeight = h;
if (window_flags & SDL_WINDOW_FULLSCREEN) {
pparams.BackBufferFormat =
PixelFormatToD3DFMT(fullscreen_mode.format);
} else {
pparams.BackBufferFormat = D3DFMT_UNKNOWN;
}
pparams.BackBufferCount = ;
pparams.SwapEffect = D3DSWAPEFFECT_DISCARD; if (window_flags & SDL_WINDOW_FULLSCREEN) {
if ((window_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
pparams.Windowed = TRUE;
pparams.FullScreen_RefreshRateInHz = ;
} else {
pparams.Windowed = FALSE;
pparams.FullScreen_RefreshRateInHz = fullscreen_mode.refresh_rate;
}
} else {
pparams.Windowed = TRUE;
pparams.FullScreen_RefreshRateInHz = ;
}
if (flags & SDL_RENDERER_PRESENTVSYNC) {
pparams.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
} else {
pparams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
} /* Get the adapter for the display that the window is on */
displayIndex = SDL_GetWindowDisplayIndex(window);
data->adapter = SDL_Direct3D9GetAdapterIndex(displayIndex); IDirect3D9_GetDeviceCaps(data->d3d, data->adapter, D3DDEVTYPE_HAL, &caps); device_flags = D3DCREATE_FPU_PRESERVE;
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
device_flags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
} else {
device_flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
} hint = SDL_GetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE);
if (hint && SDL_atoi(hint)) {
device_flags |= D3DCREATE_MULTITHREADED;
} result = IDirect3D9_CreateDevice(data->d3d, data->adapter,
D3DDEVTYPE_HAL,
pparams.hDeviceWindow,
device_flags,
&pparams, &data->device);
if (FAILED(result)) {
D3D_DestroyRenderer(renderer);
D3D_SetError("CreateDevice()", result);
return NULL;
} /* Get presentation parameters to fill info */
result = IDirect3DDevice9_GetSwapChain(data->device, , &chain);
if (FAILED(result)) {
D3D_DestroyRenderer(renderer);
D3D_SetError("GetSwapChain()", result);
return NULL;
}
result = IDirect3DSwapChain9_GetPresentParameters(chain, &pparams);
if (FAILED(result)) {
IDirect3DSwapChain9_Release(chain);
D3D_DestroyRenderer(renderer);
D3D_SetError("GetPresentParameters()", result);
return NULL;
}
IDirect3DSwapChain9_Release(chain);
if (pparams.PresentationInterval == D3DPRESENT_INTERVAL_ONE) {
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
}
data->pparams = pparams; IDirect3DDevice9_GetDeviceCaps(data->device, &caps);
renderer->info.max_texture_width = caps.MaxTextureWidth;
renderer->info.max_texture_height = caps.MaxTextureHeight;
if (caps.NumSimultaneousRTs >= ) {
renderer->info.flags |= SDL_RENDERER_TARGETTEXTURE;
} if (caps.PrimitiveMiscCaps & D3DPMISCCAPS_SEPARATEALPHABLEND) {
data->enableSeparateAlphaBlend = SDL_TRUE;
} /* Store the default render target */
IDirect3DDevice9_GetRenderTarget(data->device, , &data->defaultRenderTarget );
data->currentRenderTarget = NULL; /* Set up parameters for rendering */
D3D_InitRenderState(data); if (caps.MaxSimultaneousTextures >= )
{
#ifdef ASSEMBLE_SHADER
/* This shader was created by running the following HLSL through the fxc compiler
and then tuning the generated assembly. fxc /T fx_4_0 /O3 /Gfa /Fc yuv.fxc yuv.fx --- yuv.fx ---
Texture2D g_txY;
Texture2D g_txU;
Texture2D g_txV; SamplerState samLinear
{
Filter = ANISOTROPIC;
AddressU = Clamp;
AddressV = Clamp;
MaxAnisotropy = 1;
}; struct VS_OUTPUT
{
float2 TextureUV : TEXCOORD0;
}; struct PS_OUTPUT
{
float4 RGBAColor : SV_Target;
}; PS_OUTPUT YUV420( VS_OUTPUT In )
{
const float3 offset = {-0.0625, -0.5, -0.5};
const float3 Rcoeff = {1.164, 0.000, 1.596};
const float3 Gcoeff = {1.164, -0.391, -0.813};
const float3 Bcoeff = {1.164, 2.018, 0.000}; PS_OUTPUT Output;
float2 TextureUV = In.TextureUV; float3 yuv;
yuv.x = g_txY.Sample( samLinear, TextureUV ).r;
yuv.y = g_txU.Sample( samLinear, TextureUV ).r;
yuv.z = g_txV.Sample( samLinear, TextureUV ).r; yuv += offset;
Output.RGBAColor.r = dot(yuv, Rcoeff);
Output.RGBAColor.g = dot(yuv, Gcoeff);
Output.RGBAColor.b = dot(yuv, Bcoeff);
Output.RGBAColor.a = 1.0f; return Output;
} technique10 RenderYUV420
{
pass P0
{
SetPixelShader( CompileShader( ps_4_0_level_9_0, YUV420() ) );
}
}
*/
const char *shader_text =
"ps_2_0\n"
"def c0, -0.0625, -0.5, -0.5, 1\n"
"def c1, 1.16400003, 0, 1.59599996, 0\n"
"def c2, 1.16400003, -0.391000003, -0.813000023, 0\n"
"def c3, 1.16400003, 2.01799989, 0, 0\n"
"dcl t0.xy\n"
"dcl v0.xyzw\n"
"dcl_2d s0\n"
"dcl_2d s1\n"
"dcl_2d s2\n"
"texld r0, t0, s0\n"
"texld r1, t0, s1\n"
"texld r2, t0, s2\n"
"mov r0.y, r1.x\n"
"mov r0.z, r2.x\n"
"add r0.xyz, r0, c0\n"
"dp3 r1.x, r0, c1\n"
"dp3 r1.y, r0, c2\n"
"dp2add r1.z, r0, c3, c3.z\n" /* Logically this is "dp3 r1.z, r0, c3" but the optimizer did its magic */
"mov r1.w, c0.w\n"
"mul r0, r1, v0\n" /* Not in the HLSL, multiply by vertex color */
"mov oC0, r0\n"
;
LPD3DXBUFFER pCode;
LPD3DXBUFFER pErrorMsgs;
LPDWORD shader_data = NULL;
DWORD shader_size = ;
result = D3DXAssembleShader(shader_text, SDL_strlen(shader_text), NULL, NULL, , &pCode, &pErrorMsgs);
if (!FAILED(result)) {
shader_data = (DWORD*)pCode->lpVtbl->GetBufferPointer(pCode);
shader_size = pCode->lpVtbl->GetBufferSize(pCode);
PrintShaderData(shader_data, shader_size);
} else {
const char *error = (const char *)pErrorMsgs->lpVtbl->GetBufferPointer(pErrorMsgs);
SDL_SetError("Couldn't assemble shader: %s", error);
}
#else
const DWORD shader_data[] = {
0xffff0200, 0x05000051, 0xa00f0000, 0xbd800000, 0xbf000000, 0xbf000000,
0x3f800000, 0x05000051, 0xa00f0001, 0x3f94fdf4, 0x00000000, 0x3fcc49ba,
0x00000000, 0x05000051, 0xa00f0002, 0x3f94fdf4, 0xbec83127, 0xbf5020c5,
0x00000000, 0x05000051, 0xa00f0003, 0x3f94fdf4, 0x400126e9, 0x00000000,
0x00000000, 0x0200001f, 0x80000000, 0xb0030000, 0x0200001f, 0x80000000,
0x900f0000, 0x0200001f, 0x90000000, 0xa00f0800, 0x0200001f, 0x90000000,
0xa00f0801, 0x0200001f, 0x90000000, 0xa00f0802, 0x03000042, 0x800f0000,
0xb0e40000, 0xa0e40800, 0x03000042, 0x800f0001, 0xb0e40000, 0xa0e40801,
0x03000042, 0x800f0002, 0xb0e40000, 0xa0e40802, 0x02000001, 0x80020000,
0x80000001, 0x02000001, 0x80040000, 0x80000002, 0x03000002, 0x80070000,
0x80e40000, 0xa0e40000, 0x03000008, 0x80010001, 0x80e40000, 0xa0e40001,
0x03000008, 0x80020001, 0x80e40000, 0xa0e40002, 0x0400005a, 0x80040001,
0x80e40000, 0xa0e40003, 0xa0aa0003, 0x02000001, 0x80080001, 0xa0ff0000,
0x03000005, 0x800f0000, 0x80e40001, 0x90e40000, 0x02000001, 0x800f0800,
0x80e40000, 0x0000ffff
};
#endif
if (shader_data != NULL) {
result = IDirect3DDevice9_CreatePixelShader(data->device, shader_data, &data->ps_yuv);
if (!FAILED(result)) {
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
} else {
D3D_SetError("CreatePixelShader()", result);
}
}
} return renderer;
}

D3D_CreateRenderer()这个函数的代码很长。在这里提取它最重点的几个进行简单的分析。

PS:由于这个函数中包含了大量的Direct3D的API,这方面如果不熟悉的话,可以参考以下两篇文章:

最简单的视音频播放示例3:Direct3D播放YUV,RGB(通过Surface)

最简单的视音频播放示例4:Direct3D播放RGB(通过Texture)

(1) 为SDL_Renderer分配内存
这一步比较简单。直接使用SDL_calloc()分配内存就可以了。SDL_calloc()实际上就是calloc(),这一点在前面的文章中已经叙述,在这里不再重复。

(2) 加载Direct3D

加载Direct3D通过函数D3D_LoadDLL()完成。调用该函数可以得到一个IDirect3D9类型的接口。IDirect3D9接口可以用于完成D3D后续的初始化工作。D3D_LoadDLL()函数的代码如下。

SDL_bool D3D_LoadDLL( void **pD3DDLL, IDirect3D9 **pDirect3D9Interface )
{
*pD3DDLL = SDL_LoadObject("D3D9.DLL");
if (*pD3DDLL) {
IDirect3D9 *(WINAPI * D3DCreate) (UINT SDKVersion); D3DCreate =
(IDirect3D9 * (WINAPI *) (UINT)) SDL_LoadFunction(*pD3DDLL,
"Direct3DCreate9");
if (D3DCreate) {
*pDirect3D9Interface = D3DCreate(D3D_SDK_VERSION);
}
if (!*pDirect3D9Interface) {
SDL_UnloadObject(*pD3DDLL);
*pD3DDLL = NULL;
return SDL_FALSE;
} return SDL_TRUE;
} else {
*pDirect3D9Interface = NULL;
return SDL_FALSE;
}
}

从代码中可以看出,该函数加载了一个“D3D9.DLL”的Dll,并且调用了其中的Direct3DCreate9()方法。
(3) 渲染器接口函数赋值

SDL_Render结构体中有一系列的函数指针,包含了有关渲染器的各种功能。SDL通过调用这些函数指针就可以调用渲染器相应的功能。这是SDL支持多种渲染器的一个重要特点。代码如下所示。

renderer->WindowEvent = D3D_WindowEvent;
renderer->CreateTexture = D3D_CreateTexture;
renderer->UpdateTexture = D3D_UpdateTexture;
renderer->UpdateTextureYUV = D3D_UpdateTextureYUV;
renderer->LockTexture = D3D_LockTexture;
renderer->UnlockTexture = D3D_UnlockTexture;
renderer->SetRenderTarget = D3D_SetRenderTarget;
renderer->UpdateViewport = D3D_UpdateViewport;
renderer->UpdateClipRect = D3D_UpdateClipRect;
renderer->RenderClear = D3D_RenderClear;
renderer->RenderDrawPoints = D3D_RenderDrawPoints;
renderer->RenderDrawLines = D3D_RenderDrawLines;
renderer->RenderFillRects = D3D_RenderFillRects;
renderer->RenderCopy = D3D_RenderCopy;
renderer->RenderCopyEx = D3D_RenderCopyEx;
renderer->RenderReadPixels = D3D_RenderReadPixels;
renderer->RenderPresent = D3D_RenderPresent;
renderer->DestroyTexture = D3D_DestroyTexture;
renderer->DestroyRenderer = D3D_DestroyRenderer;

(4) 创建Device

创建Direct3D的Device通过IDirect3D9_CreateDevice()函数来实现。这一方面的知识不再叙述,可以参考Direct3D创建Device的相关的文章。

(5) 设置渲染状态

设置渲染状态在函数D3D_InitRenderState()中完成。该部分的知识也不再详述,可以参考Direct3D相关的渲染教程。贴出D3D_InitRenderState()的代码。

static void D3D_InitRenderState(D3D_RenderData *data)
{
D3DMATRIX matrix; IDirect3DDevice9 *device = data->device; IDirect3DDevice9_SetVertexShader(device, NULL);
IDirect3DDevice9_SetFVF(device, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
IDirect3DDevice9_SetRenderState(device, D3DRS_ZENABLE, D3DZB_FALSE);
IDirect3DDevice9_SetRenderState(device, D3DRS_CULLMODE, D3DCULL_NONE);
IDirect3DDevice9_SetRenderState(device, D3DRS_LIGHTING, FALSE); /* Enable color modulation by diffuse color */
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_COLOROP,
D3DTOP_MODULATE);
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_COLORARG1,
D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_COLORARG2,
D3DTA_DIFFUSE); /* Enable alpha modulation by diffuse alpha */
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_ALPHAOP,
D3DTOP_MODULATE);
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_ALPHAARG1,
D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_ALPHAARG2,
D3DTA_DIFFUSE); /* Enable separate alpha blend function, if possible */
if (data->enableSeparateAlphaBlend) {
IDirect3DDevice9_SetRenderState(device, D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
} /* Disable second texture stage, since we're done */
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_COLOROP,
D3DTOP_DISABLE);
IDirect3DDevice9_SetTextureStageState(device, , D3DTSS_ALPHAOP,
D3DTOP_DISABLE); /* Set an identity world and view matrix */
matrix.m[][] = 1.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 1.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 1.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 0.0f;
matrix.m[][] = 1.0f;
IDirect3DDevice9_SetTransform(device, D3DTS_WORLD, &matrix);
IDirect3DDevice9_SetTransform(device, D3DTS_VIEW, &matrix); /* Reset our current scale mode */
SDL_memset(data->scaleMode, 0xFF, sizeof(data->scaleMode)); /* Start the render with beginScene */
data->beginScene = SDL_TRUE;
}

(6) 创建Shader

创建Shader通过函数IDirect3DDevice9_CreatePixelShader()完成。

完成以上步骤之后,Direct3D的渲染器就创建完毕了。

2. OpenGL

OpenGL 的渲染器在创建函数是GL_CreateRenderer()。该函数位于render\opengl\SDL_render_gl.c文件中。首先看一下它的代码。

PS:其中用到了OpenGL的很多API。如果对OpenGL的API还不熟悉的话,可以参考文章:

最简单的视音频播放示例6:OpenGL播放YUV420P(通过Texture,使用Shader)

SDL_Renderer * GL_CreateRenderer(SDL_Window * window, Uint32 flags)
{
SDL_Renderer *renderer;
GL_RenderData *data;
const char *hint;
GLint value;
Uint32 window_flags;
int profile_mask, major, minor; SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); window_flags = SDL_GetWindowFlags(window);
if (!(window_flags & SDL_WINDOW_OPENGL) ||
profile_mask == SDL_GL_CONTEXT_PROFILE_ES || major != RENDERER_CONTEXT_MAJOR || minor != RENDERER_CONTEXT_MINOR) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, );
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR); if (SDL_RecreateWindow(window, window_flags | SDL_WINDOW_OPENGL) < ) {
/* Uh oh, better try to put it back... */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
SDL_RecreateWindow(window, window_flags);
return NULL;
}
} renderer = (SDL_Renderer *) SDL_calloc(, sizeof(*renderer));
if (!renderer) {
SDL_OutOfMemory();
return NULL;
} data = (GL_RenderData *) SDL_calloc(, sizeof(*data));
if (!data) {
GL_DestroyRenderer(renderer);
SDL_OutOfMemory();
return NULL;
} renderer->WindowEvent = GL_WindowEvent;
renderer->GetOutputSize = GL_GetOutputSize;
renderer->CreateTexture = GL_CreateTexture;
renderer->UpdateTexture = GL_UpdateTexture;
renderer->UpdateTextureYUV = GL_UpdateTextureYUV;
renderer->LockTexture = GL_LockTexture;
renderer->UnlockTexture = GL_UnlockTexture;
renderer->SetRenderTarget = GL_SetRenderTarget;
renderer->UpdateViewport = GL_UpdateViewport;
renderer->UpdateClipRect = GL_UpdateClipRect;
renderer->RenderClear = GL_RenderClear;
renderer->RenderDrawPoints = GL_RenderDrawPoints;
renderer->RenderDrawLines = GL_RenderDrawLines;
renderer->RenderFillRects = GL_RenderFillRects;
renderer->RenderCopy = GL_RenderCopy;
renderer->RenderCopyEx = GL_RenderCopyEx;
renderer->RenderReadPixels = GL_RenderReadPixels;
renderer->RenderPresent = GL_RenderPresent;
renderer->DestroyTexture = GL_DestroyTexture;
renderer->DestroyRenderer = GL_DestroyRenderer;
renderer->GL_BindTexture = GL_BindTexture;
renderer->GL_UnbindTexture = GL_UnbindTexture;
renderer->info = GL_RenderDriver.info;
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
renderer->driverdata = data;
renderer->window = window; data->context = SDL_GL_CreateContext(window);
if (!data->context) {
GL_DestroyRenderer(renderer);
return NULL;
}
if (SDL_GL_MakeCurrent(window, data->context) < ) {
GL_DestroyRenderer(renderer);
return NULL;
} if (GL_LoadFunctions(data) < ) {
GL_DestroyRenderer(renderer);
return NULL;
} #ifdef __MACOSX__
/* Enable multi-threaded rendering */
/* Disabled until Ryan finishes his VBO/PBO code...
CGLEnable(CGLGetCurrentContext(), kCGLCEMPEngine);
*/
#endif if (flags & SDL_RENDERER_PRESENTVSYNC) {
SDL_GL_SetSwapInterval();
} else {
SDL_GL_SetSwapInterval();
}
if (SDL_GL_GetSwapInterval() > ) {
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
} /* Check for debug output support */
if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &value) == &&
(value & SDL_GL_CONTEXT_DEBUG_FLAG)) {
data->debug_enabled = SDL_TRUE;
}
if (data->debug_enabled && SDL_GL_ExtensionSupported("GL_ARB_debug_output")) {
PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARBFunc = (PFNGLDEBUGMESSAGECALLBACKARBPROC) SDL_GL_GetProcAddress("glDebugMessageCallbackARB"); data->GL_ARB_debug_output_supported = SDL_TRUE;
data->glGetPointerv(GL_DEBUG_CALLBACK_FUNCTION_ARB, (GLvoid **)&data->next_error_callback);
data->glGetPointerv(GL_DEBUG_CALLBACK_USER_PARAM_ARB, &data->next_error_userparam);
glDebugMessageCallbackARBFunc(GL_HandleDebugMessage, renderer); /* Make sure our callback is called when errors actually happen */
data->glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
} if (SDL_GL_ExtensionSupported("GL_ARB_texture_rectangle")
|| SDL_GL_ExtensionSupported("GL_EXT_texture_rectangle")) {
data->GL_ARB_texture_rectangle_supported = SDL_TRUE;
data->glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB, &value);
renderer->info.max_texture_width = value;
renderer->info.max_texture_height = value;
} else {
data->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
renderer->info.max_texture_width = value;
renderer->info.max_texture_height = value;
} /* Check for multitexture support */
if (SDL_GL_ExtensionSupported("GL_ARB_multitexture")) {
data->glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glActiveTextureARB");
if (data->glActiveTextureARB) {
data->GL_ARB_multitexture_supported = SDL_TRUE;
data->glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &data->num_texture_units);
}
} /* Check for shader support */
hint = SDL_GetHint(SDL_HINT_RENDER_OPENGL_SHADERS);
if (!hint || *hint != '') {
data->shaders = GL_CreateShaderContext();
}
SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL shaders: %s",
data->shaders ? "ENABLED" : "DISABLED"); /* We support YV12 textures using 3 textures and a shader */
if (data->shaders && data->num_texture_units >= ) {
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
} #ifdef __MACOSX__
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_UYVY;
#endif if (SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object")) {
data->GL_EXT_framebuffer_object_supported = SDL_TRUE;
data->glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)
SDL_GL_GetProcAddress("glGenFramebuffersEXT");
data->glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
data->glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
data->glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)
SDL_GL_GetProcAddress("glBindFramebufferEXT");
data->glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
renderer->info.flags |= SDL_RENDERER_TARGETTEXTURE;
}
data->framebuffers = NULL; /* Set up parameters for rendering */
GL_ResetState(renderer); return renderer;
}

GL_CreateRenderer()这个函数的代码很长。在这里提取它最重点的几个进行简单的分析。
(1) 为SDL_Renderer分配内存

这一步比较简单。直接使用SDL_calloc()分配内存就可以了。

(2) 渲染器接口函数赋值

SDL_Render结构体中有一系列的函数指针,包含了有关渲染器的各种功能。这一点在Direct3D的时候已经提过,不再重复。代码如下。

renderer->WindowEvent = GL_WindowEvent;
renderer->GetOutputSize = GL_GetOutputSize;
renderer->CreateTexture = GL_CreateTexture;
renderer->UpdateTexture = GL_UpdateTexture;
renderer->UpdateTextureYUV = GL_UpdateTextureYUV;
renderer->LockTexture = GL_LockTexture;
renderer->UnlockTexture = GL_UnlockTexture;
renderer->SetRenderTarget = GL_SetRenderTarget;
renderer->UpdateViewport = GL_UpdateViewport;
renderer->UpdateClipRect = GL_UpdateClipRect;
renderer->RenderClear = GL_RenderClear;
renderer->RenderDrawPoints = GL_RenderDrawPoints;
renderer->RenderDrawLines = GL_RenderDrawLines;
renderer->RenderFillRects = GL_RenderFillRects;
renderer->RenderCopy = GL_RenderCopy;
renderer->RenderCopyEx = GL_RenderCopyEx;
renderer->RenderReadPixels = GL_RenderReadPixels;
renderer->RenderPresent = GL_RenderPresent;
renderer->DestroyTexture = GL_DestroyTexture;
renderer->DestroyRenderer = GL_DestroyRenderer;
renderer->GL_BindTexture = GL_BindTexture;
renderer->GL_UnbindTexture = GL_UnbindTexture;

(3) 初始化OpenGL
初始化OpenGL各种变量,包括SDL_GL_CreateContext(),SDL_GL_MakeCurrent(),GL_LoadFunctions()等函数。这一部分还没有详细分析。

(4) 初始化Shader

对Shader的初始化在函数GL_CreateShaderContext()中完成。GL_CreateShaderContext()的代码如下(位于render\opengl\SDL_shaders_gl.c)。

GL_ShaderContext * GL_CreateShaderContext()
{
GL_ShaderContext *ctx;
SDL_bool shaders_supported;
int i; ctx = (GL_ShaderContext *)SDL_calloc(, sizeof(*ctx));
if (!ctx) {
return NULL;
} if (SDL_GL_ExtensionSupported("GL_ARB_texture_rectangle")
|| SDL_GL_ExtensionSupported("GL_EXT_texture_rectangle")) {
ctx->GL_ARB_texture_rectangle_supported = SDL_TRUE;
} /* Check for shader support */
shaders_supported = SDL_FALSE;
if (SDL_GL_ExtensionSupported("GL_ARB_shader_objects") &&
SDL_GL_ExtensionSupported("GL_ARB_shading_language_100") &&
SDL_GL_ExtensionSupported("GL_ARB_vertex_shader") &&
SDL_GL_ExtensionSupported("GL_ARB_fragment_shader")) {
ctx->glGetError = (GLenum (*)(void)) SDL_GL_GetProcAddress("glGetError");
ctx->glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) SDL_GL_GetProcAddress("glAttachObjectARB");
ctx->glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) SDL_GL_GetProcAddress("glCompileShaderARB");
ctx->glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glCreateProgramObjectARB");
ctx->glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) SDL_GL_GetProcAddress("glCreateShaderObjectARB");
ctx->glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) SDL_GL_GetProcAddress("glDeleteObjectARB");
ctx->glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) SDL_GL_GetProcAddress("glGetInfoLogARB");
ctx->glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterivARB");
ctx->glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetUniformLocationARB");
ctx->glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) SDL_GL_GetProcAddress("glLinkProgramARB");
ctx->glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glShaderSourceARB");
ctx->glUniform1iARB = (PFNGLUNIFORM1IARBPROC) SDL_GL_GetProcAddress("glUniform1iARB");
ctx->glUniform1fARB = (PFNGLUNIFORM1FARBPROC) SDL_GL_GetProcAddress("glUniform1fARB");
ctx->glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glUseProgramObjectARB");
if (ctx->glGetError &&
ctx->glAttachObjectARB &&
ctx->glCompileShaderARB &&
ctx->glCreateProgramObjectARB &&
ctx->glCreateShaderObjectARB &&
ctx->glDeleteObjectARB &&
ctx->glGetInfoLogARB &&
ctx->glGetObjectParameterivARB &&
ctx->glGetUniformLocationARB &&
ctx->glLinkProgramARB &&
ctx->glShaderSourceARB &&
ctx->glUniform1iARB &&
ctx->glUniform1fARB &&
ctx->glUseProgramObjectARB) {
shaders_supported = SDL_TRUE;
}
} if (!shaders_supported) {
SDL_free(ctx);
return NULL;
} /* Compile all the shaders */
for (i = ; i < NUM_SHADERS; ++i) {
if (!CompileShaderProgram(ctx, i, &ctx->shaders[i])) {
GL_DestroyShaderContext(ctx);
return NULL;
}
} /* We're done! */
return ctx;
}

上述代码主要完成了以下两步:
第一步,初始化GL_ShaderContext。GL_ShaderContext中包含了OpenGL的Shader方面用到的各种接口函数。GL_ShaderContext定义如下。

struct GL_ShaderContext
{
GLenum (*glGetError)(void); PFNGLATTACHOBJECTARBPROC glAttachObjectARB;
PFNGLCOMPILESHADERARBPROC glCompileShaderARB;
PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB;
PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB;
PFNGLDELETEOBJECTARBPROC glDeleteObjectARB;
PFNGLGETINFOLOGARBPROC glGetInfoLogARB;
PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameterivARB;
PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB;
PFNGLLINKPROGRAMARBPROC glLinkProgramARB;
PFNGLSHADERSOURCEARBPROC glShaderSourceARB;
PFNGLUNIFORM1IARBPROC glUniform1iARB;
PFNGLUNIFORM1FARBPROC glUniform1fARB;
PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB; SDL_bool GL_ARB_texture_rectangle_supported; GL_ShaderData shaders[NUM_SHADERS];
};

看这个结构体的定义会给人一种很混乱的感觉。不用去理会那些大串的大写字母,只要知道这个结构体是函数的接口的“合集”就可以了。从函数的名称中我们可以看出有编译Shader的glCreateShaderObject(),glShaderSource(),glCompileShader()等;以及编译Program的glCreateProgramObject(),glAttachObject (),glLinkProgram(),glUseProgramObject ()等等。
GL_CreateShaderContext()函数中创建了一个GL_ShaderContext并对其中的接口函数进行了赋值。

第二步,编译Shader程序。该功能在CompileShaderProgram()函数中完成。CompileShaderProgram()的函数代码如下所示。

static SDL_bool CompileShaderProgram(GL_ShaderContext *ctx, int index, GL_ShaderData *data)
{
const int num_tmus_bound = ;
const char *vert_defines = "";
const char *frag_defines = "";
int i;
GLint location; if (index == SHADER_NONE) {
return SDL_TRUE;
} ctx->glGetError(); /* Make sure we use the correct sampler type for our texture type */
if (ctx->GL_ARB_texture_rectangle_supported) {
frag_defines =
"#define sampler2D sampler2DRect\n"
"#define texture2D texture2DRect\n";
} /* Create one program object to rule them all */
data->program = ctx->glCreateProgramObjectARB(); /* Create the vertex shader */
data->vert_shader = ctx->glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
if (!CompileShader(ctx, data->vert_shader, vert_defines, shader_source[index][])) {
return SDL_FALSE;
} /* Create the fragment shader */
data->frag_shader = ctx->glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
if (!CompileShader(ctx, data->frag_shader, frag_defines, shader_source[index][])) {
return SDL_FALSE;
} /* ... and in the darkness bind them */
ctx->glAttachObjectARB(data->program, data->vert_shader);
ctx->glAttachObjectARB(data->program, data->frag_shader);
ctx->glLinkProgramARB(data->program); /* Set up some uniform variables */
ctx->glUseProgramObjectARB(data->program);
for (i = ; i < num_tmus_bound; ++i) {
char tex_name[];
SDL_snprintf(tex_name, SDL_arraysize(tex_name), "tex%d", i);
location = ctx->glGetUniformLocationARB(data->program, tex_name);
if (location >= ) {
ctx->glUniform1iARB(location, i);
}
}
ctx->glUseProgramObjectARB(); return (ctx->glGetError() == GL_NO_ERROR);
}

从代码中可以看出,这个函数调用了GL_ShaderContext中用于初始化Shader以及Program的各个函数。有关初始化的流程不再细说,可以参考相关的文章。

在该函数中,调用了CompileShader()专门用于初始化Shader。该函数被调用了两次,分别用于初始化vertex shader和fragment shader。

CompileShader()的代码如下。

static SDL_bool CompileShader(GL_ShaderContext *ctx, GLhandleARB shader, const char *defines, const char *source)
{
GLint status;
const char *sources[]; sources[] = defines;
sources[] = source; ctx->glShaderSourceARB(shader, SDL_arraysize(sources), sources, NULL);
ctx->glCompileShaderARB(shader);
ctx->glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status);
if (status == ) {
GLint length;
char *info; ctx->glGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length);
info = SDL_stack_alloc(char, length+);
ctx->glGetInfoLogARB(shader, length, NULL, info);
SDL_LogError(SDL_LOG_CATEGORY_RENDER,
"Failed to compile shader:\n%s%s\n%s", defines, source, info);
#ifdef DEBUG_SHADERS
fprintf(stderr,
"Failed to compile shader:\n%s%s\n%s", defines, source, info);
#endif
SDL_stack_free(info); return SDL_FALSE;
} else {
return SDL_TRUE;
}
}

从代码中可以看出,该函数调用glShaderSource(),glCompileShader(),glGetObjectParameteriv()这几个函数初始化一个Shader。
Shader 的代码位于一个名称为shader_source的char型二维数组里,源代码如下所示。数组中每个元素代表一个Shader的代码,每个Shader 的代码包含两个部分:vertex shader代码(对应元素[0])以及fragment shader代码(对应元素[1])。

/*
* NOTE: Always use sampler2D, etc here. We'll #define them to the
* texture_rectangle versions if we choose to use that extension.
*/
static const char *shader_source[NUM_SHADERS][] =
{
/* SHADER_NONE */
{ NULL, NULL }, /* SHADER_SOLID */
{
/* vertex shader */
"varying vec4 v_color;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
" v_color = gl_Color;\n"
"}",
/* fragment shader */
"varying vec4 v_color;\n"
"\n"
"void main()\n"
"{\n"
" gl_FragColor = v_color;\n"
"}"
}, /* SHADER_RGB */
{
/* vertex shader */
"varying vec4 v_color;\n"
"varying vec2 v_texCoord;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
" v_color = gl_Color;\n"
" v_texCoord = vec2(gl_MultiTexCoord0);\n"
"}",
/* fragment shader */
"varying vec4 v_color;\n"
"varying vec2 v_texCoord;\n"
"uniform sampler2D tex0;\n"
"\n"
"void main()\n"
"{\n"
" gl_FragColor = texture2D(tex0, v_texCoord) * v_color;\n"
"}"
}, /* SHADER_YV12 */
{
/* vertex shader */
"varying vec4 v_color;\n"
"varying vec2 v_texCoord;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
" v_color = gl_Color;\n"
" v_texCoord = vec2(gl_MultiTexCoord0);\n"
"}",
/* fragment shader */
"varying vec4 v_color;\n"
"varying vec2 v_texCoord;\n"
"uniform sampler2D tex0; // Y \n"
"uniform sampler2D tex1; // U \n"
"uniform sampler2D tex2; // V \n"
"\n"
"// YUV offset \n"
"const vec3 offset = vec3(-0.0625, -0.5, -0.5);\n"
"\n"
"// RGB coefficients \n"
"const vec3 Rcoeff = vec3(1.164, 0.000, 1.596);\n"
"const vec3 Gcoeff = vec3(1.164, -0.391, -0.813);\n"
"const vec3 Bcoeff = vec3(1.164, 2.018, 0.000);\n"
"\n"
"void main()\n"
"{\n"
" vec2 tcoord;\n"
" vec3 yuv, rgb;\n"
"\n"
" // Get the Y value \n"
" tcoord = v_texCoord;\n"
" yuv.x = texture2D(tex0, tcoord).r;\n"
"\n"
" // Get the U and V values \n"
" tcoord *= 0.5;\n"
" yuv.y = texture2D(tex1, tcoord).r;\n"
" yuv.z = texture2D(tex2, tcoord).r;\n"
"\n"
" // Do the color transform \n"
" yuv += offset;\n"
" rgb.r = dot(yuv, Rcoeff);\n"
" rgb.g = dot(yuv, Gcoeff);\n"
" rgb.b = dot(yuv, Bcoeff);\n"
"\n"
" // That was easy. :) \n"
" gl_FragColor = vec4(rgb, 1.0) * v_color;\n"
"}"
},
};

有关OpenGL的渲染器的初始化代码暂时分析到这里。

3. Software

Software的渲染器在创建函数是SW_CreateRenderer()。该函数位于render\software\SDL_render_sw.c文件中。首先看一下它的代码。

SDL_Renderer * SW_CreateRenderer(SDL_Window * window, Uint32 flags)
{
SDL_Surface *surface; surface = SDL_GetWindowSurface(window);
if (!surface) {
return NULL;
}
return SW_CreateRendererForSurface(surface);
}

从代码中可以看出,SW_CreateRenderer()调用了2个函数:SDL_GetWindowSurface()和 SW_CreateRendererForSurface()。SDL_GetWindowSurface()用于创建一个 Surface;SW_CreateRendererForSurface()基于Surface创建一个Renderer。
下面分别看一下这2个函数的代码。

SDL_GetWindowSurface()的代码如下所示(位于video\SDL_video.c)。

SDL_Surface * SDL_GetWindowSurface(SDL_Window * window)
{
CHECK_WINDOW_MAGIC(window, NULL); if (!window->surface_valid) {
if (window->surface) {
window->surface->flags &= ~SDL_DONTFREE;
SDL_FreeSurface(window->surface);
}
window->surface = SDL_CreateWindowFramebuffer(window);
if (window->surface) {
window->surface_valid = SDL_TRUE;
window->surface->flags |= SDL_DONTFREE;
}
}
return window->surface;
}

其中调用了一个函数SDL_CreateWindowFramebuffer(),看一下该函数的代码。

static SDL_Surface * SDL_CreateWindowFramebuffer(SDL_Window * window)
{
Uint32 format;
void *pixels;
int pitch;
int bpp;
Uint32 Rmask, Gmask, Bmask, Amask; if (!_this->CreateWindowFramebuffer || !_this->UpdateWindowFramebuffer) {
return NULL;
} if (_this->CreateWindowFramebuffer(_this, window, &format, &pixels, &pitch) < ) {
return NULL;
} if (!SDL_PixelFormatEnumToMasks(format, &bpp, &Rmask, &Gmask, &Bmask, &Amask)) {
return NULL;
} return SDL_CreateRGBSurfaceFrom(pixels, window->w, window->h, bpp, pitch, Rmask, Gmask, Bmask, Amask);
}

该函数中调用了SDL_VideoDevice中的一个函数CreateWindowFramebuffer()。我们以“Windows视频驱动”为例,看看CreateWindowFramebuffer()中的代码。在“Windows视频驱动” 下,CreateWindowFramebuffer()对应的函数是WIN_CreateWindowFramebuffer()。下面看一下该函数的代码。

int WIN_CreateWindowFramebuffer(_THIS, SDL_Window * window, Uint32 * format, void ** pixels, int *pitch)
{
SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
size_t size;
LPBITMAPINFO info;
HBITMAP hbm; /* Free the old framebuffer surface */
if (data->mdc) {
DeleteDC(data->mdc);
}
if (data->hbm) {
DeleteObject(data->hbm);
} /* Find out the format of the screen */
size = sizeof(BITMAPINFOHEADER) + * sizeof (RGBQUAD);
info = (LPBITMAPINFO)SDL_stack_alloc(Uint8, size); SDL_memset(info, , size);
info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); /* The second call to GetDIBits() fills in the bitfields */
hbm = CreateCompatibleBitmap(data->hdc, , );
GetDIBits(data->hdc, hbm, , , NULL, info, DIB_RGB_COLORS);
GetDIBits(data->hdc, hbm, , , NULL, info, DIB_RGB_COLORS);
DeleteObject(hbm); *format = SDL_PIXELFORMAT_UNKNOWN;
if (info->bmiHeader.biCompression == BI_BITFIELDS) {
int bpp;
Uint32 *masks; bpp = info->bmiHeader.biPlanes * info->bmiHeader.biBitCount;
masks = (Uint32*)((Uint8*)info + info->bmiHeader.biSize);
*format = SDL_MasksToPixelFormatEnum(bpp, masks[], masks[], masks[], );
}
if (*format == SDL_PIXELFORMAT_UNKNOWN)
{
/* We'll use RGB format for now */
*format = SDL_PIXELFORMAT_RGB888; /* Create a new one */
SDL_memset(info, , size);
info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info->bmiHeader.biPlanes = ;
info->bmiHeader.biBitCount = ;
info->bmiHeader.biCompression = BI_RGB;
} /* Fill in the size information */
*pitch = (((window->w * SDL_BYTESPERPIXEL(*format)) + ) & ~);
info->bmiHeader.biWidth = window->w;
info->bmiHeader.biHeight = -window->h; /* negative for topdown bitmap */
info->bmiHeader.biSizeImage = window->h * (*pitch); data->mdc = CreateCompatibleDC(data->hdc);
data->hbm = CreateDIBSection(data->hdc, info, DIB_RGB_COLORS, pixels, NULL, );
SDL_stack_free(info); if (!data->hbm) {
return WIN_SetError("Unable to create DIB");
}
SelectObject(data->mdc, data->hbm); return ;
}

从代码中可以看出,该函数调用了Win32的API函数CreateCompatibleBitmap(),CreateCompatibleDC()等一系列方法创建了“Surface”。
SDL_GetWindowSurface()函数到此分析完毕,现在回过头来再看SW_CreateRenderer ()的另一个函数SW_CreateRendererForSurface()。该函数的代码如下。

SDL_Renderer * SW_CreateRendererForSurface(SDL_Surface * surface)
{
SDL_Renderer *renderer;
SW_RenderData *data; if (!surface) {
SDL_SetError("Can't create renderer for NULL surface");
return NULL;
} renderer = (SDL_Renderer *) SDL_calloc(, sizeof(*renderer));
if (!renderer) {
SDL_OutOfMemory();
return NULL;
} data = (SW_RenderData *) SDL_calloc(, sizeof(*data));
if (!data) {
SW_DestroyRenderer(renderer);
SDL_OutOfMemory();
return NULL;
}
data->surface = surface; renderer->WindowEvent = SW_WindowEvent;
renderer->GetOutputSize = SW_GetOutputSize;
renderer->CreateTexture = SW_CreateTexture;
renderer->SetTextureColorMod = SW_SetTextureColorMod;
renderer->SetTextureAlphaMod = SW_SetTextureAlphaMod;
renderer->SetTextureBlendMode = SW_SetTextureBlendMode;
renderer->UpdateTexture = SW_UpdateTexture;
renderer->LockTexture = SW_LockTexture;
renderer->UnlockTexture = SW_UnlockTexture;
renderer->SetRenderTarget = SW_SetRenderTarget;
renderer->UpdateViewport = SW_UpdateViewport;
renderer->UpdateClipRect = SW_UpdateClipRect;
renderer->RenderClear = SW_RenderClear;
renderer->RenderDrawPoints = SW_RenderDrawPoints;
renderer->RenderDrawLines = SW_RenderDrawLines;
renderer->RenderFillRects = SW_RenderFillRects;
renderer->RenderCopy = SW_RenderCopy;
renderer->RenderCopyEx = SW_RenderCopyEx;
renderer->RenderReadPixels = SW_RenderReadPixels;
renderer->RenderPresent = SW_RenderPresent;
renderer->DestroyTexture = SW_DestroyTexture;
renderer->DestroyRenderer = SW_DestroyRenderer;
renderer->info = SW_RenderDriver.info;
renderer->driverdata = data; SW_ActivateRenderer(renderer); return renderer;
}

与前面的函数一样,该函数完成了SDL_Renderer结构体中函数指针的赋值。

4:纹理(SDL_Texture)

上篇文章分析了该流程中的第3个函数SDL_CreateRenderer()。本文继续分析该流程中的第4个函数SDL_CreateTexture()。

SDL_Texture

SDL_Texture结构定义了一个SDL中的纹理。如果直接使用SDL2编译好的SDK的话,是看不到SDL_Texture的内部结构的。有关它的定义在头文件中只有一行代码,如下所示。

/**
* \brief An efficient driver-specific representation of pixel data
*/
struct SDL_Texture;
typedef struct SDL_Texture SDL_Texture;

在源代码工程中可以看到SDL_Texture的定义,位于render\SDL_sysrender.h文件中。它的定义如下。

/* Define the SDL texture structure */
struct SDL_Texture
{
const void *magic;
Uint32 format; /**< The pixel format of the texture */
int access; /**< SDL_TextureAccess */
int w; /**< The width of the texture */
int h; /**< The height of the texture */
int modMode; /**< The texture modulation mode */
SDL_BlendMode blendMode; /**< The texture blend mode */
Uint8 r, g, b, a; /**< Texture modulation values */ SDL_Renderer *renderer; /* Support for formats not supported directly by the renderer */
SDL_Texture *native;
SDL_SW_YUVTexture *yuv;
void *pixels;
int pitch;
SDL_Rect locked_rect; void *driverdata; /**< Driver specific texture representation */ SDL_Texture *prev;
SDL_Texture *next;
};

可以看出其中包含了一个“纹理”所具备的各种属性。下面来看看如何创建这个SDL_Texture。

SDL_CreateTexture()

函数简介

使用SDL_CreateTexture()基于渲染器创建一个纹理。SDL_CreateTexture()的原型如下。

SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer,
Uint32 format,
int access, int w,
int h);

参数的含义如下。

renderer:目标渲染器。

format :纹理的格式。后面会详述。

access :可以取以下值(定义位于SDL_TextureAccess中)

SDL_TEXTUREACCESS_STATIC :变化极少
    SDL_TEXTUREACCESS_STREAMING :变化频繁

SDL_TEXTUREACCESS_TARGET :暂时没有理解

w :纹理的宽

h :纹理的高

创建成功则返回纹理的ID,失败返回0。

函数调用关系图

SDL_ CreateTexture ()关键函数的调用关系可以用下图表示。

源代码分析

SDL_CreateTexture()的源代码位于render\SDL_render.c中。如下所示。

SDL_Texture * SDL_CreateTexture(SDL_Renderer * renderer, Uint32 format, int access, int w, int h)
{
SDL_Texture *texture; CHECK_RENDERER_MAGIC(renderer, NULL); if (!format) {
format = renderer->info.texture_formats[];
}
if (SDL_ISPIXELFORMAT_INDEXED(format)) {
SDL_SetError("Palettized textures are not supported");
return NULL;
}
if (w <= || h <= ) {
SDL_SetError("Texture dimensions can't be 0");
return NULL;
}
if ((renderer->info.max_texture_width && w > renderer->info.max_texture_width) ||
(renderer->info.max_texture_height && h > renderer->info.max_texture_height)) {
SDL_SetError("Texture dimensions are limited to %dx%d", renderer->info.max_texture_width, renderer->info.max_texture_height);
return NULL;
}
texture = (SDL_Texture *) SDL_calloc(, sizeof(*texture));
if (!texture) {
SDL_OutOfMemory();
return NULL;
}
texture->magic = &texture_magic;
texture->format = format;
texture->access = access;
texture->w = w;
texture->h = h;
texture->r = ;
texture->g = ;
texture->b = ;
texture->a = ;
texture->renderer = renderer;
texture->next = renderer->textures;
if (renderer->textures) {
renderer->textures->prev = texture;
}
renderer->textures = texture; if (IsSupportedFormat(renderer, format)) {
if (renderer->CreateTexture(renderer, texture) < ) {
SDL_DestroyTexture(texture);
return ;
}
} else {
texture->native = SDL_CreateTexture(renderer,
GetClosestSupportedFormat(renderer, format),
access, w, h);
if (!texture->native) {
SDL_DestroyTexture(texture);
return NULL;
} /* Swap textures to have texture before texture->native in the list */
texture->native->next = texture->next;
if (texture->native->next) {
texture->native->next->prev = texture->native;
}
texture->prev = texture->native->prev;
if (texture->prev) {
texture->prev->next = texture;
}
texture->native->prev = texture;
texture->next = texture->native;
renderer->textures = texture; if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) {
texture->yuv = SDL_SW_CreateYUVTexture(format, w, h);
if (!texture->yuv) {
SDL_DestroyTexture(texture);
return NULL;
}
} else if (access == SDL_TEXTUREACCESS_STREAMING) {
/* The pitch is 4 byte aligned */
texture->pitch = (((w * SDL_BYTESPERPIXEL(format)) + ) & ~);
texture->pixels = SDL_calloc(, texture->pitch * h);
if (!texture->pixels) {
SDL_DestroyTexture(texture);
return NULL;
}
}
}
return texture;
}

从源代码中可以看出,SDL_CreateTexture()的大致流程如下。

1. 检查输入参数的合理性。例如像素格式是否支持,宽和高是否小于等于0等等。

2. 新建一个SDL_Texture。调用SDL_calloc()(实际上就是calloc())为新建的SDL_Texture分配内存。

3. 调用SDL_Render的CreateTexture()方法创建纹理。这一步是整个函数的核心。

下面我们详细看一下几种不同的渲染器的CreateTexture()的方法。

1. Direct3D

Direct3D 渲染器中对应CreateTexture()的函数是D3D_CreateTexture(),它的源代码如下所示(位于render\direct3d\SDL_render_d3d.c)。

static int D3D_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
{
D3D_RenderData *renderdata = (D3D_RenderData *) renderer->driverdata;
D3D_TextureData *data;
D3DPOOL pool;
DWORD usage;
HRESULT result; data = (D3D_TextureData *) SDL_calloc(, sizeof(*data));
if (!data) {
return SDL_OutOfMemory();
}
data->scaleMode = GetScaleQuality(); texture->driverdata = data; #ifdef USE_DYNAMIC_TEXTURE
if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
pool = D3DPOOL_DEFAULT;
usage = D3DUSAGE_DYNAMIC;
} else
#endif
if (texture->access == SDL_TEXTUREACCESS_TARGET) {
/* D3DPOOL_MANAGED does not work with D3DUSAGE_RENDERTARGET */
pool = D3DPOOL_DEFAULT;
usage = D3DUSAGE_RENDERTARGET;
} else {
pool = D3DPOOL_MANAGED;
usage = ;
} result =
IDirect3DDevice9_CreateTexture(renderdata->device, texture->w,
texture->h, , usage,
PixelFormatToD3DFMT(texture->format),
pool, &data->texture, NULL);
if (FAILED(result)) {
return D3D_SetError("CreateTexture()", result);
} if (texture->format == SDL_PIXELFORMAT_YV12 ||
texture->format == SDL_PIXELFORMAT_IYUV) {
data->yuv = SDL_TRUE; result =
IDirect3DDevice9_CreateTexture(renderdata->device, texture->w / ,
texture->h / , , usage,
PixelFormatToD3DFMT(texture->format),
pool, &data->utexture, NULL);
if (FAILED(result)) {
return D3D_SetError("CreateTexture()", result);
} result =
IDirect3DDevice9_CreateTexture(renderdata->device, texture->w / ,
texture->h / , , usage,
PixelFormatToD3DFMT(texture->format),
pool, &data->vtexture, NULL);
if (FAILED(result)) {
return D3D_SetError("CreateTexture()", result);
}
} return ;
}

从代码中可以看出,该函数调用了Direct3D的API函数IDirect3DDevice9_CreateTexture()创建了一个纹理。

2. OpenGL

OpenGL渲染器中对应CreateTexture()的函数是GL_CreateTexture (),它的源代码如下所示(位于render\opengl\SDL_render_gl.c)。

static int GL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
{
GL_RenderData *renderdata = (GL_RenderData *) renderer->driverdata;
GL_TextureData *data;
GLint internalFormat;
GLenum format, type;
int texture_w, texture_h;
GLenum scaleMode; GL_ActivateRenderer(renderer); if (!convert_format(renderdata, texture->format, &internalFormat,
&format, &type)) {
return SDL_SetError("Texture format %s not supported by OpenGL",
SDL_GetPixelFormatName(texture->format));
} data = (GL_TextureData *) SDL_calloc(, sizeof(*data));
if (!data) {
return SDL_OutOfMemory();
} if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
size_t size;
data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
size = texture->h * data->pitch;
if (texture->format == SDL_PIXELFORMAT_YV12 ||
texture->format == SDL_PIXELFORMAT_IYUV) {
/* Need to add size for the U and V planes */
size += ( * (texture->h * data->pitch) / );
}
data->pixels = SDL_calloc(, size);
if (!data->pixels) {
SDL_free(data);
return SDL_OutOfMemory();
}
} if (texture->access == SDL_TEXTUREACCESS_TARGET) {
data->fbo = GL_GetFBO(renderdata, texture->w, texture->h);
} else {
data->fbo = NULL;
} GL_CheckError("", renderer);
renderdata->glGenTextures(, &data->texture);
if (GL_CheckError("glGenTexures()", renderer) < ) {
SDL_free(data);
return -;
}
texture->driverdata = data; if ((renderdata->GL_ARB_texture_rectangle_supported)
/* && texture->access != SDL_TEXTUREACCESS_TARGET */){
data->type = GL_TEXTURE_RECTANGLE_ARB;
texture_w = texture->w;
texture_h = texture->h;
data->texw = (GLfloat) texture_w;
data->texh = (GLfloat) texture_h;
} else {
data->type = GL_TEXTURE_2D;
texture_w = power_of_2(texture->w);
texture_h = power_of_2(texture->h);
data->texw = (GLfloat) (texture->w) / texture_w;
data->texh = (GLfloat) texture->h / texture_h;
} data->format = format;
data->formattype = type;
scaleMode = GetScaleQuality();
renderdata->glEnable(data->type);
renderdata->glBindTexture(data->type, data->texture);
renderdata->glTexParameteri(data->type, GL_TEXTURE_MIN_FILTER, scaleMode);
renderdata->glTexParameteri(data->type, GL_TEXTURE_MAG_FILTER, scaleMode);
/* According to the spec, CLAMP_TO_EDGE is the default for TEXTURE_RECTANGLE
and setting it causes an INVALID_ENUM error in the latest NVidia drivers.
*/
if (data->type != GL_TEXTURE_RECTANGLE_ARB) {
renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
}
#ifdef __MACOSX__
#ifndef GL_TEXTURE_STORAGE_HINT_APPLE
#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC
#endif
#ifndef STORAGE_CACHED_APPLE
#define STORAGE_CACHED_APPLE 0x85BE
#endif
#ifndef STORAGE_SHARED_APPLE
#define STORAGE_SHARED_APPLE 0x85BF
#endif
if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
renderdata->glTexParameteri(data->type, GL_TEXTURE_STORAGE_HINT_APPLE,
GL_STORAGE_SHARED_APPLE);
} else {
renderdata->glTexParameteri(data->type, GL_TEXTURE_STORAGE_HINT_APPLE,
GL_STORAGE_CACHED_APPLE);
}
if (texture->access == SDL_TEXTUREACCESS_STREAMING
&& texture->format == SDL_PIXELFORMAT_ARGB8888
&& (texture->w % ) == ) {
renderdata->glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
renderdata->glPixelStorei(GL_UNPACK_ALIGNMENT, );
renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH,
(data->pitch / SDL_BYTESPERPIXEL(texture->format)));
renderdata->glTexImage2D(data->type, , internalFormat, texture_w,
texture_h, , format, type, data->pixels);
renderdata->glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE);
}
else
#endif
{
renderdata->glTexImage2D(data->type, , internalFormat, texture_w,
texture_h, , format, type, NULL);
}
renderdata->glDisable(data->type);
if (GL_CheckError("glTexImage2D()", renderer) < ) {
return -;
} if (texture->format == SDL_PIXELFORMAT_YV12 ||
texture->format == SDL_PIXELFORMAT_IYUV) {
data->yuv = SDL_TRUE; renderdata->glGenTextures(, &data->utexture);
renderdata->glGenTextures(, &data->vtexture);
renderdata->glEnable(data->type); renderdata->glBindTexture(data->type, data->utexture);
renderdata->glTexParameteri(data->type, GL_TEXTURE_MIN_FILTER,
scaleMode);
renderdata->glTexParameteri(data->type, GL_TEXTURE_MAG_FILTER,
scaleMode);
renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
renderdata->glTexImage2D(data->type, , internalFormat, texture_w/,
texture_h/, , format, type, NULL); renderdata->glBindTexture(data->type, data->vtexture);
renderdata->glTexParameteri(data->type, GL_TEXTURE_MIN_FILTER,
scaleMode);
renderdata->glTexParameteri(data->type, GL_TEXTURE_MAG_FILTER,
scaleMode);
renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
renderdata->glTexImage2D(data->type, , internalFormat, texture_w/,
texture_h/, , format, type, NULL); renderdata->glDisable(data->type);
} return GL_CheckError("", renderer);
}

从代码中可以看出,该函数调用了OpenGL的API函数glGenTextures(),glBindTexture()创建了一个纹理。并且使用glTexParameteri()设置了有关的一些参数。

在这里有一点需要注意,在OpenGL渲染器中,如果输入像素格式是YV12或者IYUV,就会使用3个纹理。

3. Software

Software渲染器中对应CreateTexture()的函数是SW_CreateTexture (),它的源代码如下所示(位于render\software\SDL_render_sw.c)。

static int SW_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
{
int bpp;
Uint32 Rmask, Gmask, Bmask, Amask; if (!SDL_PixelFormatEnumToMasks
(texture->format, &bpp, &Rmask, &Gmask, &Bmask, &Amask)) {
return SDL_SetError("Unknown texture format");
} texture->driverdata =
SDL_CreateRGBSurface(, texture->w, texture->h, bpp, Rmask, Gmask,
Bmask, Amask);
SDL_SetSurfaceColorMod(texture->driverdata, texture->r, texture->g,
texture->b);
SDL_SetSurfaceAlphaMod(texture->driverdata, texture->a);
SDL_SetSurfaceBlendMode(texture->driverdata, texture->blendMode); if (texture->access == SDL_TEXTUREACCESS_STATIC) {
SDL_SetSurfaceRLE(texture->driverdata, );
} if (!texture->driverdata) {
return -;
}
return ;
}

该函数的源代码还没有详细分析。可以看出其中调用了SDL_CreateRGBSurface()创建了“Surface”。

5:更新纹理(SDL_UpdateTexture())

上篇文章分析了该流程中的第4个函数SDL_CreateTexture()。本文继续分析该流程中的第5个函数SDL_UpdateTexture()。

SDL_UpdateTexture()

函数简介

SDL使用SDL_UpdateTexture()设置纹理的像素数据。SDL_UpdateTexture()的原型如下。

int SDLCALL SDL_UpdateTexture(SDL_Texture * texture,
const SDL_Rect * rect,
const void *pixels, int pitch);

参数的含义如下。

texture:目标纹理。

rect:更新像素的矩形区域。设置为NULL的时候更新整个区域。

pixels:像素数据。

pitch:一行像素数据的字节数。

成功的话返回0,失败的话返回-1。

函数调用关系图

SDL_UpdateTexture()关键函数的调用关系可以用下图表示。

源代码分析

SDL_UpdateTexture()的源代码位于render\SDL_render.c中。如下所示。

int SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect,
const void *pixels, int pitch)
{
SDL_Renderer *renderer;
SDL_Rect full_rect; CHECK_TEXTURE_MAGIC(texture, -); if (!pixels) {
return SDL_InvalidParamError("pixels");
}
if (!pitch) {
return SDL_InvalidParamError("pitch");
} if (!rect) {
full_rect.x = ;
full_rect.y = ;
full_rect.w = texture->w;
full_rect.h = texture->h;
rect = &full_rect;
} if (texture->yuv) {
return SDL_UpdateTextureYUV(texture, rect, pixels, pitch);
} else if (texture->native) {
return SDL_UpdateTextureNative(texture, rect, pixels, pitch);
} else {
renderer = texture->renderer;
return renderer->UpdateTexture(renderer, texture, rect, pixels, pitch);
}
}

从源代码中可以看出,SDL_UpdateTexture()的大致流程如下。
1. 检查输入参数的合理性。例如像素格式是否支持,宽和高是否小于等于0等等。

2. 如果是一些特殊的格式,进行一定的处理:

a) 如果输入的像素数据是YUV格式的,则会调用SDL_UpdateTextureYUV()进行处理。

b) 如果输入的像素数据的像素格式不是渲染器支持的格式,则会调用SDL_UpdateTextureNative()进行处理。

3. 调用SDL_Render的UpdateTexture()方法更新纹理。这一步是整个函数的核心。

下面我们详细看一下几种不同的渲染器的UpdateTexture ()的方法。

1. Direct3D

Direct3D 渲染器中对应UpdateTexture ()的函数是D3D_UpdateTexture(),它的源代码如下所示(位于render\direct3d\SDL_render_d3d.c)。

static int
D3D_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, const void *pixels, int pitch)
{
D3D_TextureData *data = (D3D_TextureData *) texture->driverdata;
SDL_bool full_texture = SDL_FALSE; #ifdef USE_DYNAMIC_TEXTURE
if (texture->access == SDL_TEXTUREACCESS_STREAMING &&
rect->x == && rect->y == &&
rect->w == texture->w && rect->h == texture->h) {
full_texture = SDL_TRUE;
}
#endif if (!data) {
SDL_SetError("Texture is not currently available");
return -;
} if (D3D_UpdateTextureInternal(data->texture, texture->format, full_texture, rect->x, rect->y, rect->w, rect->h, pixels, pitch) < ) {
return -;
} if (data->yuv) {
/* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch); if (D3D_UpdateTextureInternal(texture->format == SDL_PIXELFORMAT_YV12 ? data->vtexture : data->utexture, texture->format, full_texture, rect->x / , rect->y / , rect->w / , rect->h / , pixels, pitch / ) < ) {
return -;
} /* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + (rect->h * pitch)/);
if (D3D_UpdateTextureInternal(texture->format == SDL_PIXELFORMAT_YV12 ? data->utexture : data->vtexture, texture->format, full_texture, rect->x / , rect->y / , rect->w / , rect->h / , pixels, pitch / ) < ) {
return -;
}
}
return ;
}

从代码中可以看出,该函数调用了D3D_UpdateTextureInternal()函数。在这里需要注意,如果输入像素格式是YUV,就会使用3个纹理,对于多出的那2个纹理会单独进行处理。调用的函数D3D_UpdateTextureInternal()代码如下。

static int D3D_UpdateTextureInternal(IDirect3DTexture9 *texture, Uint32 format, SDL_bool full_texture, int x, int y, int w, int h, const void *pixels, int pitch)
{
RECT d3drect;
D3DLOCKED_RECT locked;
const Uint8 *src;
Uint8 *dst;
int row, length;
HRESULT result; if (full_texture) {
result = IDirect3DTexture9_LockRect(texture, , &locked, NULL, D3DLOCK_DISCARD);
} else {
d3drect.left = x;
d3drect.right = x + w;
d3drect.top = y;
d3drect.bottom = y + h;
result = IDirect3DTexture9_LockRect(texture, , &locked, &d3drect, );
} if (FAILED(result)) {
return D3D_SetError("LockRect()", result);
} src = (const Uint8 *)pixels;
dst = locked.pBits;
length = w * SDL_BYTESPERPIXEL(format);
if (length == pitch && length == locked.Pitch) {
SDL_memcpy(dst, src, length*h);
} else {
if (length > pitch) {
length = pitch;
}
if (length > locked.Pitch) {
length = locked.Pitch;
}
for (row = ; row < h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += locked.Pitch;
}
}
IDirect3DTexture9_UnlockRect(texture, ); return ;
}

从代码中可以看出,该函数首先调用IDirect3DTexture9_LockRect()锁定纹理,然后使用SDL_memcpy()将新的像素数据拷贝至纹理(SDL_memcpy()实际上就是memcpy()), 最后使用IDirect3DTexture9_UnlockRect()解锁纹理。

2. OpenGL

OpenGL渲染器中对应UpdateTexture()的函数是GL_UpdateTexture(),它的源代码如下所示(位于render\opengl\SDL_render_gl.c)。

static int GL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, const void *pixels, int pitch)
{
GL_RenderData *renderdata = (GL_RenderData *) renderer->driverdata;
GL_TextureData *data = (GL_TextureData *) texture->driverdata; GL_ActivateRenderer(renderer); renderdata->glEnable(data->type);
renderdata->glBindTexture(data->type, data->texture);
renderdata->glPixelStorei(GL_UNPACK_ALIGNMENT, );
renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH,
(pitch / SDL_BYTESPERPIXEL(texture->format)));
renderdata->glTexSubImage2D(data->type, , rect->x, rect->y, rect->w,
rect->h, data->format, data->formattype,
pixels);
if (data->yuv) {
renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH, (pitch / )); /* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
if (texture->format == SDL_PIXELFORMAT_YV12) {
renderdata->glBindTexture(data->type, data->vtexture);
} else {
renderdata->glBindTexture(data->type, data->utexture);
}
renderdata->glTexSubImage2D(data->type, , rect->x/, rect->y/,
rect->w/, rect->h/,
data->format, data->formattype, pixels); /* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + (rect->h * pitch)/);
if (texture->format == SDL_PIXELFORMAT_YV12) {
renderdata->glBindTexture(data->type, data->utexture);
} else {
renderdata->glBindTexture(data->type, data->vtexture);
}
renderdata->glTexSubImage2D(data->type, , rect->x/, rect->y/,
rect->w/, rect->h/,
data->format, data->formattype, pixels);
}
renderdata->glDisable(data->type); return GL_CheckError("glTexSubImage2D()", renderer);
}

从代码中可以看出,该函数调用了OpenGL的API函数glBindTexture (),glTexSubImage2D()等更新了一个纹理。
在这里有一点需要注意,如果输入像素格式是YUV,就会使用3个纹理,对于多出的那2个纹理会单独进行处理。

3. Software

Software渲染器中对应UpdateTexture()的函数是SW_UpdateTexture(),它的源代码如下所示(位于render\software\SDL_render_sw.c)。

static int SW_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * rect, const void *pixels, int pitch)
{
SDL_Surface *surface = (SDL_Surface *) texture->driverdata;
Uint8 *src, *dst;
int row;
size_t length; if(SDL_MUSTLOCK(surface))
SDL_LockSurface(surface);
src = (Uint8 *) pixels;
dst = (Uint8 *) surface->pixels +
rect->y * surface->pitch +
rect->x * surface->format->BytesPerPixel;
length = rect->w * surface->format->BytesPerPixel;
for (row = ; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += surface->pitch;
}
if(SDL_MUSTLOCK(surface))
SDL_UnlockSurface(surface);
return ;
}

该函数的源代码还没有详细分析。其中最关键的函数要数SDL_memcpy()了,正是这个函数更新了纹理的像素数据。但是Software渲染器纹理修改的时候是否需要Lock()和Unlock()呢?这一点一直也没太搞清。

6:复制到渲染器(SDL_RenderCopy())

上篇文章分析了该流程中的第5个函数SDL_UpdateTexture()。本文继续分析该流程中的第6个函数SDL_RenderCopy()。

SDL_RenderCopy()

函数简介

SDL使用SDL_RenderCopy()将纹理数据复制给渲染目标。SDL_RenderCopy()的原型如下。

[cpp] view plaincopy

  1. int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer,
  2. SDL_Texture * texture,
  3. const SDL_Rect * srcrect,
  4. const SDL_Rect * dstrect);

参数的含义如下。
renderer:渲染目标。

texture:输入纹理。

srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。

dstrect:选择渲染目标的一块矩形区域作为输出。设置为NULL的时候整个渲染目标作为输出。

成功的话返回0,失败的话返回-1。

函数调用关系图

SDL_RenderCopy()关键函数的调用关系可以用下图表示。

源代码分析

SDL_RenderCopy()的源代码位于render\SDL_render.c中,如下所示。

int SDL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_Rect * dstrect)
{
SDL_Rect real_srcrect = { , , , };
SDL_Rect real_dstrect = { , , , };
SDL_FRect frect; CHECK_RENDERER_MAGIC(renderer, -);
CHECK_TEXTURE_MAGIC(texture, -); if (renderer != texture->renderer) {
return SDL_SetError("Texture was not created with this renderer");
} real_srcrect.x = ;
real_srcrect.y = ;
real_srcrect.w = texture->w;
real_srcrect.h = texture->h;
if (srcrect) {
if (!SDL_IntersectRect(srcrect, &real_srcrect, &real_srcrect)) {
return ;
}
} SDL_RenderGetViewport(renderer, &real_dstrect);
real_dstrect.x = ;
real_dstrect.y = ;
if (dstrect) {
if (!SDL_HasIntersection(dstrect, &real_dstrect)) {
return ;
}
real_dstrect = *dstrect;
} if (texture->native) {
texture = texture->native;
} /* Don't draw while we're hidden */
if (renderer->hidden) {
return ;
} frect.x = real_dstrect.x * renderer->scale.x;
frect.y = real_dstrect.y * renderer->scale.y;
frect.w = real_dstrect.w * renderer->scale.x;
frect.h = real_dstrect.h * renderer->scale.y; return renderer->RenderCopy(renderer, texture, &real_srcrect, &frect);
}

从源代码中可以看出,SDL_RenderCopy()的大致流程如下。

1. 检查输入参数的合理性。
2. 调用SDL_Render的RenderCopy ()方法复制纹理到渲染目标。
这一步是整个函数的核心。

下面我们详细看一下几种不同的渲染器的RenderCopy()的方法。

1. Direct3D

Direct3D 渲染器中对应RenderCopy()的函数是D3D_RenderCopy(),它的源代码如下所示(位于render\direct3d\SDL_render_d3d.c)。

static int D3D_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_FRect * dstrect)
{
D3D_RenderData *data = (D3D_RenderData *) renderer->driverdata;
D3D_TextureData *texturedata;
LPDIRECT3DPIXELSHADER9 shader = NULL;
float minx, miny, maxx, maxy;
float minu, maxu, minv, maxv;
DWORD color;
Vertex vertices[];
HRESULT result; if (D3D_ActivateRenderer(renderer) < ) {
return -;
} texturedata = (D3D_TextureData *)texture->driverdata;
if (!texturedata) {
SDL_SetError("Texture is not currently available");
return -;
} minx = dstrect->x - 0.5f;
miny = dstrect->y - 0.5f;
maxx = dstrect->x + dstrect->w - 0.5f;
maxy = dstrect->y + dstrect->h - 0.5f; minu = (float) srcrect->x / texture->w;
maxu = (float) (srcrect->x + srcrect->w) / texture->w;
minv = (float) srcrect->y / texture->h;
maxv = (float) (srcrect->y + srcrect->h) / texture->h; color = D3DCOLOR_ARGB(texture->a, texture->r, texture->g, texture->b); vertices[].x = minx;
vertices[].y = miny;
vertices[].z = 0.0f;
vertices[].color = color;
vertices[].u = minu;
vertices[].v = minv; vertices[].x = maxx;
vertices[].y = miny;
vertices[].z = 0.0f;
vertices[].color = color;
vertices[].u = maxu;
vertices[].v = minv; vertices[].x = maxx;
vertices[].y = maxy;
vertices[].z = 0.0f;
vertices[].color = color;
vertices[].u = maxu;
vertices[].v = maxv; vertices[].x = minx;
vertices[].y = maxy;
vertices[].z = 0.0f;
vertices[].color = color;
vertices[].u = minu;
vertices[].v = maxv; D3D_SetBlendMode(data, texture->blendMode); D3D_UpdateTextureScaleMode(data, texturedata, ); result =
IDirect3DDevice9_SetTexture(data->device, , (IDirect3DBaseTexture9 *)
texturedata->texture);
if (FAILED(result)) {
return D3D_SetError("SetTexture()", result);
} if (texturedata->yuv) {
shader = data->ps_yuv; D3D_UpdateTextureScaleMode(data, texturedata, );
D3D_UpdateTextureScaleMode(data, texturedata, ); result =
IDirect3DDevice9_SetTexture(data->device, , (IDirect3DBaseTexture9 *)
texturedata->utexture);
if (FAILED(result)) {
return D3D_SetError("SetTexture()", result);
} result =
IDirect3DDevice9_SetTexture(data->device, , (IDirect3DBaseTexture9 *)
texturedata->vtexture);
if (FAILED(result)) {
return D3D_SetError("SetTexture()", result);
}
} if (shader) {
result = IDirect3DDevice9_SetPixelShader(data->device, shader);
if (FAILED(result)) {
return D3D_SetError("SetShader()", result);
}
}
result =
IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLEFAN, ,
vertices, sizeof(*vertices));
if (FAILED(result)) {
return D3D_SetError("DrawPrimitiveUP()", result);
}
if (shader) {
result = IDirect3DDevice9_SetPixelShader(data->device, NULL);
if (FAILED(result)) {
return D3D_SetError("SetShader()", result);
}
}
return ;
}

从代码中可以看出,D3D_RenderCopy()函数按照执行的顺序调用了如下函数:

D3D_ActivateRenderer():激活渲染器。其内部使用Direct3D的API函数IDirect3DDevice9_BeginScene()开始一个D3D的场景。
D3D_SetBlendMode():设置渲染器状态。其内部使用Direct3D的API函数IDirect3DDevice9_SetRenderState()设置渲染器的状态。

D3D_UpdateTextureScaleMode():设置纹理采样方式。其内部调用使用Direct3D的API函数IDirect3DDevice9_SetSamplerState()设置D3D的纹理采样方式。

IDirect3DDevice9_SetTexture():Direct3D的API,用于设置当前启用的纹理。

IDirect3DDevice9_SetPixelShader():Direct3D的API,用于设置使用的像素着色器。

IDirect3DDevice9_DrawPrimitiveUP():Direct3D的API,用于渲染。

上述几个函数中,前3个函数是SDL中的函数,后3个函数是Direct3D的API。在此附上前三个函数的代码。

D3D_ActivateRenderer():激活渲染器。

static int D3D_ActivateRenderer(SDL_Renderer * renderer)
{
D3D_RenderData *data = (D3D_RenderData *) renderer->driverdata;
HRESULT result; if (data->updateSize) {
SDL_Window *window = renderer->window;
int w, h; SDL_GetWindowSize(window, &w, &h);
data->pparams.BackBufferWidth = w;
data->pparams.BackBufferHeight = h;
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
data->pparams.BackBufferFormat =
PixelFormatToD3DFMT(SDL_GetWindowPixelFormat(window));
} else {
data->pparams.BackBufferFormat = D3DFMT_UNKNOWN;
}
if (D3D_Reset(renderer) < ) {
return -;
} data->updateSize = SDL_FALSE;
}
if (data->beginScene) {
result = IDirect3DDevice9_BeginScene(data->device);
if (result == D3DERR_DEVICELOST) {
if (D3D_Reset(renderer) < ) {
return -;
}
result = IDirect3DDevice9_BeginScene(data->device);
}
if (FAILED(result)) {
return D3D_SetError("BeginScene()", result);
}
data->beginScene = SDL_FALSE;
}
return ;
}

D3D_SetBlendMode():设置渲染器状态。

static void D3D_SetBlendMode(D3D_RenderData * data, int blendMode)
{
switch (blendMode) {
case SDL_BLENDMODE_NONE:
IDirect3DDevice9_SetRenderState(data->device, D3DRS_ALPHABLENDENABLE,
FALSE);
break;
case SDL_BLENDMODE_BLEND:
IDirect3DDevice9_SetRenderState(data->device, D3DRS_ALPHABLENDENABLE,
TRUE);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_SRCBLEND,
D3DBLEND_SRCALPHA);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_DESTBLEND,
D3DBLEND_INVSRCALPHA);
if (data->enableSeparateAlphaBlend) {
IDirect3DDevice9_SetRenderState(data->device, D3DRS_SRCBLENDALPHA,
D3DBLEND_ONE);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_DESTBLENDALPHA,
D3DBLEND_INVSRCALPHA);
}
break;
case SDL_BLENDMODE_ADD:
IDirect3DDevice9_SetRenderState(data->device, D3DRS_ALPHABLENDENABLE,
TRUE);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_SRCBLEND,
D3DBLEND_SRCALPHA);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_DESTBLEND,
D3DBLEND_ONE);
if (data->enableSeparateAlphaBlend) {
IDirect3DDevice9_SetRenderState(data->device, D3DRS_SRCBLENDALPHA,
D3DBLEND_ZERO);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_DESTBLENDALPHA,
D3DBLEND_ONE);
}
break;
case SDL_BLENDMODE_MOD:
IDirect3DDevice9_SetRenderState(data->device, D3DRS_ALPHABLENDENABLE,
TRUE);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_SRCBLEND,
D3DBLEND_ZERO);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_DESTBLEND,
D3DBLEND_SRCCOLOR);
if (data->enableSeparateAlphaBlend) {
IDirect3DDevice9_SetRenderState(data->device, D3DRS_SRCBLENDALPHA,
D3DBLEND_ZERO);
IDirect3DDevice9_SetRenderState(data->device, D3DRS_DESTBLENDALPHA,
D3DBLEND_ONE);
}
break;
}
}

D3D_UpdateTextureScaleMode():设置纹理采样方式。

static void D3D_UpdateTextureScaleMode(D3D_RenderData *data, D3D_TextureData *texturedata, unsigned index)
{
if (texturedata->scaleMode != data->scaleMode[index]) {
IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MINFILTER,
texturedata->scaleMode);
IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MAGFILTER,
texturedata->scaleMode);
data->scaleMode[index] = texturedata->scaleMode;
}
}
2. OpenGL

OpenGL渲染器中对应RenderCopy()的函数是GL_RenderCopy(),它的源代码如下所示(位于render\opengl\SDL_render_gl.c)。

static int GL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_FRect * dstrect)
{
GL_RenderData *data = (GL_RenderData *) renderer->driverdata;
GL_TextureData *texturedata = (GL_TextureData *) texture->driverdata;
GLfloat minx, miny, maxx, maxy;
GLfloat minu, maxu, minv, maxv; GL_ActivateRenderer(renderer); data->glEnable(texturedata->type);
if (texturedata->yuv) {
data->glActiveTextureARB(GL_TEXTURE2_ARB);
data->glBindTexture(texturedata->type, texturedata->vtexture); data->glActiveTextureARB(GL_TEXTURE1_ARB);
data->glBindTexture(texturedata->type, texturedata->utexture); data->glActiveTextureARB(GL_TEXTURE0_ARB);
}
data->glBindTexture(texturedata->type, texturedata->texture); if (texture->modMode) {
GL_SetColor(data, texture->r, texture->g, texture->b, texture->a);
} else {
GL_SetColor(data, , , , );
} GL_SetBlendMode(data, texture->blendMode); if (texturedata->yuv) {
GL_SetShader(data, SHADER_YV12);
} else {
GL_SetShader(data, SHADER_RGB);
} minx = dstrect->x;
miny = dstrect->y;
maxx = dstrect->x + dstrect->w;
maxy = dstrect->y + dstrect->h; minu = (GLfloat) srcrect->x / texture->w;
minu *= texturedata->texw;
maxu = (GLfloat) (srcrect->x + srcrect->w) / texture->w;
maxu *= texturedata->texw;
minv = (GLfloat) srcrect->y / texture->h;
minv *= texturedata->texh;
maxv = (GLfloat) (srcrect->y + srcrect->h) / texture->h;
maxv *= texturedata->texh; data->glBegin(GL_TRIANGLE_STRIP);
data->glTexCoord2f(minu, minv);
data->glVertex2f(minx, miny);
data->glTexCoord2f(maxu, minv);
data->glVertex2f(maxx, miny);
data->glTexCoord2f(minu, maxv);
data->glVertex2f(minx, maxy);
data->glTexCoord2f(maxu, maxv);
data->glVertex2f(maxx, maxy);
data->glEnd(); data->glDisable(texturedata->type); return GL_CheckError("", renderer);
}

从代码中可以看出,GL_RenderCopy()函数调用了OpenGL的API函数 glActiveTexture(),glBindTexture()创建了一个纹理。并且使用 GL_SetBlendMode(),GL_SetShader()设置了有关的一些参数。

有一点需要注意,在OpenGL渲染器中,如果输入像素格式是YUV,就会使用3个纹理。

3. Software

Software渲染器中对应RenderCopy()的函数是SW_RenderCopy(),它的源代码如下所示(位于render\software\SDL_render_sw.c)。

static int SW_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
const SDL_Rect * srcrect, const SDL_FRect * dstrect)
{
SDL_Surface *surface = SW_ActivateRenderer(renderer);
SDL_Surface *src = (SDL_Surface *) texture->driverdata;
SDL_Rect final_rect; if (!surface) {
return -;
} if (renderer->viewport.x || renderer->viewport.y) {
final_rect.x = (int)(renderer->viewport.x + dstrect->x);
final_rect.y = (int)(renderer->viewport.y + dstrect->y);
} else {
final_rect.x = (int)dstrect->x;
final_rect.y = (int)dstrect->y;
}
final_rect.w = (int)dstrect->w;
final_rect.h = (int)dstrect->h; if ( srcrect->w == final_rect.w && srcrect->h == final_rect.h ) {
return SDL_BlitSurface(src, srcrect, surface, &final_rect);
} else {
return SDL_BlitScaled(src, srcrect, surface, &final_rect);
}
}

该函数的源代码还没有详细分析。

7:显示(SDL_RenderPresent())

上篇文章分析了该流程中的第6个函数SDL_RenderCopy()。本文继续分析该流程中的最后一个函数SDL_RenderPresent()。

SDL_RenderPresent()

函数简介

SDL使用SDL_RenderPresent()显示画面。SDL_RenderPresent()的原型如下。

[cpp] view plaincopy

  1. void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);

参数renderer用于指定渲染器。

函数调用关系图

SDL_RenderPresent()关键函数的调用关系可以用下图表示。

源代码分析

SDL_RenderPresent()的源代码位于render\SDL_render.c中。如下所示。

void SDL_RenderPresent(SDL_Renderer * renderer)
{
CHECK_RENDERER_MAGIC(renderer, ); /* Don't draw while we're hidden */
if (renderer->hidden) {
return;
}
renderer->RenderPresent(renderer);
}

从源代码中可以看出,SDL_RenderPresent()调用了SDL_Render的RenderPresent()方法显示图像。

下面我们详细看一下几种不同的渲染器的RenderPresent()的方法。

1. Direct3D

Direct3D 渲染器中对应RenderPresent()的函数是D3D_RenderPresent(),它的源代码如下所示(位于render\direct3d\SDL_render_d3d.c)。

static void D3D_RenderPresent(SDL_Renderer * renderer)
{
D3D_RenderData *data = (D3D_RenderData *) renderer->driverdata;
HRESULT result; if (!data->beginScene) {
IDirect3DDevice9_EndScene(data->device);
data->beginScene = SDL_TRUE;
} result = IDirect3DDevice9_TestCooperativeLevel(data->device);
if (result == D3DERR_DEVICELOST) {
/* We'll reset later */
return;
}
if (result == D3DERR_DEVICENOTRESET) {
D3D_Reset(renderer);
}
result = IDirect3DDevice9_Present(data->device, NULL, NULL, NULL, NULL);
if (FAILED(result)) {
D3D_SetError("Present()", result);
}
}

从代码中可以看出,该函数调用了2个最关键Direct3D的API:
IDirect3DDevice9_EndScene():结束一个场景。

IDirect3DDevice9_Present():显示。

2. OpenGL

OpenGL渲染器中对应RenderPresent()的函数是GL_RenderPresent(),它的源代码如下所示(位于render\opengl\SDL_render_gl.c)。

static void GL_RenderPresent(SDL_Renderer * renderer)
{
GL_ActivateRenderer(renderer); SDL_GL_SwapWindow(renderer->window);
}

代码比较简单,只有两行。关键的显示函数位于SDL_GL_SwapWindow()函数中。下面看一下SDL_GL_SwapWindow()的代码(位于video\SDL_video.c。感觉这里调用关系稍微有点乱…)。

void SDL_GL_SwapWindow(SDL_Window * window)
{
CHECK_WINDOW_MAGIC(window, ); if (!(window->flags & SDL_WINDOW_OPENGL)) {
SDL_SetError("The specified window isn't an OpenGL window");
return;
} if (SDL_GL_GetCurrentWindow() != window) {
SDL_SetError("The specified window has not been made current");
return;
} _this->GL_SwapWindow(_this, window);
}

从上述代码中可以看出,SDL_GL_SwapWindow()调用了SDL_VideoDevice的GL_SwapWindow()函数。我们看一下在 “Windows视频驱动”的情况下,该函数的代码。在“Windows视频驱动”的情况下,调用GL_SwapWindow()实际上是调用了 WIN_GL_SwapWindow()函数。看一下WIN_GL_SwapWindow()函数的代码(位于video\windows \SDL_windowsopengl.c)。

void WIN_GL_SwapWindow(_THIS, SDL_Window * window)
{
HDC hdc = ((SDL_WindowData *) window->driverdata)->hdc; SwapBuffers(hdc);
}

代码中调用了简单的一个函数SwapBuffers(),完成了显示功能。

3. Software

Software渲染器中对应RenderPresent()的函数是SW_RenderPresent(),它的源代码如下所示(位于render\software\SDL_render_sw.c)。

static void SW_RenderPresent(SDL_Renderer * renderer)
{
SDL_Window *window = renderer->window; if (window) {
SDL_UpdateWindowSurface(window);
}
}

从代码中可以看出,SW_RenderPresent()调用了一个函数SDL_UpdateWindowSurface()。我们看一下SDL_UpdateWindowSurface()的代码(位于video\SDL_video.c)。

int SDL_UpdateWindowSurface(SDL_Window * window)
{
SDL_Rect full_rect; CHECK_WINDOW_MAGIC(window, -); full_rect.x = ;
full_rect.y = ;
full_rect.w = window->w;
full_rect.h = window->h;
return SDL_UpdateWindowSurfaceRects(window, &full_rect, );
}

SDL_UpdateWindowSurface()又调用了另一个函数SDL_UpdateWindowSurfaceRects()。继续看SDL_UpdateWindowSurfaceRects()的代码。

int SDL_UpdateWindowSurfaceRects(SDL_Window * window, const SDL_Rect * rects,
int numrects)
{
CHECK_WINDOW_MAGIC(window, -); if (!window->surface_valid) {
return SDL_SetError("Window surface is invalid, please call SDL_GetWindowSurface() to get a new surface");
} return _this->UpdateWindowFramebuffer(_this, window, rects, numrects);
}

SDL_UpdateWindowSurfaceRects() 调用了SDL_VideoDevice的UpdateWindowFramebuffer()函数。在“Windows视频驱动”的情况下,相当于调用了 WIN_UpdateWindowFramebuffer()。我们看一下该函数的代码(位于video\windows \SDL_windowsframebuffer.c)

int WIN_UpdateWindowFramebuffer(_THIS, SDL_Window * window, const SDL_Rect * rects, int numrects)
{
SDL_WindowData *data = (SDL_WindowData *) window->driverdata; BitBlt(data->hdc, , , window->w, window->h, data->mdc, , , SRCCOPY);
return ;
}

经过一系列的寻找之后,终于找到了Software渲染器显示视频的“源头”:BitBlt()函数。

8:视频显示总结

本文简单总结一下SDL显示视频的源代码。

SDL显示视频的结构体

SDL显示视频涉及到下列结构体:

SDL_Window:代表了窗口
SDL_Renderer:代表了渲染器

SDL_Texture:代表了纹理

SDL_Rect:一个矩形框,用于确定纹理显示的位置。

上述几个结构体之间的关系如下图所示。

PS:该图源自于文章《最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

由图可见,YUV/RGB像素数据首先加载至SDL_Texture,然后通过SDL_Render渲染至SDL_Window。其中SDL_Rect可以指定显示的位置。

SDL显示视频的流程

SDL显示视频的流程如下图所示。

更清晰的图片链接(右键保存):http://my.csdn.net/leixiaohua1020/album/detail/1795751

从图中可以看出,整体的流程可以概括为如下步骤:

1. 初始化:SDL_Init()

2. 创建SDL_Window:SDL_CreateWindow()

3. 创建SDL_Render:SDL_CreateRenderer()

4. 创建SDL_Texture:SDL_CreateTexture()

5. 更新SDL_Texture:SDL_UpdateTexture()

6. 渲染SDL_Texture:SDL_RenderCopy()

7. 显示:SDL_RenderPresent()

8. 返回步骤4继续执行

上图中显示了SDL播放视频的时候API的调用流程。下文总结一下在不同的系统以及渲染技术下,这些SDL的API和系统底层API之间的调用关系。

SDL-Windows-Direct3D

SDL在Windows系统下,使用Direct3D渲染视频的时候的函数调用关系如下图所示。

PS:白色背景函数为SDL的API;蓝色背景的函数为Win32的API;紫色背景的函数Direct3D的API。

更清晰的图片链接(右键保存):http://my.csdn.net/leixiaohua1020/album/detail/1795753

从图中可以看出,SDL在Windows下使用Direct3D渲染视频的时候。函数之间的调用关系如下所列:

SDL_CreateWindow()调用了如下Win32的API:

CreateWindow()

SetWindowText()

ShowWindow()

SetWindowPos()

SDL_CreateRenderer()调用了如下Direc3D的API:

Direct3DCreate9()

IDirect3D9_GetDeviceCaps()

IDirect3D9_CreateDevice()

IDirect3DDevice9_SetFVF()

IDirect3DDevice9_SetRenderState()

IDirect3DDevice9_SetTextureStageState()

IDirect3DDevice9_SetTransform()

IDirect3DDevice9_CreatePixelShader()

SDL_CreateTexture()调用了如下Direc3D的API:

IDirect3DDevice9_CreateTexture()

SDL_UpdateTexture()调用了如下Direc3D的API:

IDirect3DTexture9_LockRect()

memcpy():这个不算D3D的,用于拷贝像素数据。

IDirect3DTexture9_UnlockRect()

SDL_RenderCopy()调用了如下Direc3D的API:

IDirect3DDevice9_BeginScene()

IDirect3DDevice9_SetRenderState()

IDirect3DDevice9_SetSamplerState()

IDirect3DDevice9_SetTexture()

IDirect3DDevice9_SetPixelShader()

IDirect3DDevice9_DrawPrimitiveUP()

SDL_RenderPresent()调用了如下Direc3D的API:

IDirect3DDevice9_EndScene()

IDirect3DDevice9_Present()

SDL-Windows-OpenGL

SDL在Windows系统下,使用OpenGL渲染视频的时候的函数调用关系如下图所示。

PS:白色背景函数为SDL的API;蓝色背景的函数为Win32的API;紫色背景的函数OpenGL的API。

更清晰的图片链接(右键保存):http://my.csdn.net/leixiaohua1020/album/detail/1795755

从图中可以看出,SDL在Windows下使用OpenGL渲染视频的时候。函数之间的调用关系如下所列:

SDL_CreateWindow()调用了如下Win32的API:

CreateWindow()

SetWindowText()

ShowWindow()

SetWindowPos()

SDL_CreateRenderer()调用了如下OpenGL的API:

glCreateProgramObject()

glCreateShaderObject()

glShaderSource()

glCompileShader()

GetObjectParameteriv()

AttachObject()

LinkProgram()

UseProgramObject()

SDL_CreateTexture()调用了如下OpenGL的API:

glGenTextures()

glBindTexture()

glTexParameteri()

glTexImage2D()

SDL_UpdateTexture()调用了如下OpenGL的API:

glBindTexture()

glTexSubImage2D()

SDL_RenderCopy()调用了如下OpenGL的API:

glActiveTexture()

glBindTexture()

SDL_RenderPresent()调用了如下OpenGL的API:

SwapBuffers()

SDL-Windows-Software

SDL在Windows系统下,使用Software渲染视频的时候的函数调用关系如下图所示。

PS1:白色背景函数为SDL的API;蓝色背景的函数为Win32的API。

PS2:Software渲染目前还没有透彻分析。

更清晰的图片链接(右键保存):http://my.csdn.net/leixiaohua1020/album/detail/1795757

从图中可以看出,SDL在Windows下使用Software渲染视频的时候。函数之间的调用关系如下所列:

SDL_CreateWindow()调用了如下Win32的API:

CreateWindow()

SetWindowText()

ShowWindow()

SetWindowPos()

SDL_CreateRenderer()调用了如下Win32的API:

CreateCompatibleBitmap()

GetDIBits()

CreateCompatibleDC()

CreateDIBSection()

SelectObject()

SDL_UpdateTexture()调用了memcpy()填充像素数据。

SDL_RenderPresent()调用了如下Win32的API:

BitBlt()

转:SDL2源代码分析的更多相关文章

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

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

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

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

  3. SDL2源代码分析6:复制到渲染器(SDL_RenderCopy())

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

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

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

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

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

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

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

  7. SDL2源代码分析2:窗口(SDL_Window)

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

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

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

  9. SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

随机推荐

  1. 小甲鱼python视频第九讲(课后习题)

    1.列表里可以存放什么东西? 可以存放任何的数据类型, list1 = ['我爱你','i love you',1314] print(list1) 2.向列表中添加元素 append(将参数作为一个 ...

  2. 浏览器css bug及bug解决方法

    Bugs及解决方案列表(以下实例默认运行环境都为Standard mode): 如何在IE6及更早浏览器中定义小高度的容器? 方法: #test{overflow:hidden;height:1px; ...

  3. 20145225《Java程序设计》 实验三 "敏捷开发与XP实践"

    20145225<Java程序设计> 实验三 "敏捷开发与XP实践" 实验报告 实验内容 使用 git 上传代码 使用 git 相互更改代码 实现代码的重载 git 上 ...

  4. 【转】supervisor安装与配置

    1.安装 宿主机环境:(Centos7) 宿主机环境 #yum install python-setuptools yum install python-setuptools#easy_install ...

  5. jsp_数据库的连接

    一.添加数据库以及表 在这里我们使用的是mysql数据库 二.配置数据库的驱动程序 将mysql的驱动程序复制到Tomcat目录下的lib目录中 注:在Tomcat中如果配置了新的jar包,则配置完成 ...

  6. java中的匿名内部类小结

    匿名内部类也就是没有名字的内部类.正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写. 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口. 实例1:不使用匿名内部类来实现 ...

  7. bcb安装控件方法汇总

    1.BCB 菜单: Project->Options->Packages->Add[喝小酒的网摘]http://blog.const.net.cn/a/8496.htm如果是BCB6 ...

  8. opengles2.0之图元装配和光栅化

    光栅化的过程就是把三维世界中的物体转换成屏幕上像素的过程. glGetfloatv();    --------v表示的是数组 gles2.0里面有两种绘图命令.glDrawArrays和glDraw ...

  9. 『TCP/IP详解——卷一:协议』读书笔记——08

    2013-08-21 13:56:23 3.3 IP路由选择 1. IP路由选择有两种情况.(1)如果目的主机与源主机直接相连(如点对点链路)或都在一个共享网络上(以太网或令牌环网),那么IP数据报就 ...

  10. VS2013_QT255开发相关技巧理解心得

    1. 在VS2013中打开QTCreater新建的项目 (1)通过双击.ui 打开QT的设计器,然后修改. (2)通过QT设计器,新建ui文件,放在VSQT的工程中 但是需要对此xxx.ui文件,进行 ...