이미 창이 열려있겠죠. 그럼 이제 이미지를 넣어봅시다.


메모: 이 튜토리얼에서는 소스 코드의 주요 부분만을 다룰 겁니다. 전체 프로그램의 경우, 전체 소스 코드를 다운로드해야 합니다.


// SDL을 시작하고 창을 만듭니다
bool init();

// 미디어를 부릅니다
bool loadMedia();

// 미디어를 비우고 SDL을 닫습니다
void close();


우린 처음 튜토리얼에선 모든 정의를 main 함수에 넣었었죠. 작은 프로그램이었던지라 가능했던 겁니다만, 진짜 프로그램(비디오 게임 등등) 같은 곳에선 코드를 모듈화 할 수 있어야 합니다. 이는 스스로 쓴 코드를 조그마하게 잘라, 디버그를 쉽게하고, 재사용 가능토록 해줄 겁니다.


// 창이 렌더링 될 겁니다
SDL_Window* gWindow = NULL;

// 창 내부 표면
SDL_Surface* gScreenSurface = NULL;

// 이미지를 로드하고 화면에 띄우기
SDL_Surface* gHelloWorld = NULL;

여기에서 우리는 몇 가지 글로벌 변수를 선언합니다. 일반적으로 큰 프로그램에선 전역 변수를 사용하면 매우 복잡해지기 때문에 쓰지 않도록 해야하지만, 소스 코드를 가능한 한 단순하도록 쓰기 원하기 때문에 이렇게 쓰도록 합니다. 이 프로그램은 하나의 소스 파일만 쓰기 때문에 그 문제는 걱정하지 않아도 됩니다.


일단 SDL 표면(Surface)라는 새로운 데이터 타입이 보입니다. SDL 표면을 렌더링 하는데 필요한 모든 데이터와 함께 이미지의 픽셀을 포함한 단순한 화상 데이터입니다. SDL 표면을 렌더링하기 위해 CPU를 사용합니다. 물론 하드웨어 이미지 렌더링도 할 수 있지만 조금 더 어렵습니다. 그러니 우리는 쉬운 방법을 먼저 배우기로 하죠. 나중에 읽을 튜토리얼에서는 GPU 가속 이미지를 렌더링 하는 방법을 다루도록 하겠습니다.


여기선 파일을 로드해 화면에서 이미지(윈도우 내부 참조)를 다룰 겁니다.


이러한 SDL 표면에 포인터가 있다는 걸 볼 수 있습니다. 그 이유는 1) 우리는 동적 이미지를 로드하기 위해 메모리를 할당하기 위함입니다. 그리고 2) 메모리 위치에 따라 이미지를 참조하는 것이 좋습니다. 슈퍼 마리오 브라더스 같은 반복되는 이미지로 같은 벽돌이 렌더링 되는 걸 생각해보세요. 한 번 렌더링 할 때마다 새로운 이미지를 만드는 것보다 한 번 만든 이미지의 복사본을 가지고 계속 렌더링 해주는 게 훨씬 효과적이겠지요.


추가로, 포인터는 항상 초기화 해야 합니다. 우리는 포인터를 선언 할 때, 즉시 NULL로 선언했습니다.


bool init() {
    // 플래그 초기화
    bool success = true;

    // SDL 초기화
    if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
        printf( "SDL이 초기화 되지 않음! SDL_Error: %s\n", SDL_GetError() );
        success = false;
    } else {
        // 창 생성
        gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
        if( gWindow == NULL ) {
            printf( "창이 생성되지 않음! SDL_Error: %s\n", SDL_GetError() );
            success = false;
        } else {
            // 윈도우 표면 구하기
            gScreenSurface = SDL_GetWindowSurface( gWindow );
        }
    }

    return success;
}

여기선 SDL 초기화와 윈도우 생성 코드를 쓰고 고유한 기능을 집어넣은 것을 볼 수 있습니다. SDL_GetWindowSurface를 부르는 것도 새로운 점입니다.


이미지를 윈도우 내부에 보여주기 위해 윈도우 내부와 윈도우 내부에 들어갈 이미지를 얻을 필요가 있습니다. 그래서 윈도우 내부의 표면을 잡아 SDL_GetWindowSurface를 호출하는 거죠.


bool loadMedia() {
    // 성공 플래그 로드
    bool success = true;

    // 스플래시 이미지 로드
    gHelloWorld = SDL_LoadBMP( "02_getting_an_image_on_the_screen/hello_world.bmp" );
    if( gHelloWorld == NULL ) {
        printf( "이미지를 로드할 수 없습니다 %s! SDL Error: %s\n", "02_getting_an_image_on_the_screen/hello_world.bmp", SDL_GetError() );
        success = false;
    }

    return success;
}

미디어 로드 함수를 부르면, SDL_LoadBMP를 사용하여 지정한 이미지가 로드됩니다. SDL_LoadBMP는 BMP 파일의 경로를 취해 표면에 반환 시킵니다. 함수가 NULL을 반환하는 경우, 콘솔 창에서 SDL_GetError를 사용하여 오류를 표시해 실패했다고 알려줍니다.


주목해야 할 중요한 점은 위 코드는 현재 작업 디렉토리 아래 경로의 "02_getting_an_image_on_the_screen" 폴더 내부에 존재하는 "hello_world.bmp"라는 이미지라고 가정해두었다. 응용 프로그램이 실행되는 위치로부터 상대 경로가 됩니다.[각주:1]


            // 표면 업데이트
            SDL_UpdateWindowSurface( gWindow );

SDL_UpdateWindowSurface를 사용하여 이 프레임에 대해 표시할 것을 모두 그린 후 화면을 업데이트 해줘야 합니다. 화면에 그려진 것을 볼 때, 우리가 평소에 보듯 화면에 바로 그려지는 것은 아닙니다. 기본적으로 대부분의 렌더링 시스템은 두 개의 버퍼가 존재하는데, 각 버퍼를 프론트 버퍼와 백 버퍼라고 부릅니다.


SDL_BlitSurface 같은 그리기를 호출할 때, 백 버퍼가 렌더링하고 프론트 버퍼가 화면에 표시하게 됩니다. 그 이유는 대부분의 프레임이 화면에 많은 것들을 그려야 하기 때문입니다. 만약 프론트 버퍼만 존재한다면, 프레임이 그대로 그려지게 되고 결국 미완성 프레임을 보게 된다는 겁니다. 그러니 먼저 모든 것을 백 버퍼에 담아, 다시 프론트 버퍼에게 넘겨주면 이제 사용자가 완성된 프레임을 보게 되는 거죠.


물론 이는 각 프레임이 뿌려지고 난 뒤 SDL_UpdateWindowSurface를 모든 프레임마다 호출해주지 않는다는 의미입니다.

            // 2초 기다리기
            SDL_Delay( 2000 );
        }
    }

    // 자원을 비우고 SDL을 종료
    close();

    return 0;
}

그럼 이제 모든 것이 창에 렌더링 되었고, 창이 갑자기 사라지는 것을 막기 위해 2초 동안 대기 시간을 주었습니다. 2초간 기다리면, 프로그램이 닫히겠지요.


- 위 튜토리얼의 리소스, 소스코드는 여기서 받으세요.

  1. 원래 좀 더 긴 내용인데 C++ 기능과는 불필요한 설명이라 간추렸다. [본문으로]