SDL全名Simple DirectMedia Layer,是一個跨平台的底層音頻、視頻、鍵盤、鼠標操作庫,操作實際通過更底層的OpenGL/Direct3D完成,在保留跨平台的兼容性之外提供了非常高的效率,所以廣泛的應用在多種游戲和對速度敏感的應用中,比如鼎鼎大名的steam平台/ffmpeg/qemu/模擬器等,當前的版本是2.0。更詳細的資料可以訪問官網:https://www.libsdl.org/。
SDL2的編程理念清晰易用,代碼簡潔高效,這里用顯式一副圖片的最簡代碼來作為入門的示例,正式的教學可以搜索很多國內的教學網站。
老辦法,讓代碼自己來說話:
#include <stdio.h>
//引入SDL頭文件
#include <SDL.h>
//顯式bmp之外的圖片需要用到sdl_image庫,需要單獨引入頭文件
#include <SDL_image.h>
#define bool int
#define false 0
#define true (!false)
int main(int argc, char ** argv)
{
bool quit = false;
SDL_Event event;
//SDL初始化,這里只顯示圖片,所以只初始化VIDEO系統,更多的支持查看官方文檔
SDL_Init(SDL_INIT_VIDEO);
//為了顯示png圖片,額外使用了圖片庫,所以要單獨初始化
IMG_Init(IMG_INIT_JPG);
//建立SDL窗口
SDL_Window * window = SDL_CreateWindow("SDL2 Displaying Image",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
//渲染層
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);
//如果只是顯示一張bmp圖片,使用sdl內置的功能即可
//SDL_Surface * image = SDL_LoadBMP("only_support_BMP.bmp");
//因為要顯示png圖片,所以使用了外部庫,sdl_image庫當前支持jpg/png/webp/tiff圖片格式
SDL_Surface * image = IMG_Load("/Users/andrew/Downloads/webFavorite/3481980_orig.png");
//載入的圖片生成SDL貼圖材質
SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, image);
while (!quit)
{//主消息循環
SDL_WaitEvent(&event);
switch (event.type)
{ //用戶從菜單要求退出程序
case SDL_QUIT:
quit = true;
break;
}
//如果指定顯示位置使用下面注釋起來的兩句
//SDL_Rect dstrect = { 5, 5, 320, 240 };
//SDL_RenderCopy(renderer, texture, NULL, &dstrect);
//把貼圖材質復制到渲染器
SDL_RenderCopy(renderer, texture, NULL, NULL);
//顯示出來
SDL_RenderPresent(renderer);
}
//典型的三明治結構,清理各項資源
SDL_DestroyTexture(texture);
SDL_FreeSurface(image);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
//退出image庫
IMG_Quit();
//退出SDL
SDL_Quit();
return 0;
}
代碼中基本算是逐句注釋,所以讀起來應當很清晰。主要需要說明的有兩點,但其實跟這段代碼並沒有直接關系,而是有關在眾多的繪圖技術、架構、方案中,SDL處於一個什么位置:
1.首先是繪圖哲學,使用過OpenGL及Direct3D的看這些代碼應當不陌生,但這里要單獨給傳統GUI繪圖的同學多說兩句。通常使用GUI繪圖,大概是這樣一個邏輯,請看偽代碼:
准備畫板();
畫一個點(x,y);
畫一條線(x1,y1,x2,y2,c);
畫一個圓(x,y,r,c);
貼一張圖(x,y,w,h,bmp);
結束繪圖();
在偽代碼的過程中,每執行一條命令,比如畫了線,在屏幕上就會看到結果,然后那條線也會一直存在,直到程序清掉它或者其它屏幕元素遮住它。
而SDL所使用的模式用偽代碼表示大致是這樣的邏輯:
准備工作();
主循環 {
游戲邏輯處理();
界面元素1進場();
界面元素2進場();
界面元素n進場();
渲染();
是否退出判斷();
}
比較主要的區別是,每一個界面元素,或者說屏上的元素進場,並非真正的繪圖,只是像拍電影一樣准備一個場景。等到所有屏幕元素都到齊,場景完全准備好,再一次性渲染,這時候是真正的繪制到屏幕上。更形象的比喻就好像演員都准備好了,相機快門按下,才真正成像。這個成像稱為一幀,隨后循環起來,一次次的准備好場景、渲染成像,就形成了連續不斷的幀從而形成了幀動畫,也就是我們熟悉的屏幕游戲畫面。這里面每一秒鍾能夠進行多少次循環,就成為了游戲玩家熟悉的幀率,追求高幀率是大多游戲玩家對電腦的要求。
這兩種繪圖的方式,各有優劣,但依據特征,有不同的應用方向。前者多用於打印、繪圖輸出相關的辦公、平面設計等場合,傳統軟件的界面也多用這種方式,還有比如我們都熟悉的上網瀏覽器頁面也是采用這種渲染方式。這種方式對速度不敏感,雖然有可能硬件加速,但實際上大多工作是由CPU完成的。
后者也就是SDL所采用的方式,則在游戲、視頻、3D動畫、VR、AR等領域大放異彩,我們耳熟能詳的OpenGL、Direct3D也都采用這種方式,這種方式的流程邏輯,也更適合把大量的數據和素材交給GPU去完成更耗時的計算。
顯而易見,從繪圖哲學的角度看,SDL/OpenGL/Direct3D所采用的繪圖方式,顯然更適合3D類繪圖、動畫的加速,那么這種技術對平面繪圖,比如就是單純的視頻播放,是如何加速的呢?其實很簡單,我們知道所有的3D繪圖都包括至少兩個主要的部分,一是3D物體的構造模型,比如是一個球體還是一個圓柱體;另一部分則是這個3D物體表面看起來是什么樣子的,比如是一個石膏的球體還是一個毛絨玩具的球體。這第二部分就需要用到材質,材質實際上主要是由三維物體的表面積在二維展開的圖片。所以3D繪圖對二維的加速實際上就是在屏幕上繪制一個全屏幕的平面,然后把二維圖像當做材質貼圖上去的結果。你看上面SDL代碼中載入的png圖片,實際最后就是當做一副材質(texture)來使用了。
2.SDL/OpenGL/Direct3D同GTK/MFC/QT/Cocoa是什么關系?
剛才其實比較清楚的講了SDL/OpenGL/Direct3D在繪圖上的作用,其實它們就是一套繪圖的體系。
GTK/MFC/QT/Cocoa也是顯示相關這沒錯,但是它們主要是提供用戶程序的界面管理、顯示及事件處理。更具體一點說,比如你看到屏幕上的菜單、窗口、對話框、按鈕、文字,幾乎都是這些界面管理器來實現的,我們點了一個按鈕、拖動一個窗口,都會產生事件,這些事件會由這些界面管理器收集、分類、排序,調用響應用戶響應函數做出最后的處理。所以平常我們所見的應用程序,其實都是基於這一類軟件庫完成的。而重要的是,這些界面管理庫,實際上最終也是經由OpenGL/Direct3D或者類似功能更底層一些的顯示繪圖庫來完成界面部分的繪制功能。但是這些顯示系統往往太龐大、臃腫了,對於對速度極為敏感的游戲、視頻類應用而言,通常我們見到的這些界面所占比重又比較小,所以游戲類的應用,往往不采用或者較少部分采用這些傳統的界面管理庫。
這兩類系統往往不是獨立存在的,比如舉例說一個視頻播放器,播放器的窗口界面、菜單、文件打開等界面和操作,都是由界面管理器比如Windows上的MFC或者Mac上的Cocoa來完成的,到真正視頻播放的環節,在窗口中給定的區域,則是由SDL、OpenGL、Direct3D出馬,完成視頻的逐幀繪制的功能。
回到今天的主題。上面的代碼在編譯的時候,因為使用了SDL2/SDL_image兩個額外的附加庫,所以在編譯、執行代碼之前,首先要安裝這兩個軟件庫。在mac電腦上安裝這兩個庫的命令是:brew install sdl2 sdl2_image
。
編譯代碼使用:
gcc -o sdlpng sdlpng.c $(pkg-config --cflags --libs sdl2_image)
后面$(pkg-config --cflags --libs sdl2_image)
的意思是,將sdl2_image代碼庫及其依賴庫(這里當然就是sdl2庫)的編譯參數和引用庫參數全部顯示出來,作為字符串加入到編譯命令中去。這個功能是由pkg-config這個包管理器完成的。如果不需要處理png圖片,只是bmp圖片,則不需要使用sdl2_image庫,僅適用sdl2庫即可。這個時候可以使用$(pkg-config --cflags --libs sdl2)
。sdl2也提供了自己的包參數工具sdl2-config可以完成類似的功能,但僅對自己有效,所以為了通用起見,我們還是使用pkg-config更方便一些。
談到附加包的編譯參數,我們也經常看到一些教科書上寫成類似:`pkg-config --cflags --libs sdl2`
這樣的形式,這是因為在bash下面,反單引號`
就是用來執行命令、並將結果當做字符串返回的功能。但是這種方式在別的shell,比如fish中是不起作用的,但是$( ... )
這樣的方式就有了更好的通用性。
參考鏈接:
http://gigi.nullneuron.net/gigilabs/loading-images-in-sdl2-with-sdl_image/