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;
}
本プログラムを実行すると以下のウィンドウが表示されます。