SDL2の初期化処理、ウィンドウ作成、描画処理

SDL_Window構造体でウィンドウを作成し、SDL_Renderer構造体経由で描画処理を実行します。

 

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

 

本プログラムを実行すると以下のウィンドウが表示されます。

https://dl-web.dropbox.com/s/27u7nkle20y7d8c/0001_SampleProram.png