SDL_Window構造体でウィンドウを作成し、SDL_Renderer構造体経由で描画処理を実行します。
Table of Contents
1 SDL_Init
SDL_InitでSDLを初期化します。
int SDL_Init(Uint32 flags)
flagsにシステムに対応した値を設定することで、システムを初期化します。値にはSDL_INIT_VIDEO、SDL_INIT_EVENTS等があります。
SDL_INIT_VIDEOで初期化することで描画処理が実行可能になります。SDL_INIT_EVENTSで初期化することで入力処理が実行可能となります。
全てのシステムを有効にするSDL_INIT_EVERYTHINGがあり、通常はこちらを用いれば良いと思います。
もし、初期化を段階的に分けたい場合は、初期化処理の1つ目はSDL_Initを呼び出し、初期化処理の2つ目以降はSDL_InitSubSystemを呼び出します。
2 SDL_Quit
SDL_Quitで初期化したシステムを終了します。
void SDL_Quit(void)
段階的にシステムを終了する場合はSDL_QuitSubSystemを用います。通常はSDL_Quitのみを用いれば良いでしょう。
3 SDL_Window構造体
ウィンドウを管理する構造体です。
3.1 SDL_CreateWindow
SDL_CreateWindowでウィンドウを作成します。
SDL_Window* SDL_CreateWindow(const char* title, int x, int y, int w, int h, Uint32 flags)
開始位置の座標x, yにはSDL_WINDOWPOS_UNDEFINEDを指定でき、開始位置をシステム任せにすることが可能です。flagsにはサイズ変更可能かどうか、フルスクリーンにするかどうか等のフラグを設定します。作成するアプリケーションに合わせてフラグを設定することになります。
SDL_CreateWindowでSDL_Window構造体を作成後、終了処理まではSDL_Window構造体が必要になることはないでしょう。
3.2 SDL_DestroyWindow
SDL_DestroyWindowでウィンドウを破棄します。
void SDL_DestroyWindow(SDL_Window* window)
4 SDL_Renderer構造体
SDL_Renderer構造体はSDL2から追加された構造体で、描画処理を担うものです。
SDLでは以前からSDL_Window構造体が持つSDL_Surface構造体に対して描画対象をコピーすることで描画処理を実現していました(BMP画像のSDL_Surface構造体を作り、SDL_Window構造体のSDL_Surface構造体にコピーする)。
SDL2からSDL_Surface構造体の代わりにSDL_Texture構造体が描画処理に使われるようになりました。大きな違いはSDL_Surface構造体はメンバ変数を外部からアクセスできるのに対し、SDL_Texture構造体はアクセスできません。
これはSDL_Texture構造体のメンバ変数の振る舞いはSDLライブラリのコンパイル時に決定し(例えばSDL2を搭載したシステムがOpenGLを使ってる場合とDirectx使っている場合で扱いが異なる)、システムに合わせて最適化する為です。
よって、外部からメンバにアクセスすると不整合が起きる為、外部にメンバ変数を公開しておりません。逆に公開していた場合は、アプリケーション実行時(コンパイル時ではない)にSDL2ライブラリの状況を詳しく調べる必要が出てしまい、コードが増加してしまいます。システムがOpenGLを使おうが、Directxを使っていようが、同じソースコードでアプリケーションを構築可能というSDLの利点と矛盾します。
SDL_Renderer構造体はSDL_Texture構造体に対応したものです。SDL2からは、BMP画像のSDL_Texture構造体を作成し、SDL_Renderer構造体にコピーすることで描画を実現します。
4.1 SDL_CreateRenderer
SDL_CreateRendererにSDL_Window構造体を渡すことでSDL_Renderer構造体を作成します。
SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, int index, Uint32 flags)
SDL_CreateWindowAndRendererを用いれば、SDL_Window構造体とSDL_Renderer構造体を同時に作成できます。
int SDL_CreateWindowAndRenderer(int width, int height, Uint32 window_flags, SDL_Window** window, SDL_Renderer** renderer)
ただし、SDL_Renderer構造体の属性を決定するフラグはSDL_CreateRendererのみで設定可能です(サンプルソースコードではSDL_RENDERER_SOFTWAREが必要である為、SDL_CreateRendererを使用しています)。
SDL_Texture構造体を作成する際にもSDL_Renderer構造体は必要となります。 SDL_CreateTextureFromSurfaceでSDL_Surface構造体からSDL_Texture構造体を作成します。
SDL_Texture* SDL_CreateTextureFromSurface(SDL_Renderer* renderer, SDL_Surface* surface)
描画処理はSDL_Renderer構造体の内容クリア、描画対象をSDL_Renderer構造体にコピー、SDL_Renderer構造体の内容を画面に反映、という流れで実行されます。
4.2 SDL_RenderClear
SDL_RenderClearでSDL_Renderer構造体の内容をクリアします。
int SDL_RenderClear(SDL_Renderer* renderer)
4.3 SDL_RendererCopy
SDL_RendererCopyでSDL_Texture構造体をSDL_Renderer構造体にコピーし、 SDL_RenderDrawRect等で四角形等の図形をSDL_Renderer構造体にコピーします。
int SDL_RenderCopy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)
int SDL_RenderDrawRect(SDL_Renderer* renderer, const SDL_Rect* rect)
SDL_Textureの回転処理を実行するにはSDL_RenderCopyExを使います。
int SDL_RenderCopyEx(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect, const double angle, const SDL_Point* center, const SDL_RendererFlip flip)
4.4 SDL_RenderPresent
SDL_RenderPresentでSDL_Renderer構造体の内容を画面に反映します。この際、ダブルバッファを使用しているので、画面描画のちらつきは発生しません。
void SDL_RenderPresent(SDL_Renderer* renderer)
よって、SDL2の描画処理の1フレームとは、SDL_RenderClearからSDL_RenderPresentまでの処理のことを表します。
4.5 SDL_DestroyRenderer
SDL_DestroyRendererでSDL_Renderer構造体を破棄します。
void SDL_DestroyWindow(SDL_Window* window)
4.6 SDL_Renderer構造体を扱うAPI群は同一スレッドで扱う必要がある
とても重要な点として、SDL_Renderer構造体を扱うAPI群は同一のスレッドである必要があります。
SDL_CreateRendererを実行したスレッドでないと、それ以降のSDLxxxRenderxxx関数を使うことできません。たまたま使えてしまう環境もあるようなので気をつけて下さい。マルチコア環境ではうまく動かない場合が多いです。これはOpenGLの仕様に伴うものだそうです。
よって、全てのコードを単一のスレッドで実行するか、SDL_Renderer構造体を扱うレンダリングスレッドを用意する必要があります。
5 サンプルプログラム
本プログラムは640 x 360サイズのウィンドウを作成し、白色の四角形を描画します。10000ミリ秒スリープした後、プログラムを終了します。
#include <SDL.h> #include <iostream> #define SDL_WINDOW_TITLE "SDL2" #define SDL_WINDOW_WIDTH (640) #define SDL_WINDOW_HEIGHT (360) /** NOTE: Windows on KVM cannot call direct3D. Then SDL_RENDERER_SOFTWARE will be needed. */ #ifdef NEED_RENDERER_SOFTWARE #define SDL_RENDERER_FLAGS (SDL_RENDERER_SOFTWARE) #else #define SDL_RENDERER_FLAGS (0) #endif #define SDL_PrintError(name) \ do { \ std::cerr << #name << ": " << SDL_GetError() << std::endl; \ } while (0) /** NOTE: SDL_Renderer is referered with draw function like SDL_RenderDrawRect, SDL_RenderCopy and etc. for drawing. These draw functions and SDL_CreateRenderer must be called by same thread. */ static SDL_Window *gWindow; static SDL_Renderer *gRenderer; static bool initialize() { if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { SDL_PrintError(SDL_Init); return false; } gWindow = SDL_CreateWindow(SDL_WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, 0); if (gWindow == nullptr) { SDL_PrintError(SDL_CreateWindow); goto err1; } gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_FLAGS); if (gRenderer == nullptr) { SDL_PrintError(SDL_CreateRenderer); goto err2; } return true; err2: SDL_DestroyWindow(gWindow); err1: SDL_Quit(); return false; } static void finalize() { SDL_DestroyRenderer(gRenderer); SDL_DestroyWindow(gWindow); SDL_Quit(); } int main(int argc, char *argv[]) { if (!initialize()) return 1; /** NOTE: Clear render with Color (0, 0, 0, 0) for erasing current drawing. */ SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 0); SDL_RenderClear(gRenderer); /** NOTE: Draw rect (0, 0, 100, 100) with Color (254, 254, 254, 254) */ SDL_Rect rect = { 0, 0, 100, 100 }; SDL_SetRenderDrawColor(gRenderer, 254, 254, 254, 254); SDL_RenderFillRect(gRenderer, &rect); /** NOTE: Update drawing (switch double buffer). */ SDL_RenderPresent(gRenderer); /** NOTE: Sleep 10000 msec. */ SDL_Delay(10000); finalize(); return 0; }
本プログラムを実行すると以下のウィンドウが表示されます。