SDL は、いくつかのプラットフォーム (Linux/X11, Win32, BeOS, MacOS Classic/Toolbox, MacOS X, FreeBSD/X11 そして Solaris/X11) において、OpenGL コンテキストを生成し使うことができます。 これにより、あなたの OpenGL アプリケーション (しばしば GLUT によって実行される機能) において、 SDL のオーディオ・イベント処理・スレッドそしてタイマーを 使うことができます。
OpenGL を使うための SDL の初期化は、普通に SDL を初期化するのと 大きな違いはありません。 3 つの違いがあります。 SDL_SetVideoMode に SDL_OPENGL を渡す必要があり、 SDL_GL_SetAttribute を使っていくつかの GL 属性(デプスバッファのサイズ・フレームバッファのサイズ)を 指定する必要があり、 そして最後にダブルバッファを使いたいなら SDL_SetVideoMode に SDL_DOUBLEBUF フラグを渡すことによってではなく、GL アトリビュートとして指定する必要があります。
Example 2-7. OpenGL と一緒に SDL を初期化する
/* 現在のビデオ設定についての情報 */
const SDL_VideoInfo* info = NULL;
/* ウィンドウの寸法 */
int width = 0;
int height = 0;
/* ウィンドウの色深度の(ビット数表記) */
int bpp = 0;
/* SDL_SetVideoMode に渡すフラグ */
int flags = 0;
/* まず、SDL ビデオサブシステムを初期化 */
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
/* 失敗したので終了 */
fprintf( stderr, "ビデオの初期化に失敗しました: %s\n",
SDL_GetError( ) );
quit_tutorial( 1 );
}
/* いくつかのビデオ情報を取得しよう */
info = SDL_GetVideoInfo( );
if( !info ) {
/* おそらくこれは絶対に起きないはず */
fprintf( stderr, "ビデオの問い合わせに失敗しました: %s\n",
SDL_GetError( ) );
quit_tutorial( 1 );
}
/*
* 横幅/高さを 640/480 にセット。
* (もちろん普通のアプリケーションにおいて
* ユーザーにこれを決定してもらうだろう)
* 画面から要求していたピクセル深度を取得。
* X11 では、VidMode は解像度を変えることができないため、
* これはおそらく過度安全だろう。
* Win32 では、ChangeDisplaySettings によって
* ピクセル深度を変えることができる。
*/
width = 640;
height = 480;
bpp = info->vfmt->BitsPerPixel;
/*
* ここで、OpenGL ウィンドウのために要求されたウィンドウ属性を設定したい。
* RGB 各チャンネルに *少なくとも* 5 ビット欲しい。
* また、少なくとも 16 ビットのデプスバッファも欲しい。
*
* 最後にする事はダブルバッファウィンドウの要求である。
* '1' でダルバッファが有効になり、
* '0' で無効になる。
*
* SDL_SetVideoMode へのフラグにおいて
* SDL_DOUBLEBUF を使わない事に注意。
* それは GL アトリビュートに影響せず、
* 標準の 2D blit 転送の設定だけに影響する。
*/
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
/*
* SDL がフルスクリーンビデオモードにおいて
* OpenGL ウィンドウを提供してくれるよう要求したい。
*
* 練習:
* ウィンドウ状態で開始するオプションを作り、
* glViewPort でリサイズイベントを適切に処理せよ。
*/
flags = SDL_OPENGL | SDL_FULLSCREEN;
/*
* ビデオモードを設定。
*/
if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) {
/*
* これはさまざまな理由で起き得る。
* DISPLAY が設定されていない、
* 指定された解像度が利用可能でない、など。
*/
fprintf( stderr, "ビデオモードのセットに失敗しました: %s\n",
SDL_GetError( ) );
quit_tutorial( 1 );
}
初期化とは別に、 SDL の中で OpenGL を使うことは、 GLUT などの他の API と一緒に OpenGL を使うことと同じです。 すべての同じ関数とデータ型がそのまま使えます。 しかしながら、ダブルバッファ画面を使うときは、 バッファを交換し画面を更新するために SDL_GL_SwapBuffers() を使う必要があります。 OpenGL でダブルバッファを要求するためには、 SDL_GL_DOUBLEBUFFER と一緒に SDL_GL_SetAttribute とつかい、 実際に得られたかどうかを確認するために SDL_GL_GetAttribute を使います。
以下に完全なサンプルコードリストを掲載します。
Example 2-8. SDL と OpenGL
/*
* SDL OpenGL チュートリアル.
* (c) Michael Vance, 2000
* briareos@lokigames.com
*
* LGPL の条件のもとで配布されています。
*/
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <stdio.h>
#include <stdlib.h>
static GLboolean should_rotate = GL_TRUE;
static void quit_tutorial( int code )
{
/*
* SDL を終了してフルスクリーンモードを解放し、
* 以前のビデオ設定などを戻す。
*/
SDL_Quit( );
/* プログラムを終了する。*/
exit( code );
}
static void handle_key_down( SDL_keysym* keysym )
{
/*
* 興味があるのは 'ESC' が押された時だけ。
*
* 練習:
* 矢印キーを処理し、表示位置・角度を変更するようにせよ。
*/
switch( keysym->sym ) {
case SDLK_ESCAPE:
quit_tutorial( 0 );
break;
case SDLK_SPACE:
should_rotate = !should_rotate;
break;
default:
break;
}
}
static void process_events( void )
{
/* SDL イベントの置き場 */
SDL_Event event;
/* すべてのイベントをキューからつかみ取る */
while( SDL_PollEvent( &event ) ) {
switch( event.type ) {
case SDL_KEYDOWN:
/* キー押下を処理 */
handle_key_down( &event.key.keysym );
break;
case SDL_QUIT:
/* 終了要求 (Ctrl-c など) を処理 */
quit_tutorial( 0 );
break;
}
}
}
static void draw_screen( void )
{
/* 回転角 */
static float angle = 0.0f;
/*
* 練習:
* このひどいごみを頂点配列で置き換え、
* glDrawElements を呼び出せ。
*
* 練習:
* 上を終えた後、コンパイルされた頂点配列に変更せよ。
*
* 練習:
* 私の螺旋形状が正しいことを確認せよ。;)
*/
static GLfloat v0[] = { -1.0f, -1.0f, 1.0f };
static GLfloat v1[] = { 1.0f, -1.0f, 1.0f };
static GLfloat v2[] = { 1.0f, 1.0f, 1.0f };
static GLfloat v3[] = { -1.0f, 1.0f, 1.0f };
static GLfloat v4[] = { -1.0f, -1.0f, -1.0f };
static GLfloat v5[] = { 1.0f, -1.0f, -1.0f };
static GLfloat v6[] = { 1.0f, 1.0f, -1.0f };
static GLfloat v7[] = { -1.0f, 1.0f, -1.0f };
static GLubyte red[] = { 255, 0, 0, 255 };
static GLubyte green[] = { 0, 255, 0, 255 };
static GLubyte blue[] = { 0, 0, 255, 255 };
static GLubyte white[] = { 255, 255, 255, 255 };
static GLubyte yellow[] = { 0, 255, 255, 255 };
static GLubyte black[] = { 0, 0, 0, 255 };
static GLubyte orange[] = { 255, 255, 0, 255 };
static GLubyte purple[] = { 255, 0, 255, 0 };
/* 色・デプスバッファを消去 */
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
/* 射影行列は変更したくない */
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
/* z 軸の方向に下げる */
glTranslatef( 0.0, 0.0, -5.0 );
/* 回転 */
glRotatef( angle, 0.0, 1.0, 0.0 );
if( should_rotate ) {
if( ++angle > 360.0f ) {
angle = 0.0f;
}
}
/* 三角形データをパイプラインに送る */
glBegin( GL_TRIANGLES );
glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( blue );
glVertex3fv( v2 );
glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( blue );
glVertex3fv( v2 );
glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( black );
glVertex3fv( v5 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( blue );
glVertex3fv( v2 );
glColor4ubv( black );
glVertex3fv( v5 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( purple );
glVertex3fv( v7 );
glColor4ubv( black );
glVertex3fv( v5 );
glColor4ubv( purple );
glVertex3fv( v7 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( purple );
glVertex3fv( v7 );
glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( blue );
glVertex3fv( v2 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( purple );
glVertex3fv( v7 );
glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( black );
glVertex3fv( v5 );
glEnd( );
/*
* 練習:
* 'Spc' で回転停止、'Esc' で終了することを
* ユーザーに教えるテキストを描画せよ。
* ベクターとテクスチャが貼られた四角形で行え。
*/
/*
* バッファを交換する。これはバックバッファからの
* 次のフレームの描画と、
* フロントバッファであったものに起こる
* すべての描画操作の設定をドライバに通知する。
*
* ダブルバッファによって、
* 更新中の画面領域へアプリケーションが同時に描画することから起きる、
* 表示の乱れが防止される。
*/
SDL_GL_SwapBuffers( );
}
static void setup_opengl( int width, int height )
{
float ratio = (float) width / (float) height;
/* シェーディングモデルは Gouraud (なめらか) */
glShadeModel( GL_SMOOTH );
/* 裏面を取り除く */
glCullFace( GL_BACK );
glFrontFace( GL_CCW );
glEnable( GL_CULL_FACE );
/* 消去時の色をセット */
glClearColor( 0, 0, 0, 0 );
/* ビューポートを設定 */
glViewport( 0, 0, width, height );
/*
* 射影行列を変更し、ビューボリュームにセット。
*/
glMatrixMode( GL_PROJECTION );
glLoadIdentity( );
/*
* 練習:
* これを glFrustum の呼び出しに置き換えよ。
*/
gluPerspective( 60.0, ratio, 1.0, 1024.0 );
}
int main( int argc, char* argv[] )
{
/* 現在のビデオ設定についての情報 */
const SDL_VideoInfo* info = NULL;
/* ウィンドウの寸法 */
int width = 0;
int height = 0;
/* ウィンドウの色のピクセル深度 */
int bpp = 0;
/* SDL_SetVideoMode に渡すフラグ */
int flags = 0;
/* まず、SDL ビデオサブシステムを初期化 */
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
/* 失敗したので終了 */
fprintf( stderr, "ビデオの初期化に失敗しました: %s\n",
SDL_GetError( ) );
quit_tutorial( 1 );
}
/* いくつかのビデオ情報を取得しよう */
info = SDL_GetVideoInfo( );
if( !info ) {
/* おそらくこれは絶対に起きないはず */
fprintf( stderr, "ビデオの問い合わせに失敗しました: %s\n",
SDL_GetError( ) );
quit_tutorial( 1 );
}
/*
* 横幅/高さを 640/480 にセット。
* (もちろん普通のアプリケーションにおいて
* ユーザーにこれを決定してもらうだろう)
* 画面から要求していたピクセル深度を取得。
* X11 では、VidMode は解像度を変えることができないため、
* これはおそらく過度安全だろう。
* Win32 では、ChangeDisplaySettings によって
* ピクセル深度を変えることができる。
*/
width = 640;
height = 480;
bpp = info->vfmt->BitsPerPixel;
/*
* ここで、OpenGL ウィンドウのために要求されたウィンドウ属性を設定したい。
* RGB 各チャンネルに *少なくとも* 5 ビット欲しい。
* また、少なくとも 16 ビットのデプスバッファも欲しい。
*
* 最後にする事はダブルバッファウィンドウの要求である。
* '1' でダルバッファが有効になり、
* '0' で無効になる。
*
* SDL_SetVideoMode へのフラグにおいて
* SDL_DOUBLEBUF を使わない事に注意。
* それは GL アトリビュートに影響せず、
* 標準の 2D blit 転送の設定だけに影響する。
*/
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
/*
* SDL がフルスクリーンビデオモードにおいて
* OpenGL ウィンドウを提供してくれるよう要求したい。
*
* 練習:
* ウィンドウ状態で開始するオプションを作り、
* glViewPort でリサイズイベントを適切に処理せよ。
*/
flags = SDL_OPENGL | SDL_FULLSCREEN;
/*
* Set the video mode
*/
if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) {
/*
* これはさまざまな理由で起き得る。
* DISPLAY が設定されていない、
* 指定された解像度が利用可能でない、など。
*/
fprintf( stderr, "ビデオモードのセットに失敗しました: %s\n",
SDL_GetError( ) );
quit_tutorial( 1 );
}
/*
* ここで、OpenGL の使用のために
* ダブルバッファのウィンドウを適切に設定したはず。
*/
setup_opengl( width, height );
/*
* さて、通常のアプリケーション処理 -- たくさんの再描画と
* イベントループを始めたい。
*/
while( 1 ) {
/* やってくるイベントを処理 */
process_events( );
/* 画面を表示 */
draw_screen( );
}
/*
* 練習:
* SDL_GetTicks() を使ってタイミングを記録し、
* プログラム終了時に 1 秒間のフレームを表示せよ。
*/
/* ここには届かない */
return 0;
}