SDL2のスレッド処理

SDL_Thread構造体でスレッド処理を実行します。

 

1 SDL_Thread構造体

SDL_Thread構造体はスレッドの生成、停止、終了待ちを実現する為に使用される構造体です。

本構造体のメンバ変数は隠蔽されている為、SDL_Thread構造体を扱うAPI経由でしかアクセスできません。

struct SDL_Thread;
typedef struct SDL_Thread SDL_Thread;

1.1 SDL_GetThreadID

SDL_GetThreadIDでスレッドIDを取得します。引数threadにnullptrを設定した場合は本関数を呼び出したスレッドのスレッドIDを取得します。

SDL_threadID SDL_GetThreadID(SDL_Thread* thread)

同様のAPIにSDL_ThreadIDがあり、SDL_GetThreadIDにnullptrを設定した場合と同様に、本関数を呼び出したスレッドのスレッドIDを取得します。

SDL_threadID SDL_ThreadID(void)

ミューテックス等の排他制御を実行するマルチスレッドなプログラムは、ミューテックスに対するロックの所有者を確かめ、所有者が自分である場合はアンロックしてからスリープする等の動作が必要になります。よって、SDL_ThreadID等でロック所有者のスレッドIDを保存しておけば良いことになります。


1.2 SDL_CreateThread

SDL_CreateThreadでスレッドを生成します。

SDL_Thread* SDL_CreateThread(SDL_ThreadFunction fn,
                             const char*        name,
                             void*              data)

SDL_ThreadFunctionは以下の通りにtypedefされており、スレッドで実行する関数を指定します。dataを利用することで、SDL_ThreadFunctionの関数引数にデータを渡すことができます。

typedef int (SDLCALL * SDL_ThreadFunction) (void *data);

1.3 SDL_DetachThread

SDL_DetachThreadでスレッドを強制終了させます。

void SDL_DetachThread(SDL_Thread* thread)

SDL_ThreadFunctionで指定した関数が終了する前に強制的に終了させる為、SDL_ThreadFunctionで必要な処理が実行されない場合があります。


1.4 SDL_WaitThread

SDL_WaitThreadでスレッドが終了するのを待ちます(SDL_CreateThreadのSDL_ThreadFunctionで指定した関数が終了するまで待ちます)。

void SDL_WaitThread(SDL_Thread* thread,
                    int*        status)

statusにSDL_ThreadFunctionの戻り値が格納されます。SDL_ThreadFunctionで指定した関数は終了するように設計すべきです。


1.5 SDL_DetachThreadとSDL_WaitThreadのどちらを使うべきか

SDL2ではSDL_RendererのAPIは同一のスレッドで扱う必要があり、必然的にレンダリングスレッドにおいて、SDL_Rendererの初期化処理、描画処理、終了処理を実行する必要があります。


例えば以下のような処理を実行するレンダリングスレッドにおいて、2.の「SDL_QUITが発生するまでSDL_Rendererの描画処理のループ」を実行してる時に、SDL_DetachThreadでレンダリングスレッドを強制終了させると3.の「SDL_Rendererの終了処理」が実行されません。

1. SDL_Rendererの初期化処理
2. SDL_QUITが発生するまでSDL_Rendererの描画処理のループ
3. SDL_Rendererの終了処理

よって上記のレンダリングスレッドの処理では、2.の「SDL_QUITが発生するまでSDL_Rendererの描画処理のループ」のループを中断するうにしてから、SDL_WaitThreadを使用すべきです。

SDL_DetachThreadは終了処理が必要のないスレッドで使用すると良いでしょう。ループ中断等のコードが必要なくなります。


終了処理が必要な場合はSDL_WaitThreadを(ただし、コードのどの部分でも中断ができるようにする必要が有る)、終了処理が必要ない場合はSDL_DetachThreadを(中断の必要はない)使用すれば良いでしょう。


2 サンプルプログラム

次の動作を実行するサンプルプログラムです。

1. レンダリングスレッドを生成する。
2. レンダリングスレッドにて白い四角形を描画する。
3. メインループにてイベント処理を実行し、矢印キーに合わせて白い四角形の描画位置を変更する。
4. ウィンドウのxボタンが押された場合はプログラムを終了する。
5. レンダリングスレッドの描画回数が1024回を超えたらプログラムを終了する。
#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_RENDERER_TIME (1024)
#define SDL_DELAY_VALUE (10)

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

/** NOTE: Draw rect position. */
static int gRectX = 0;
static int gRectY = 0;

/** NOTE: Quit flag for multi-thread. */
static bool gQuit = false;

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

  return true;

 err1:
  SDL_Quit();
  return false;
}

static void finalize()
{
  SDL_DestroyWindow(gWindow);
  SDL_Quit();
}

static void render()
{
  /** 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 = { gRectX, gRectY, 100, 100 };
  SDL_SetRenderDrawColor(gRenderer, 254, 254, 254, 254);
  SDL_RenderFillRect(gRenderer, &rect);

  /** NOTE: Update drawing (switch double buffer). */
  SDL_RenderPresent(gRenderer);

  /** NOTE: Sleep 10 msec. */
  SDL_Delay(10);
}

static bool input()
{
  SDL_Event event;

  /** NOTE: SDL_PollEvent does not sleep while SDL_WaitEvent sleep
      till event comes. SDL_WaitEvent is more relaxible than
      SDL_PollEvent. If input is combined with draw, SDL_WaitEvent
      cannot be used. */
  while (SDL_PollEvent(&event)) {
    switch (event.type) {
    case SDL_QUIT:
      return true;
    case SDL_KEYDOWN:
      switch (event.key.keysym.sym) {
      case SDLK_UP:
        gRectY--;
        break;
      case SDLK_RIGHT:
        gRectX++;
        break;
      case SDLK_DOWN:
        gRectY++;
        break;
      case SDLK_LEFT:
        gRectX--;
        break;
      default:
        break;
      }
      break;
    default:
      break;
    }
  }

  return false;
}

static int renderThread(void *)
{
  gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_FLAGS);
  if (gRenderer == nullptr) {
    SDL_PrintError(SDL_CreateRenderer);
    return -1;
  }

  for (int i = 0; i < SDL_RENDERER_TIME; ++i) {
    if (gQuit) {
      SDL_DestroyRenderer(gRenderer);
      return 1;
    }
    render();
    SDL_Delay(SDL_DELAY_VALUE);
  }

  SDL_DestroyRenderer(gRenderer);

  SDL_Event event;
  event.type = SDL_QUIT;
  SDL_PushEvent(&event);
  return 0;
}

int main(int argc, char *argv[])
{
  SDL_Thread *thread;
  int ret;

  if (!initialize())
    return 1;

  thread = SDL_CreateThread(renderThread, "Render", nullptr);
  if (thread == nullptr) {
    SDL_PrintError(SDL_CreateThread);
    goto err1;
  }

  while (!gQuit)
    if (input())
      gQuit = true;

  SDL_WaitThread(thread, &ret);
  std::cout << "ret = " << ret << std::endl;

  finalize();
  return 0;

 err1:
  finalize();
  return 1;
}