SDL2源代碼分析


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 -1;  
    }  


/* Clear the error message */
    SDL_ClearError();  


#if SDL_VIDEO_DRIVER_WINDOWS
if ((flags & (SDL_INIT_HAPTIC|SDL_INIT_JOYSTICK))) {  
if (SDL_HelperWindowCreate() < 0) {  
return -1;  
        }  
    }  
#endif


#if !SDL_TIMERS_DISABLED
    SDL_TicksInit();  
#endif


if ((flags & SDL_INIT_GAMECONTROLLER)) {  
/* game controller implies joystick */
        flags |= SDL_INIT_JOYSTICK;  
    }  


if ((flags & (SDL_INIT_VIDEO|SDL_INIT_JOYSTICK))) {  
/* video or joystick implies events */
        flags |= SDL_INIT_EVENTS;  
    }  


/* Initialize the event subsystem */
if ((flags & SDL_INIT_EVENTS)) {  
#if !SDL_EVENTS_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_EVENTS)) {  
if (SDL_StartEventLoop() < 0) {  
return (-1);  
            }  
            SDL_QuitInit();  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_EVENTS);  
#else
return SDL_SetError("SDL not built with events support");  
#endif
    }  


/* Initialize the timer subsystem */
if ((flags & SDL_INIT_TIMER)){  
#if !SDL_TIMERS_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_TIMER)) {  
if (SDL_TimerInit() < 0) {  
return (-1);  
            }  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_TIMER);  
#else
return SDL_SetError("SDL not built with timer support");  
#endif
    }  


/* Initialize the video subsystem */
if ((flags & SDL_INIT_VIDEO)){  
#if !SDL_VIDEO_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_VIDEO)) {  
if (SDL_VideoInit(NULL) < 0) {  
return (-1);  
            }  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_VIDEO);  
#else
return SDL_SetError("SDL not built with video support");  
#endif
    }  


/* Initialize the audio subsystem */
if ((flags & SDL_INIT_AUDIO)){  
#if !SDL_AUDIO_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_AUDIO)) {  
if (SDL_AudioInit(NULL) < 0) {  
return (-1);  
            }  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_AUDIO);  
#else
return SDL_SetError("SDL not built with audio support");  
#endif
    }  


/* Initialize the joystick subsystem */
if ((flags & SDL_INIT_JOYSTICK)){  
#if !SDL_JOYSTICK_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_JOYSTICK)) {  
if (SDL_JoystickInit() < 0) {  
return (-1);  
           }  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_JOYSTICK);  
#else
return SDL_SetError("SDL not built with joystick support");  
#endif
    }  


if ((flags & SDL_INIT_GAMECONTROLLER)){  
#if !SDL_JOYSTICK_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_GAMECONTROLLER)) {  
if (SDL_GameControllerInit() < 0) {  
return (-1);  
            }  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_GAMECONTROLLER);  
#else
return SDL_SetError("SDL not built with joystick support");  
#endif
    }  


/* Initialize the haptic subsystem */
if ((flags & SDL_INIT_HAPTIC)){  
#if !SDL_HAPTIC_DISABLED
if (SDL_PrivateShouldInitSubsystem(SDL_INIT_HAPTIC)) {  
if (SDL_HapticInit() < 0) {  
return (-1);  
            }  
        }  
        SDL_PrivateSubsystemRefCountIncr(SDL_INIT_HAPTIC);  
#else
return SDL_SetError("SDL not built with haptic (force feedback) support");  
#endif
    }  


return (0);  
}

SDL_InitSubSystem()函數的定義看上去很長,實際上卻並不復雜。下面簡單闡述一下它的一些關鍵點:

1. 通過將傳入的flag與子系統的宏定義(例如SDL_INIT_VIDEO,SDL_INIT_AUDIO等)相與,判斷是否需要初始化該子系統。

2. 有很多的預定義的宏,用於判斷SDL是否支持這些子系統。例如 SDL_EVENTS_DISABLED,SDL_TIMERS_DISABLED,SDL_VIDEO_DISABLED,SDL_AUDIO_DISABLED,SDL_JOYSTICK_DISABLED,SDL_HAPTIC_DISABLED 等。這些宏的定義位於SDL_config_minimal.h文件中,如下所示。

/* Enable the dummy audio driver (src/audio/dummy/\*.c) */
#define SDL_AUDIO_DRIVER_DUMMY  1


/* Enable the stub joystick driver (src/joystick/dummy/\*.c) */
#define SDL_JOYSTICK_DISABLED   1


/* Enable the stub haptic driver (src/haptic/dummy/\*.c) */
#define SDL_HAPTIC_DISABLED 1


/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */
#define SDL_LOADSO_DISABLED 1


/* Enable the stub thread support (src/thread/generic/\*.c) */
#define SDL_THREADS_DISABLED    1


/* Enable the stub timer support (src/timer/dummy/\*.c) */
#define SDL_TIMERS_DISABLED 1


/* Enable the dummy video driver (src/video/dummy/\*.c) */
#define SDL_VIDEO_DRIVER_DUMMY  1


/* Enable the dummy filesystem driver (src/filesystem/dummy/\*.c) */
#define SDL_FILESYSTEM_DUMMY  1

如果這些定義取值不為0,代表該子系統已經被disable了,就不編譯指定子系統的源代碼了。初始化的時候會調用SDL_SetError()函數輸出錯誤信息。例如SDL_VIDEO_DISABLED如果設置為1的話,初始化視頻子系統的時候會執行以下代碼。

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

3. 在每一個子系統真正初始化之前,都會調用一個函數SDL_PrivateShouldInitSubsystem()。該函數用於檢查目標子系統是否需要初始化。

4. 在一個子系統初始化之后,都會調用一個函數SDL_PrivateSubsystemRefCountIncr()。該函數用於增加子系統的引用計數。

5. 下表列出各個子系統的初始化函數。

子系統名稱

函數

AUDIO(音頻)

SDL_AudioInit()

VIDEO(視頻)

SDL_VideoInit()

TIMERS(定時器)

SDL_TicksInit(),SDL_TimerInit()

EVENTS(事件)

SDL_StartEventLoop()

JOYSTICK(搖桿)

SDL_GameControllerInit()

HAPTIC(觸摸屏)

SDL_HapticInit()

我們先不看JOYSTICK(搖桿),HAPTIC(觸摸屏)這些方面的代碼,專門關注AUDIO(音頻),VIDEO(視頻)這兩個方面的代碼。

1. VIDEO(視頻)

視頻子系統的初始化函數是SDL_VideoInit()。它的源代碼位於video\SDL_video.c文件中,如下所示。

/*
 * Initialize the video and event subsystems -- determine native pixel format
 */
int SDL_VideoInit(const char *driver_name)  
{  
    SDL_VideoDevice *video;  
const char *hint;  
int index;  
int i;  
    SDL_bool allow_screensaver;  


/* Check to make sure we don't overwrite '_this' */
if (_this != NULL) {  
        SDL_VideoQuit();  
    }  


#if !SDL_TIMERS_DISABLED
    SDL_TicksInit();  
#endif


/* Start the event loop */
if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0 ||  
        SDL_KeyboardInit() < 0 ||  
        SDL_MouseInit() < 0 ||  
        SDL_TouchInit() < 0) {  
return -1;  
    }  


/* Select the proper video driver */
    index = 0;  
    video = NULL;  
if (driver_name == NULL) {  
        driver_name = SDL_getenv("SDL_VIDEODRIVER");  
    }  
if (driver_name != NULL) {  
for (i = 0; bootstrap[i]; ++i) {  
if (SDL_strncasecmp(bootstrap[i]->name, driver_name, SDL_strlen(driver_name)) == 0) {  
if (bootstrap[i]->available()) {  
                    video = bootstrap[i]->create(index);  
break;  
                }  
            }  
        }  
    } else {  
for (i = 0; bootstrap[i]; ++i) {  
if (bootstrap[i]->available()) {  
                video = bootstrap[i]->create(index);  
if (video != NULL) {  
break;  
                }  
            }  
        }  
    }  
if (video == NULL) {  
if (driver_name) {  
return SDL_SetError("%s not available", driver_name);  
        }  
return SDL_SetError("No available video device");  
    }  
    _this = video;  
    _this->name = bootstrap[i]->name;  
    _this->next_object_id = 1;  




/* Set some very sane GL defaults */
    _this->gl_config.driver_loaded = 0;  
    _this->gl_config.dll_handle = NULL;  
    SDL_GL_ResetAttributes();  


    _this->current_glwin_tls = SDL_TLSCreate();  
    _this->current_glctx_tls = SDL_TLSCreate();  


/* Initialize the video subsystem */
if (_this->VideoInit(_this) < 0) {  
        SDL_VideoQuit();  
return -1;  
    }  


/* Make sure some displays were added */
if (_this->num_displays == 0) {  
        SDL_VideoQuit();  
return SDL_SetError("The video driver did not add any displays");  
    }  


/* Add the renderer framebuffer emulation if desired */
if (ShouldUseTextureFramebuffer()) {  
        _this->CreateWindowFramebuffer = SDL_CreateWindowTexture;  
        _this->UpdateWindowFramebuffer = SDL_UpdateWindowTexture;  
        _this->DestroyWindowFramebuffer = SDL_DestroyWindowTexture;  
    }  


/* Disable the screen saver by default. This is a change from <= 2.0.1,
       but most things using SDL are games or media players; you wouldn't
       want a screensaver to trigger if you're playing exclusively with a
       joystick, or passively watching a movie. Things that use SDL but
       function more like a normal desktop app should explicitly reenable the
       screensaver. */
    hint = SDL_GetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER);  
if (hint) {  
        allow_screensaver = SDL_atoi(hint) ? SDL_TRUE : SDL_FALSE;  
    } else {  
        allow_screensaver = SDL_FALSE;  
    }  
if (!allow_screensaver) {  
        SDL_DisableScreenSaver();  
    }  


/* If we don't use a screen keyboard, turn on text input by default,
       otherwise programs that expect to get text events without enabling
       UNICODE input won't get any events.


       Actually, come to think of it, you needed to call SDL_EnableUNICODE(1)
       in SDL 1.2 before you got text input events.  Hmm...
     */
if (!SDL_HasScreenKeyboardSupport()) {  
        SDL_StartTextInput();  
    }  


/* We're ready to go! */
return 0;  
}

下面簡單闡述一下它的大致步驟:
1. 初始化一些子系統,比如EVENTS(事件)子系統。也就是說,就算在調用SDL_Init()的時候不指定初始化EVENTS子系統,在初始化VIDEO子系統的時候,同樣也會初始化EVENTS子系統。
2. 選擇一個合適的SDL_VideoDevice。
在這里,涉及到兩個重要的結構體:SDL_VideoDevice以及VideoBootStrap。其中SDL_VideoDevice代表了一個視頻驅動程序。VideoBootStrap從字面上理解是“視頻驅動程序的引導程序”,即用於創建一個SDL_VideoDevice。因此,我們先來看看 VideoBootStrap這個結構體。它的定義如下(位於video\SDL_sysvideo.h)。

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

可以看出它的定義比較簡單,每個字段的含義如下:

name:驅動名稱
desc:描述
available():檢查是否可用的一個函數指針
create():創建SDL_VideoDevice的函數指針


SDL中有一個VideoBootStrap類型的靜態數組bootstrap。用於存儲SDL支持的視頻驅動程序。注意這是SDL“跨平台”特性中很重要的一部分。該靜態數組定義如下(位於video\SDL_video.c)。

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

從代碼中可以看出,SDL通過預編譯宏取值是否非0來判斷是否支持該視頻驅動。我們可以看一下Windows的視頻設備驅動的定義。該設備驅動同樣是一個靜態變量,名稱為WINDOWS_bootstrap(位於video\windows\SDL_windowsvideo.c)。

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

可以看出該視頻驅動名稱為“windows”,描述為“SDL Windows video driver”,檢查是否可用的函數為“WIN_Available()”,創建SDL_VideoDevice的函數為“WIN_CreateDevice()”。
同樣, Android的視頻設備驅動的名稱為Android_bootstrap;PSP的視頻設備驅動為PSP_bootstrap;X11的視頻設備驅動為X11_bootstrap。不再一一例舉。
下面看一下Windows視頻驅動中那兩個函數的定義。WIN_Available()定義如下。

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

可見該函數沒有做任何的處理。WIN_CreateDevice()定義如下。

static SDL_VideoDevice *  
WIN_CreateDevice(int devindex)  
{  
    SDL_VideoDevice *device;  
    SDL_VideoData *data;  


    SDL_RegisterApp(NULL, 0, NULL);  


/* Initialize all variables that we clean on shutdown */
    device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));  
if (device) {  
        data = (struct SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));  
    } else {  
        data = NULL;  
    }  
if (!data) {  
        SDL_free(device);  
        SDL_OutOfMemory();  
return NULL;  
    }  
    device->driverdata = data;  


    data->userDLL = SDL_LoadObject("USER32.DLL");  
if (data->userDLL) {  
        data->CloseTouchInputHandle = (BOOL (WINAPI *)( HTOUCHINPUT )) SDL_LoadFunction(data->userDLL, "CloseTouchInputHandle");  
        data->GetTouchInputInfo = (BOOL (WINAPI *)( HTOUCHINPUT, UINT, PTOUCHINPUT, int )) SDL_LoadFunction(data->userDLL, "GetTouchInputInfo");  
        data->RegisterTouchWindow = (BOOL (WINAPI *)( HWND, ULONG )) SDL_LoadFunction(data->userDLL, "RegisterTouchWindow");  
    }  


/* Set the function pointers */
    device->VideoInit = WIN_VideoInit;  
    device->VideoQuit = WIN_VideoQuit;  
    device->GetDisplayBounds = WIN_GetDisplayBounds;  
    device->GetDisplayModes = WIN_GetDisplayModes;  
    device->SetDisplayMode = WIN_SetDisplayMode;  
    device->PumpEvents = WIN_PumpEvents;  


#undef CreateWindow
    device->CreateWindow = WIN_CreateWindow;  
    device->CreateWindowFrom = WIN_CreateWindowFrom;  
    device->SetWindowTitle = WIN_SetWindowTitle;  
    device->SetWindowIcon = WIN_SetWindowIcon;  
    device->SetWindowPosition = WIN_SetWindowPosition;  
    device->SetWindowSize = WIN_SetWindowSize;  
    device->ShowWindow = WIN_ShowWindow;  
    device->HideWindow = WIN_HideWindow;  
    device->RaiseWindow = WIN_RaiseWindow;  
    device->MaximizeWindow = WIN_MaximizeWindow;  
    device->MinimizeWindow = WIN_MinimizeWindow;  
    device->RestoreWindow = WIN_RestoreWindow;  
    device->SetWindowBordered = WIN_SetWindowBordered;  
    device->SetWindowFullscreen = WIN_SetWindowFullscreen;  
    device->SetWindowGammaRamp = WIN_SetWindowGammaRamp;  
    device->GetWindowGammaRamp = WIN_GetWindowGammaRamp;  
    device->SetWindowGrab = WIN_SetWindowGrab;  
    device->DestroyWindow = WIN_DestroyWindow;  
    device->GetWindowWMInfo = WIN_GetWindowWMInfo;  
    device->CreateWindowFramebuffer = WIN_CreateWindowFramebuffer;  
    device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer;  
    device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer;  
    device->OnWindowEnter = WIN_OnWindowEnter;  


    device->shape_driver.CreateShaper = Win32_CreateShaper;  
    device->shape_driver.SetWindowShape = Win32_SetWindowShape;  
    device->shape_driver.ResizeWindowShape = Win32_ResizeWindowShape;  


#if SDL_VIDEO_OPENGL_WGL
    device->GL_LoadLibrary = WIN_GL_LoadLibrary;  
    device->GL_GetProcAddress = WIN_GL_GetProcAddress;  
    device->GL_UnloadLibrary = WIN_GL_UnloadLibrary;  
    device->GL_CreateContext = WIN_GL_CreateContext;  
    device->GL_MakeCurrent = WIN_GL_MakeCurrent;  
    device->GL_SetSwapInterval = WIN_GL_SetSwapInterval;  
    device->GL_GetSwapInterval = WIN_GL_GetSwapInterval;  
    device->GL_SwapWindow = WIN_GL_SwapWindow;  
    device->GL_DeleteContext = WIN_GL_DeleteContext;  
#endif
    device->StartTextInput = WIN_StartTextInput;  
    device->StopTextInput = WIN_StopTextInput;  
    device->SetTextInputRect = WIN_SetTextInputRect;  


    device->SetClipboardText = WIN_SetClipboardText;  
    device->GetClipboardText = WIN_GetClipboardText;  
    device->HasClipboardText = WIN_HasClipboardText;  


    device->free = WIN_DeleteDevice;  


return device;  
}

該函數首先通過SDL_calloc()的方法為創建的SDL_VideoDevice分配了一塊內存,接下來為創建的SDL_VideoDevice結構體中的函數指針賦了一大堆的值。這也是SDL“跨平台”特性的一個特點:通過調用SDL_VideoDevice中的接口函數,就可以調用不同平台的具體實現功能的函數。

PS:在這里補充一個SDL中內存分配函數的知識。在SDL中分配內存使用SDL_malloc(),SDL_calloc(),這些函數實際上就是malloc(),calloc()。它們的定義位於stdlib\SDL_malloc.c文件中。如下所示:

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

下面來看一下SDL_VideoDevice這個結構體的定義(位於video\SDL_sysvideo.h)。

struct SDL_VideoDevice  
{  
/* * * */
/* The name of this video driver */
const char *name;  


/* * * */
/* Initialization/Query functions */


/*
     * Initialize the native video subsystem, filling in the list of
     * displays for this driver, returning 0 or -1 if there's an error.
     */
int (*VideoInit) (_THIS);  


/*
     * Reverse the effects VideoInit() -- called if VideoInit() fails or
     * if the application is shutting down the video subsystem.
     */
void (*VideoQuit) (_THIS);  


/* * * */
/*
     * Display functions
     */


/*
     * Get the bounds of a display
     */
int (*GetDisplayBounds) (_THIS, SDL_VideoDisplay * display, SDL_Rect * rect);  


/*
     * Get a list of the available display modes for a display.
     */
void (*GetDisplayModes) (_THIS, SDL_VideoDisplay * display);  


/*
     * Setting the display mode is independent of creating windows, so
     * when the display mode is changed, all existing windows should have
     * their data updated accordingly, including the display surfaces
     * associated with them.
     */
int (*SetDisplayMode) (_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode);  


/* * * */
/*
     * Window functions
     */
int (*CreateWindow) (_THIS, SDL_Window * window);  
int (*CreateWindowFrom) (_THIS, SDL_Window * window, const void *data);  
void (*SetWindowTitle) (_THIS, SDL_Window * window);  
void (*SetWindowIcon) (_THIS, SDL_Window * window, SDL_Surface * icon);  
void (*SetWindowPosition) (_THIS, SDL_Window * window);  
void (*SetWindowSize) (_THIS, SDL_Window * window);  
void (*SetWindowMinimumSize) (_THIS, SDL_Window * window);  
void (*SetWindowMaximumSize) (_THIS, SDL_Window * window);  
void (*ShowWindow) (_THIS, SDL_Window * window);  
void (*HideWindow) (_THIS, SDL_Window * window);  
void (*RaiseWindow) (_THIS, SDL_Window * window);  
void (*MaximizeWindow) (_THIS, SDL_Window * window);  
void (*MinimizeWindow) (_THIS, SDL_Window * window);  
void (*RestoreWindow) (_THIS, SDL_Window * window);  
void (*SetWindowBordered) (_THIS, SDL_Window * window, SDL_bool bordered);  
void (*SetWindowFullscreen) (_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen);  
int (*SetWindowGammaRamp) (_THIS, SDL_Window * window, const Uint16 * ramp);  
int (*GetWindowGammaRamp) (_THIS, SDL_Window * window, Uint16 * ramp);  
void (*SetWindowGrab) (_THIS, SDL_Window * window, SDL_bool grabbed);  
void (*DestroyWindow) (_THIS, SDL_Window * window);  
int (*CreateWindowFramebuffer) (_THIS, SDL_Window * window, Uint32 * format, void ** pixels, int *pitch);  
int (*UpdateWindowFramebuffer) (_THIS, SDL_Window * window, const SDL_Rect * rects, int numrects);  
void (*DestroyWindowFramebuffer) (_THIS, SDL_Window * window);  
void (*OnWindowEnter) (_THIS, SDL_Window * window);  


/* * * */
/*
     * Shaped-window functions
     */
    SDL_ShapeDriver shape_driver;  


/* Get some platform dependent window information */
      SDL_bool(*GetWindowWMInfo) (_THIS, SDL_Window * window,  
struct SDL_SysWMinfo * info);  


/* * * */
/*
     * OpenGL support
     */
int (*GL_LoadLibrary) (_THIS, const char *path);  
void *(*GL_GetProcAddress) (_THIS, const char *proc);  
void (*GL_UnloadLibrary) (_THIS);  
      SDL_GLContext(*GL_CreateContext) (_THIS, SDL_Window * window);  
int (*GL_MakeCurrent) (_THIS, SDL_Window * window, SDL_GLContext context);  
void (*GL_GetDrawableSize) (_THIS, SDL_Window * window, int *w, int *h);  
int (*GL_SetSwapInterval) (_THIS, int interval);  
int (*GL_GetSwapInterval) (_THIS);  
void (*GL_SwapWindow) (_THIS, SDL_Window * window);  
void (*GL_DeleteContext) (_THIS, SDL_GLContext context);  


/* * * */
/*
     * Event manager functions
     */
void (*PumpEvents) (_THIS);  


/* Suspend the screensaver */
void (*SuspendScreenSaver) (_THIS);  


/* Text input */
void (*StartTextInput) (_THIS);  
void (*StopTextInput) (_THIS);  
void (*SetTextInputRect) (_THIS, SDL_Rect *rect);  


/* Screen keyboard */
    SDL_bool (*HasScreenKeyboardSupport) (_THIS);  
void (*ShowScreenKeyboard) (_THIS, SDL_Window *window);  
void (*HideScreenKeyboard) (_THIS, SDL_Window *window);  
    SDL_bool (*IsScreenKeyboardShown) (_THIS, SDL_Window *window);  


/* Clipboard */
int (*SetClipboardText) (_THIS, const char *text);  
char * (*GetClipboardText) (_THIS);  
    SDL_bool (*HasClipboardText) (_THIS);  


/* MessageBox */
int (*ShowMessageBox) (_THIS, const SDL_MessageBoxData *messageboxdata, int *buttonid);  


/* * * */
/* Data common to all drivers */
    SDL_bool suspend_screensaver;  
int num_displays;  
    SDL_VideoDisplay *displays;  
    SDL_Window *windows;  
    Uint8 window_magic;  
    Uint32 next_object_id;  
char * clipboard_text;  


/* * * */
/* Data used by the GL drivers */
struct
    {  
int red_size;  
int green_size;  
int blue_size;  
int alpha_size;  
int depth_size;  
int buffer_size;  
int stencil_size;  
int double_buffer;  
int accum_red_size;  
int accum_green_size;  
int accum_blue_size;  
int accum_alpha_size;  
int stereo;  
int multisamplebuffers;  
int multisamplesamples;  
int accelerated;  
int major_version;  
int minor_version;  
int flags;  
int profile_mask;  
int share_with_current_context;  
int framebuffer_srgb_capable;  
int retained_backing;  
int driver_loaded;  
char driver_path[256];  
void *dll_handle;  
    } gl_config;  


/* * * */
/* Cache current GL context; don't call the OS when it hasn't changed. */
/* We have the global pointers here so Cocoa continues to work the way
       it always has, and the thread-local storage for the general case.
     */
    SDL_Window *current_glwin;  
    SDL_GLContext current_glctx;  
    SDL_TLSID current_glwin_tls;  
    SDL_TLSID current_glctx_tls;  


/* * * */
/* Data private to this driver */
void *driverdata;  
struct SDL_GLDriverData *gl_data;  

#if SDL_VIDEO_OPENGL_EGL
struct SDL_EGL_VideoData *egl_data;  
#endif

#if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
struct SDL_PrivateGLESData *gles_data;  
#endif


/* * * */
/* The function used to dispose of this structure */
void (*free) (_THIS);  
};

這個結構體包含了一大堆的函數指針。這些指針在前文所說的VideoBootStrap的create()方法調用的時候會被賦值。SDL通過調用這些函數指針,完成視頻顯示的各項內容。
3. 調用選中的SDL_VideoDevice的VideoInit()方法。
選擇了合適的SDL_VideoDevice之后,調用該SDL_VideoDevice的VideoInit()就可以真正的初始化視頻驅動了。以 Windows系統為例。從前文的函數中可以看出,Windows系統的VideoInit()接口實際上調用了WIN_VideoInit()函數。我們來看一下WIN_VideoInit()函數的定義(位於video\windows\SDL_windowsvideo.c)。

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


    WIN_InitKeyboard(_this);  
    WIN_InitMouse(_this);  


return 0;  
}

其中有3個函數:WIN_InitModes(),WIN_InitKeyboard(),WIN_InitMouse()。后兩個函數用於初始化鍵盤和鼠標,我們暫且不研究。看一下WIN_InitModes()的函數。

int WIN_InitModes(_THIS)  
{  
int pass;  
DWORD i, j, count;  
    DISPLAY_DEVICE device;  


    device.cb = sizeof(device);  


/* Get the primary display in the first pass */
for (pass = 0; pass < 2; ++pass) {  
for (i = 0; ; ++i) {  
TCHAR DeviceName[32];  


if (!EnumDisplayDevices(NULL, i, &device, 0)) {  
break;  
            }  
if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {  
continue;  
            }  
if (pass == 0) {  
if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {  
continue;  
                }  
            } else {  
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {  
continue;  
                }  
            }  
            SDL_memcpy(DeviceName, device.DeviceName, sizeof(DeviceName));  
#ifdef DEBUG_MODES
            printf("Device: %s\n", WIN_StringToUTF8(DeviceName));  
#endif
            count = 0;  
for (j = 0; ; ++j) {  
if (!EnumDisplayDevices(DeviceName, j, &device, 0)) {  
break;  
                }  
if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {  
continue;  
                }  
if (pass == 0) {  
if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {  
continue;  
                    }  
                } else {  
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {  
continue;  
                    }  
                }  
                count += WIN_AddDisplay(device.DeviceName);  
            }  
if (count == 0) {  
                WIN_AddDisplay(DeviceName);  
            }  
        }  
    }  
if (_this->num_displays == 0) {  
return SDL_SetError("No displays available");  
    }  
return 0;  
}

該函數的作用就是獲得系統中顯示設備的信息。目前還沒有深入研究,待有時間再補上該函數的分析。

2. AUDIO(音頻)

音頻子系統的初始化函數是SDL_ AudioInit ()。它的源代碼位於audio\SDL_audio.c文件中,如下所示。

int SDL_AudioInit(const char *driver_name)  
{  
int i = 0;  
int initialized = 0;  
int tried_to_init = 0;  


if (SDL_WasInit(SDL_INIT_AUDIO)) {  
        SDL_AudioQuit();        /* shutdown driver if already running. */
    }  


    SDL_memset(¤t_audio, '\0', sizeof(current_audio));  
    SDL_memset(open_devices, '\0', sizeof(open_devices));  


/* Select the proper audio driver */
if (driver_name == NULL) {  
        driver_name = SDL_getenv("SDL_AUDIODRIVER");  
    }  


for (i = 0; (!initialized) && (bootstrap[i]); ++i) {  
/* make sure we should even try this driver before doing so... */
const AudioBootStrap *backend = bootstrap[i];  
if ((driver_name && (SDL_strncasecmp(backend->name, driver_name, SDL_strlen(driver_name)) != 0)) ||  
            (!driver_name && backend->demand_only)) {  
continue;  
        }  


        tried_to_init = 1;  
        SDL_memset(¤t_audio, 0, sizeof(current_audio));  
        current_audio.name = backend->name;  
        current_audio.desc = backend->desc;  
        initialized = backend->init(¤t_audio.impl);  
    }  


if (!initialized) {  
/* specific drivers will set the error message if they fail... */
if (!tried_to_init) {  
if (driver_name) {  
                SDL_SetError("Audio target '%s' not available", driver_name);  
            } else {  
                SDL_SetError("No available audio device");  
            }  
        }  


        SDL_memset(¤t_audio, 0, sizeof(current_audio));  
return (-1);            /* No driver was available, so fail. */
    }  


    finalize_audio_entry_points();  


return (0);  
}

音頻初始化和視頻很類似,比視頻簡單一些,關鍵在於選擇一個合適的SDL_AudioDriver。

在這里,涉及到兩個重要的結構體:SDL_AudioDriver以及AudioBootStrap。其中SDL_AudioDriver代表了一個音頻驅動程序。AudioBootStrap從字面上理解是“音頻驅動程序的引導程序”,即用於創建一個SDL_AudioDriver。可以看出音頻子系統中的結構體和視頻子系統中的結構體的格式基本上是一模一樣的。我們先來看看AudioBootStrap這個結構體。它的定義如下(位於 audio\SDL_sysaudio.h)。

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

可以看出它的定義比較簡單,每個字段的含義如下:

name:驅動名稱
desc:描述
init():創建SDL_AudioDriver的函數指針
demand_only:沒有研究過。


SDL中有一個AudioBootStrap類型的靜態數組bootstrap。用於存儲SDL支持的音頻驅動程序。該靜態數組定義如下(位於audio\SDL_audio.c)。

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

在這里我們可以看一下DirectSound的AudioBootStrap的變量DSOUND_bootstrap(audio\directsound\SDL_directsound.c)。

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

可以看出該音頻驅動名稱為“directsound”,描述為“DirectSound”,創建SDL_AudioDriver的函數為“DSOUND_Init()”。
下面看一下DirectSound初始化函數DSOUND_Init()的定義。

static int DSOUND_Init(SDL_AudioDriverImpl * impl)  
{  
if (!DSOUND_Load()) {  
return 0;  
    }  


/* Set the function pointers */
    impl->DetectDevices = DSOUND_DetectDevices;  
    impl->OpenDevice = DSOUND_OpenDevice;  
    impl->PlayDevice = DSOUND_PlayDevice;  
    impl->WaitDevice = DSOUND_WaitDevice;  
    impl->WaitDone = DSOUND_WaitDone;  
    impl->GetDeviceBuf = DSOUND_GetDeviceBuf;  
    impl->CloseDevice = DSOUND_CloseDevice;  
    impl->Deinitialize = DSOUND_Deinitialize;  


return 1;   /* this audio target is available. */
}

和視頻驅動的初始化一樣,音頻驅動初始化也是對SDL_AudioDriver的接口指針進行賦值。在這里涉及到了一個DirectSound的加載函數DSOUND_Load(),我們可以看一下它的代碼。

static int DSOUND_Load(void)  
{  
int loaded = 0;  


    DSOUND_Unload();  


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


if (!loaded) {  
            SDL_SetError("DirectSound: System doesn't appear to have DX8.");  
        }  
    }  


if (!loaded) {  
        DSOUND_Unload();  
    }  


return loaded;  
}

從代碼中可以看出,該函數加載了“DSOUND.DLL”的DirectSoundCreate8(),DirectSoundEnumerateW(),DirectSoundCaptureEnumerateW()函數。

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) < 0) {  
return NULL;  
        }  
    }  


/* Some platforms can't create zero-sized windows */
if (w < 1) {  
        w = 1;  
    }  
if (h < 1) {  
        h = 1;  
    }  


/* 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) < 0) {  
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) > 0) {  
            flags &= ~SDL_WINDOW_ALLOW_HIGHDPI;  
        }  
    }  


    window = (SDL_Window *)SDL_calloc(1, 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) / 2;  
        }  
if (SDL_WINDOWPOS_ISUNDEFINED(y) || SDL_WINDOWPOS_ISCENTERED(y)) {  
            window->y = bounds.y + (bounds.h - h) / 2;  
        }  
    }  
    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) < 0) {  
        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, 0, NULL);  


/* Initialize all variables that we clean on shutdown */
    device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));  
if (device) {  
        data = (struct SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));  
    } else {  
        data = NULL;  
    }  
if (!data) {  
        SDL_free(device);  
        SDL_OutOfMemory();  
return NULL;  
    }  
    device->driverdata = data;  


    data->userDLL = SDL_LoadObject("USER32.DLL");  
if (data->userDLL) {  
        data->CloseTouchInputHandle = (BOOL (WINAPI *)( HTOUCHINPUT )) SDL_LoadFunction(data->userDLL, "CloseTouchInputHandle");  
        data->GetTouchInputInfo = (BOOL (WINAPI *)( HTOUCHINPUT, UINT, PTOUCHINPUT, int )) SDL_LoadFunction(data->userDLL, "GetTouchInputInfo");  
        data->RegisterTouchWindow = (BOOL (WINAPI *)( HWND, ULONG )) SDL_LoadFunction(data->userDLL, "RegisterTouchWindow");  
    }  


/* Set the function pointers */
    device->VideoInit = WIN_VideoInit;  
    device->VideoQuit = WIN_VideoQuit;  
    device->GetDisplayBounds = WIN_GetDisplayBounds;  
    device->GetDisplayModes = WIN_GetDisplayModes;  
    device->SetDisplayMode = WIN_SetDisplayMode;  
    device->PumpEvents = WIN_PumpEvents;  


#undef CreateWindow
    device->CreateWindow = WIN_CreateWindow;  
    device->CreateWindowFrom = WIN_CreateWindowFrom;  
    device->SetWindowTitle = WIN_SetWindowTitle;  
    device->SetWindowIcon = WIN_SetWindowIcon;  
    device->SetWindowPosition = WIN_SetWindowPosition;  
    device->SetWindowSize = WIN_SetWindowSize;  
    device->ShowWindow = WIN_ShowWindow;  
    device->HideWindow = WIN_HideWindow;  
    device->RaiseWindow = WIN_RaiseWindow;  
    device->MaximizeWindow = WIN_MaximizeWindow;  
    device->MinimizeWindow = WIN_MinimizeWindow;  
    device->RestoreWindow = WIN_RestoreWindow;  
    device->SetWindowBordered = WIN_SetWindowBordered;  
    device->SetWindowFullscreen = WIN_SetWindowFullscreen;  
    device->SetWindowGammaRamp = WIN_SetWindowGammaRamp;  
    device->GetWindowGammaRamp = WIN_GetWindowGammaRamp;  
    device->SetWindowGrab = WIN_SetWindowGrab;  
    device->DestroyWindow = WIN_DestroyWindow;  
    device->GetWindowWMInfo = WIN_GetWindowWMInfo;  
    device->CreateWindowFramebuffer = WIN_CreateWindowFramebuffer;  
    device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer;  
    device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer;  
    device->OnWindowEnter = WIN_OnWindowEnter;  


    device->shape_driver.CreateShaper = Win32_CreateShaper;  
    device->shape_driver.SetWindowShape = Win32_SetWindowShape;  
    device->shape_driver.ResizeWindowShape = Win32_ResizeWindowShape;  


#if SDL_VIDEO_OPENGL_WGL
    device->GL_LoadLibrary = WIN_GL_LoadLibrary;  
    device->GL_GetProcAddress = WIN_GL_GetProcAddress;  
    device->GL_UnloadLibrary = WIN_GL_UnloadLibrary;  
    device->GL_CreateContext = WIN_GL_CreateContext;  
    device->GL_MakeCurrent = WIN_GL_MakeCurrent;  
    device->GL_SetSwapInterval = WIN_GL_SetSwapInterval;  
    device->GL_GetSwapInterval = WIN_GL_GetSwapInterval;  
    device->GL_SwapWindow = WIN_GL_SwapWindow;  
    device->GL_DeleteContext = WIN_GL_DeleteContext;  
#endif
    device->StartTextInput = WIN_StartTextInput;  
    device->StopTextInput = WIN_StopTextInput;  
    device->SetTextInputRect = WIN_SetTextInputRect;  


    device->SetClipboardText = WIN_SetClipboardText;  
    device->GetClipboardText = WIN_GetClipboardText;  
    device->HasClipboardText = WIN_HasClipboardText;  


    device->free = WIN_DeleteDevice;  


return device;  
}

從上文中可以看出,“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, 0);  
    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) < 0) {  
        DestroyWindow(hwnd);  
return -1;  
    }  


#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) < 0) {  
            WIN_DestroyWindow(_this, window);  
return -1;  
        }  
#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) < 0) {  
            WIN_DestroyWindow(_this, window);  
return -1;  
        }  
    }  
#endif


return 0;  
}

從該函數的代碼中我們可以看到很多的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, -1);  


    flags &= FULLSCREEN_MASK;  


if ( flags == (window->flags & FULLSCREEN_MASK) ) {  
return 0;  
    }  


/* clear the previous flags and OR in the new ones */
    window->flags &= ~FULLSCREEN_MASK;  
    window->flags |= flags;  


    SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window));  


return 0;  
}

從代碼中可以看出,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) == 0) {  
                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 = 0;  
        rect.top = 0;  
        rect.right = window->windowed.w;  
        rect.bottom = window->windowed.h;  
        menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);  
        AdjustWindowRectEx(&rect, style, menu, 0);  
        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, 0, 0);  
}

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 == '0') {  
            flags &= ~SDL_RENDERER_PRESENTVSYNC;  
        } else {  
            flags |= SDL_RENDERER_PRESENTVSYNC;  
        }  
    }  


if (index < 0) {  
        hint = SDL_GetHint(SDL_HINT_RENDER_DRIVER);  
if (hint) {  
for (index = 0; index < n; ++index) {  
const SDL_RenderDriver *driver = render_drivers[index];  


if (SDL_strcasecmp(hint, driver->info.name) == 0) {  
/* Create a new renderer instance */
                    renderer = driver->CreateRenderer(window, flags);  
break;  
                }  
            }  
        }  


if (!renderer) {  
for (index = 0; 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() - 1);  
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[16]; /**< 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),  
     1,  
     {SDL_PIXELFORMAT_ARGB8888},  
     0,  
     0}  
};

可以看出創建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),  
     1,  
     {SDL_PIXELFORMAT_ARGB8888},  
     0,  
     0}  
};

可以看出創建OpenGL渲染器的函數是GL_CreateRenderer()。
軟件渲染器驅動程序SW_RenderDriver的定義如下(位於render\software\SDL_render_sw.c)。

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

可以看出創建軟件渲染器的函數是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(1, sizeof(*renderer));  
if (!renderer) {  
        SDL_OutOfMemory();  
return NULL;  
    }  


    data = (D3D_RenderData *) SDL_calloc(1, 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 = 1;  
    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 = 0;  
        } else {  
            pparams.Windowed = FALSE;  
            pparams.FullScreen_RefreshRateInHz = fullscreen_mode.refresh_rate;  
        }  
    } else {  
        pparams.Windowed = TRUE;  
        pparams.FullScreen_RefreshRateInHz = 0;  
    }  
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, 0, &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 >= 2) {  
        renderer->info.flags |= SDL_RENDERER_TARGETTEXTURE;  
    }  


if (caps.PrimitiveMiscCaps & D3DPMISCCAPS_SEPARATEALPHABLEND) {  
        data->enableSeparateAlphaBlend = SDL_TRUE;  
    }  


/* Store the default render target */
    IDirect3DDevice9_GetRenderTarget(data->device, 0, &data->defaultRenderTarget );  
    data->currentRenderTarget = NULL;  


/* Set up parameters for rendering */
    D3D_InitRenderState(data);  


if (caps.MaxSimultaneousTextures >= 3)  
    {  
#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 = 0;  
        result = D3DXAssembleShader(shader_text, SDL_strlen(shader_text), NULL, NULL, 0, &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, 0, D3DTSS_COLOROP,  
                                          D3DTOP_MODULATE);  
    IDirect3DDevice9_SetTextureStageState(device, 0, D3DTSS_COLORARG1,  
                                          D3DTA_TEXTURE);  
    IDirect3DDevice9_SetTextureStageState(device, 0, D3DTSS_COLORARG2,  
                                          D3DTA_DIFFUSE);  


/* Enable alpha modulation by diffuse alpha */
    IDirect3DDevice9_SetTextureStageState(device, 0, D3DTSS_ALPHAOP,  
                                          D3DTOP_MODULATE);  
    IDirect3DDevice9_SetTextureStageState(device, 0, D3DTSS_ALPHAARG1,  
                                          D3DTA_TEXTURE);  
    IDirect3DDevice9_SetTextureStageState(device, 0, 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, 1, D3DTSS_COLOROP,  
                                          D3DTOP_DISABLE);  
    IDirect3DDevice9_SetTextureStageState(device, 1, D3DTSS_ALPHAOP,  
                                          D3DTOP_DISABLE);  


/* Set an identity world and view matrix */
    matrix.m[0][0] = 1.0f;  
    matrix.m[0][1] = 0.0f;  
    matrix.m[0][2] = 0.0f;  
    matrix.m[0][3] = 0.0f;  
    matrix.m[1][0] = 0.0f;  
    matrix.m[1][1] = 1.0f;  
    matrix.m[1][2] = 0.0f;  
    matrix.m[1][3] = 0.0f;  
    matrix.m[2][0] = 0.0f;  
    matrix.m[2][1] = 0.0f;  
    matrix.m[2][2] = 1.0f;  
    matrix.m[2][3] = 0.0f;  
    matrix.m[3][0] = 0.0f;  
    matrix.m[3][1] = 0.0f;  
    matrix.m[3][2] = 0.0f;  
    matrix.m[3][3] = 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, 0);  
        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) < 0) {  
/* 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(1, sizeof(*renderer));  
if (!renderer) {  
        SDL_OutOfMemory();  
return NULL;  
    }  


    data = (GL_RenderData *) SDL_calloc(1, 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) < 0) {  
        GL_DestroyRenderer(renderer);  
return NULL;  
    }  


if (GL_LoadFunctions(data) < 0) {  
        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(1);  
    } else {  
        SDL_GL_SetSwapInterval(0);  
    }  
if (SDL_GL_GetSwapInterval() > 0) {  
        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;  
    }  


/* Check for debug output support */
if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &value) == 0 &&  
        (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 != '0') {  
        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 >= 3) {  
        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(1, 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 = 0; 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 = 4;  
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][0])) {  
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][1])) {  
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 = 0; i < num_tmus_bound; ++i) {  
char tex_name[10];  
        SDL_snprintf(tex_name, SDL_arraysize(tex_name), "tex%d", i);  
        location = ctx->glGetUniformLocationARB(data->program, tex_name);  
if (location >= 0) {  
            ctx->glUniform1iARB(location, i);  
        }  
    }  
    ctx->glUseProgramObjectARB(0);  


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[2];  


    sources[0] = defines;  
    sources[1] = source;  


    ctx->glShaderSourceARB(shader, SDL_arraysize(sources), sources, NULL);  
    ctx->glCompileShaderARB(shader);  
    ctx->glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status);  
if (status == 0) {  
        GLint length;  
char *info;  


        ctx->glGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length);  
        info = SDL_stack_alloc(char, length+1);  
        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][2] =  
{  
/* 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) < 0) {  
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) + 256 * sizeof (RGBQUAD);  
    info = (LPBITMAPINFO)SDL_stack_alloc(Uint8, size);  


    SDL_memset(info, 0, size);  
    info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);  


/* The second call to GetDIBits() fills in the bitfields */
    hbm = CreateCompatibleBitmap(data->hdc, 1, 1);  
    GetDIBits(data->hdc, hbm, 0, 0, NULL, info, DIB_RGB_COLORS);  
    GetDIBits(data->hdc, hbm, 0, 0, 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[0], masks[1], masks[2], 0);  
    }  
if (*format == SDL_PIXELFORMAT_UNKNOWN)  
    {  
/* We'll use RGB format for now */
        *format = SDL_PIXELFORMAT_RGB888;  


/* Create a new one */
        SDL_memset(info, 0, size);  
        info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);  
        info->bmiHeader.biPlanes = 1;  
        info->bmiHeader.biBitCount = 32;  
        info->bmiHeader.biCompression = BI_RGB;  
    }  


/* Fill in the size information */
    *pitch = (((window->w * SDL_BYTESPERPIXEL(*format)) + 3) & ~3);  
    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, 0);  
    SDL_stack_free(info);  


if (!data->hbm) {  
return WIN_SetError("Unable to create DIB");  
    }  
    SelectObject(data->mdc, data->hbm);  


return 0;  
}

從代碼中可以看出,該函數調用了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(1, sizeof(*renderer));  
if (!renderer) {  
        SDL_OutOfMemory();  
return NULL;  
    }  


    data = (SW_RenderData *) SDL_calloc(1, 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[0];  
    }  
if (SDL_ISPIXELFORMAT_INDEXED(format)) {  
        SDL_SetError("Palettized textures are not supported");  
return NULL;  
    }  
if (w <= 0 || h <= 0) {  
        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(1, 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 = 255;  
    texture->g = 255;  
    texture->b = 255;  
    texture->a = 255;  
    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) < 0) {  
            SDL_DestroyTexture(texture);  
return 0;  
        }  
    } 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)) + 3) & ~3);  
            texture->pixels = SDL_calloc(1, 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(1, 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 = 0;  
    }  


    result =  
        IDirect3DDevice9_CreateTexture(renderdata->device, texture->w,  
                                       texture->h, 1, 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 / 2,  
                                           texture->h / 2, 1, usage,  
                                           PixelFormatToD3DFMT(texture->format),  
                                           pool, &data->utexture, NULL);  
if (FAILED(result)) {  
return D3D_SetError("CreateTexture()", result);  
        }  


        result =  
            IDirect3DDevice9_CreateTexture(renderdata->device, texture->w / 2,  
                                           texture->h / 2, 1, usage,  
                                           PixelFormatToD3DFMT(texture->format),  
                                           pool, &data->vtexture, NULL);  
if (FAILED(result)) {  
return D3D_SetError("CreateTexture()", result);  
        }  
    }  


return 0;  
}

從代碼中可以看出,該函數調用了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(1, 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 += (2 * (texture->h * data->pitch) / 4);  
        }  
        data->pixels = SDL_calloc(1, 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(1, &data->texture);  
if (GL_CheckError("glGenTexures()", renderer) < 0) {  
        SDL_free(data);  
return -1;  
    }  
    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 % 8) == 0) {  
        renderdata->glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);  
        renderdata->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);  
        renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH,  
                          (data->pitch / SDL_BYTESPERPIXEL(texture->format)));  
        renderdata->glTexImage2D(data->type, 0, internalFormat, texture_w,  
                                 texture_h, 0, format, type, data->pixels);  
        renderdata->glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE);  
    }  
else
#endif
    {  
        renderdata->glTexImage2D(data->type, 0, internalFormat, texture_w,  
                                 texture_h, 0, format, type, NULL);  
    }  
    renderdata->glDisable(data->type);  
if (GL_CheckError("glTexImage2D()", renderer) < 0) {  
return -1;  
    }  


if (texture->format == SDL_PIXELFORMAT_YV12 ||  
        texture->format == SDL_PIXELFORMAT_IYUV) {  
        data->yuv = SDL_TRUE;  


        renderdata->glGenTextures(1, &data->utexture);  
        renderdata->glGenTextures(1, &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, 0, internalFormat, texture_w/2,  
                                 texture_h/2, 0, 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, 0, internalFormat, texture_w/2,  
                                 texture_h/2, 0, 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(0, 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, 1);  
    }  


if (!texture->driverdata) {  
return -1;  
    }  
return 0;  
}

該函數的源代碼還沒有詳細分析。可以看出其中調用了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, -1);  


if (!pixels) {  
return SDL_InvalidParamError("pixels");  
    }  
if (!pitch) {  
return SDL_InvalidParamError("pitch");  
    }  


if (!rect) {  
        full_rect.x = 0;  
        full_rect.y = 0;  
        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 == 0 && rect->y == 0 &&  
        rect->w == texture->w && rect->h == texture->h) {  
        full_texture = SDL_TRUE;  
    }  
#endif


if (!data) {  
        SDL_SetError("Texture is not currently available");  
return -1;  
    }  


if (D3D_UpdateTextureInternal(data->texture, texture->format, full_texture, rect->x, rect->y, rect->w, rect->h, pixels, pitch) < 0) {  
return -1;  
    }  


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 / 2, rect->y / 2, rect->w / 2, rect->h / 2, pixels, pitch / 2) < 0) {  
return -1;  
        }  


/* Skip to the correct offset into the next texture */
        pixels = (const void*)((const Uint8*)pixels + (rect->h * pitch)/4);  
if (D3D_UpdateTextureInternal(texture->format == SDL_PIXELFORMAT_YV12 ? data->utexture : data->vtexture, texture->format, full_texture, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, pixels, pitch / 2) < 0) {  
return -1;  
        }  
    }  
return 0;  
}

從代碼中可以看出,該函數調用了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, 0, &locked, NULL, D3DLOCK_DISCARD);  
    } else {  
        d3drect.left = x;  
        d3drect.right = x + w;  
        d3drect.top = y;  
        d3drect.bottom = y + h;  
        result = IDirect3DTexture9_LockRect(texture, 0, &locked, &d3drect, 0);  
    }  


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 = 0; row < h; ++row) {  
            SDL_memcpy(dst, src, length);  
            src += pitch;  
            dst += locked.Pitch;  
        }  
    }  
    IDirect3DTexture9_UnlockRect(texture, 0);  


return 0;  
}

從代碼中可以看出,該函數首先調用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, 1);  
    renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH,  
                              (pitch / SDL_BYTESPERPIXEL(texture->format)));  
    renderdata->glTexSubImage2D(data->type, 0, rect->x, rect->y, rect->w,  
                                rect->h, data->format, data->formattype,  
                                pixels);  
if (data->yuv) {  
        renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH, (pitch / 2));  


/* 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, 0, rect->x/2, rect->y/2,  
                                    rect->w/2, rect->h/2,  
                                    data->format, data->formattype, pixels);  


/* Skip to the correct offset into the next texture */
        pixels = (const void*)((const Uint8*)pixels + (rect->h * pitch)/4);  
if (texture->format == SDL_PIXELFORMAT_YV12) {  
            renderdata->glBindTexture(data->type, data->utexture);  
        } else {  
            renderdata->glBindTexture(data->type, data->vtexture);  
        }  
        renderdata->glTexSubImage2D(data->type, 0, rect->x/2, rect->y/2,  
                                    rect->w/2, rect->h/2,  
                                    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 = 0; row < rect->h; ++row) {  
        SDL_memcpy(dst, src, length);  
        src += pitch;  
        dst += surface->pitch;  
    }  
if(SDL_MUSTLOCK(surface))  
        SDL_UnlockSurface(surface);  
return 0;  
}

該函數的源代碼還沒有詳細分析。其中最關鍵的函數要數SDL_memcpy()了,正是這個函數更新了紋理的像素數據。但是Software渲染器紋理修改的時候是否需要Lock()和Unlock()呢?這一點一直也沒太搞清。

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

上篇文章分析了該流程中的第5個函數SDL_UpdateTexture()。本文繼續分析該流程中的第6個函數SDL_RenderCopy()。

SDL_RenderCopy()

函數簡介

SDL使用SDL_RenderCopy()將紋理數據復制給渲染目標。SDL_RenderCopy()的原型如下。

[cpp] view plaincopy在CODE上查看代碼片派生到我的代碼片

  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 = { 0, 0, 0, 0 };  
    SDL_Rect real_dstrect = { 0, 0, 0, 0 };  
    SDL_FRect frect;  


    CHECK_RENDERER_MAGIC(renderer, -1);  
    CHECK_TEXTURE_MAGIC(texture, -1);  


if (renderer != texture->renderer) {  
return SDL_SetError("Texture was not created with this renderer");  
    }  


    real_srcrect.x = 0;  
    real_srcrect.y = 0;  
    real_srcrect.w = texture->w;  
    real_srcrect.h = texture->h;  
if (srcrect) {  
if (!SDL_IntersectRect(srcrect, &real_srcrect, &real_srcrect)) {  
return 0;  
        }  
    }  


    SDL_RenderGetViewport(renderer, &real_dstrect);  
    real_dstrect.x = 0;  
    real_dstrect.y = 0;  
if (dstrect) {  
if (!SDL_HasIntersection(dstrect, &real_dstrect)) {  
return 0;  
        }  
        real_dstrect = *dstrect;  
    }  


if (texture->native) {  
        texture = texture->native;  
    }  


/* Don't draw while we're hidden */
if (renderer->hidden) {  
return 0;  
    }  


    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[4];  
HRESULT result;  


if (D3D_ActivateRenderer(renderer) < 0) {  
return -1;  
    }  


    texturedata = (D3D_TextureData *)texture->driverdata;  
if (!texturedata) {  
        SDL_SetError("Texture is not currently available");  
return -1;  
    }  


    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[0].x = minx;  
    vertices[0].y = miny;  
    vertices[0].z = 0.0f;  
    vertices[0].color = color;  
    vertices[0].u = minu;  
    vertices[0].v = minv;  


    vertices[1].x = maxx;  
    vertices[1].y = miny;  
    vertices[1].z = 0.0f;  
    vertices[1].color = color;  
    vertices[1].u = maxu;  
    vertices[1].v = minv;  


    vertices[2].x = maxx;  
    vertices[2].y = maxy;  
    vertices[2].z = 0.0f;  
    vertices[2].color = color;  
    vertices[2].u = maxu;  
    vertices[2].v = maxv;  


    vertices[3].x = minx;  
    vertices[3].y = maxy;  
    vertices[3].z = 0.0f;  
    vertices[3].color = color;  
    vertices[3].u = minu;  
    vertices[3].v = maxv;  


    D3D_SetBlendMode(data, texture->blendMode);  


    D3D_UpdateTextureScaleMode(data, texturedata, 0);  


    result =  
        IDirect3DDevice9_SetTexture(data->device, 0, (IDirect3DBaseTexture9 *)  
                                    texturedata->texture);  
if (FAILED(result)) {  
return D3D_SetError("SetTexture()", result);  
    }  


if (texturedata->yuv) {  
        shader = data->ps_yuv;  


        D3D_UpdateTextureScaleMode(data, texturedata, 1);  
        D3D_UpdateTextureScaleMode(data, texturedata, 2);  


        result =  
            IDirect3DDevice9_SetTexture(data->device, 1, (IDirect3DBaseTexture9 *)  
                                        texturedata->utexture);  
if (FAILED(result)) {  
return D3D_SetError("SetTexture()", result);  
        }  


        result =  
            IDirect3DDevice9_SetTexture(data->device, 2, (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, 2,  
                                         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 0;  
}

從代碼中可以看出,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) < 0) {  
return -1;  
        }  


        data->updateSize = SDL_FALSE;  
    }  
if (data->beginScene) {  
        result = IDirect3DDevice9_BeginScene(data->device);  
if (result == D3DERR_DEVICELOST) {  
if (D3D_Reset(renderer) < 0) {  
return -1;  
            }  
            result = IDirect3DDevice9_BeginScene(data->device);  
        }  
if (FAILED(result)) {  
return D3D_SetError("BeginScene()", result);  
        }  
        data->beginScene = SDL_FALSE;  
    }  
return 0;  
}

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, 255, 255, 255, 255);  
    }  


    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 -1;  
    }  


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在CODE上查看代碼片派生到我的代碼片

  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, -1);  


    full_rect.x = 0;  
    full_rect.y = 0;  
    full_rect.w = window->w;  
    full_rect.h = window->h;  
return SDL_UpdateWindowSurfaceRects(window, &full_rect, 1);  
}

SDL_UpdateWindowSurface()又調用了另一個函數SDL_UpdateWindowSurfaceRects()。繼續看SDL_UpdateWindowSurfaceRects()的代碼。

int SDL_UpdateWindowSurfaceRects(SDL_Window * window, const SDL_Rect * rects,  
int numrects)  
{  
    CHECK_WINDOW_MAGIC(window, -1);  


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, 0, 0, window->w, window->h, data->mdc, 0, 0, SRCCOPY);  
return 0;  
}

經過一系列的尋找之后,終於找到了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()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM