CAFE

프로그래밍

SDL + wxWidgets 통합 강좌

작성자DOSBox─X|작성시간10.08.05|조회수1,477 목록 댓글 3

SDL + wxWidgets 통합 강좌

 

wxWidgets는 크로스 플랫폼 라이브러리이므로 Windows뿐 아니라 여러 운영 체제에서 사용할 수 있는 "그래픽 사용자 인터페이스"(메뉴 등)를 제공하는 라이브러리입니다. 프로그래머가 Win32 API만을 고집하게 되면 다른 운영 체제로 기존의 소스 코드를 이식할 수가 없어서 코드 변경을 해 주어야 하는 불편함이 따르는 데 반해 wxWidgets는 통합 그래픽 사용자 인터페이스를 사용함으로써 코드를 한 번만 짜 주면 여러 플랫폼에서 사용할 수 있다는 장점이 있습니다.

 

SDL 또한 마찬가지로 크로스 플랫폼 라이브러리이므로 여러 곳에서 사용할 수 있습니다. 영상과 소리 출력 등을 다루는 데 도움을 줍니다.

 

아래는 SDL의 창 내용을 wxWidgets의 사용자 인터페이스(창)에 결합시키는 강좌입니다.

이 강좌는 SDL_Surface를 wxWidgets에 이용하여 SDL의 그래픽을 제공합니다. (OpenGL을 이용하는 것이 아닙니다.)

 

 

일단 wx_sdl.cc라는 새로운 파일을 만들어 봅시다. 이름은 아무 것이나 상관 없으나 컴파일을 할 때(Makefile 등을 사용시) 신경을 써 주셔야 할 것입니다.

 

#include <iostream>

 

#include <wx/wxprec.h>

 

#ifndef WX_PRECOMP

    #include <wx/wx.h>

#endif

 

#include <wx/dcbuffer.h>

#include <wx/image.h>

 

#include "SDL.h"

 

class SDLApp : public wxApp {

    DECLARE_CLASS(SDLApp)

   

private:

    SDLFrame *frame;

   

public:

    bool OnInit();

    int OnRun();

    int OnExit();

};

 

bool SDLApp::OnInit() {

    // SDLFrame 만들기

    frame = new SDLFrame;

    frame->SetClientSize(640, 480);

    frame->Centre();

    frame->Show();

   

    // SDLFrame을 최상위 창으로

    SetTopWindow(frame);

 

    // 초기화 성공

    return true;

}

 

int SDLApp::OnRun() {

    // SDL 초기화

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {

        std::cerr << "unable to init SDL: " << SDL_GetError() << '\n';

       

        return -1;

    }

   

    // idle 이벤트 생성

    wxIdleEvent event;

    event.SetEventObject(&frame->getPanel());

    frame->getPanel().AddPendingEvent(event);

 

    // 루프 시작

    return wxApp::OnRun();

}

 

int SDLApp::OnExit() {

    // SDL 끝내기

    SDL_Quit();

   

    // 표준 exit 코드 반환

    return wxApp::OnExit();

}

 

IMPLEMENT_CLASS(SDLApp, wxApp)

IMPLEMENT_APP(SDLApp)

 

가장 기본적인 설정입니다. 헤더 파일은 wxWidgets, SDL, STL iostream 라이브러리를 포함하고 있습니다. 나중에는 다른 클래스를 만들 때 wx/image.h와 wx/dcbuffer.h가 필요합니다. 일단 계속해 봅시다. (wxWidgets와 SDL는 최신 버전을 이용하고 있습니다.)

 

SDLApp 클래스에는 다음의 세 가지가 있습니다: OnInit, OnRun, OnExit

일단은 응용 프로그램을 초기화하고(OnInit), 실행할 준비를 하고(OnRun), 응용 프로그램을 끝냅니다(OnExit). OnInIt()을 먼저 봅시다.

 

    // SDLFrame 만들기

    frame = new SDLFrame;

    frame->SetClientSize(640, 480);

    frame->Centre();

    frame->Show();

   

    // SDLFrame을 최상위 창으로

    SetTopWindow(frame);

 

여기에 SDLFrame을 만들고 이 SDLFrame을 최상위 창으로 만들 것입니다. SDLFrame이 뭔지에 대해서는 아직 생각할 필요가 없습니다. 아직 만들지도 않았으니까요. 이제 wxFrame을 확장할 준비가 되었습니다. 클라이언트 창 크기를 640x480으로 설정하고 화면 가운데에 보이게 할 것입니다.

 

OnInit()은 매우 단순하므로 이제 OnRun()으로 넘어갑시다.

 

    // SDL 초기화

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {

        std::cerr << "unable to init SDL: " << SDL_GetError() << '\n';

       

        return -1;

    }

 

여기서 SDL 라이브러리를 초기화합니다. 여기에 사용한 것은 비디오 출력밖에 없으며 이것은 초기화하려는 SDL의 일부일 뿐입니다.

 

    // idle 이벤트 생성

    wxIdleEvent event;

    event.SetEventObject(&frame->getPanel());

    frame->getPanel().AddPendingEvent(event);

 

유휴(idle) 이벤트가 있는 동안에도 드로잉(그리기)을 계속 할 것이므로 루프를 걸어 줍시다.

 

    // 루프 시작

    return wxApp::OnRun();

 

끝으로 wxWidgets 메인 이벤트 루프 처리를 시작하는 부모 wxApp:OnRun()를 호출합니다. SDL을 완전히 사용하고 있지 않더라도 wxWidgets가 일반적으로 이벤트를 핸들할 수 있게 도와 줍니다. 그러나 이 기능은 SDL과 wxWidgets 이벤트를 모두 사용하고 싶을 때에는 동작하지 않습니다. 끝으로 OnExit()입니다.

 

    // SDL 끝내기

    SDL_Quit();

   

    // 표준 exit 코드 반환

    return wxApp::OnExit();

 

OnExit()는 OnInit()보다 더 단순합니다. 대부분의 SDL 프로그램들은 atexit() 함수를 사용하여 SDL_Quit() 콜백을 등록하지만 일단 이것을 여기에 넣을 것입니다.

 

이제 기본 응용 프로그램 클래스가 완성되었으므로 SDLFrame를 빌드해 봅시다.

 

아래의 줄들을 SDL 헤더 include 바로 뒤에 추가합시다. (wx_sdl.cc 파일)

 

enum {

    ID_FRAME = 10000,

    ID_PANEL,

    IDM_FILE_EXIT,

    IDM_HELP_ABOUT

};

 

class SDLFrame : public wxFrame {

    DECLARE_CLASS(SDLFrame)

    DECLARE_EVENT_TABLE()

   

private:

    SDLPanel *panel;

 

    void onFileExit(wxCommandEvent &event);

    void on_HelpAbout(wxCommandEvent &event);

   

public:

    SDLFrame();

    SDLPanel &getPanel();

};

 

inline void SDLFrame::onFileExit(wxCommandEvent &) { Close(); }

inline SDLPanel &SDLFrame::getPanel() { return *panel; }

 

IMPLEMENT_CLASS(SDLFrame, wxFrame)

 

BEGIN_EVENT_TABLE(SDLFrame, wxFrame)

    EVT_MENU(IDM_FILE_EXIT, SDLFrame::onFileExit)

    EVT_MENU(IDM_HELP_ABOUT, SDLFrame::on_HelpAbout)

END_EVENT_TABLE()

 

SDLFrame::SDLFrame() {

    // SDLFrame 만들기

    Create(NULL, ID_FRAME, wxT("Frame Title"), wxDefaultPosition,

           wxDefaultSize, wxCAPTION | wxSYSTEM_MENU |

           wxMINIMIZE_BOX | wxCLOSE_BOX);

 

    // 메뉴 모음 만들기

    wxMenuBar *mb = new wxMenuBar;

   

    // 파일 메뉴 만들기

    wxMenu *fileMenu = new wxMenu;

    fileMenu->Append(IDM_FILE_EXIT, wxT("끝내기(&X)"));

   

    // 파일 메뉴를 메뉴 모음에 추가

    mb->Append(fileMenu, wxT("파일(&F)"));

   

    // 도움말 메뉴 만들기

    wxMenu *helpMenu = new wxMenu;

    helpMenu->Append(IDM_HELP_ABOUT, wxT("정보"));

   

    // 도움말 메뉴를 메뉴 모음에 추가

    mb->Append(helpMenu, wxT("도움말(&H)"));

   

    // 메뉴 모음을 SDLFrame에 추가

    SetMenuBar(mb);

   

    // SDLPanel 만들기

    panel = new SDLPanel(this);

}

 

void SDLFrame::on_HelpAbout(wxCommandEvent &) {

    wxMessageBox(wxT("wx-sdl tutorial\nCopyright (C) 2005 John Ratliff"),

                 wxT("about wx-sdl tutorial"), wxOK | wxICON_INFORMATION);

}

 

SDLFrame은 응용 프로그램에 위젯을 추가합니다. 응용 프로그램과 메뉴가 클릭할 수 있는 Frame을 가집니다. SDLFrame은 세 개의 메쏘드와 생성자가 있습니다. 생성자부터 시작합시다.

 

    // SDLFrame 만들기

    Create(NULL, ID_FRAME, wxT("Frame Title"), wxDefaultPosition,

           wxDefaultSize, wxCAPTION | wxSYSTEM_MENU |

           wxMINIMIZE_BOX | wxCLOSE_BOX);

 

몇 가지 기본 옵션으로 wxFrame를 만듭니다. 최상위 창이므로 부모 창은 없습니다. 인식자로 ID_FRAME을 사용합니다. 창 제목은 Frame Title입니다. SDLApp의 위치와 크기를 설정하므로 이것을 기본값으로 둡니다. 크기를 조절할 수 있는 테두리와 최대화 단추가 제거된 wxFrame의 기본 스타일을 만들어 봅시다.

 

    // 메뉴 모음 만들기

    wxMenuBar *mb = new wxMenuBar;

   

    // 파일 메뉴 만들기

    wxMenu *fileMenu = new wxMenu;

    fileMenu->Append(IDM_FILE_EXIT, wxT("끝내기(&X)"));

   

    // 파일 메뉴를 메뉴 모음에 추가

    mb->Append(fileMenu, wxT("파일(&F)"));

   

    // 도움말 메뉴 만들기

    wxMenu *helpMenu = new wxMenu;

    helpMenu->Append(IDM_HELP_ABOUT, wxT("정보"));

   

    // 도움말 메뉴를 메뉴 모음에 추가

    mb->Append(helpMenu, wxT("도움말(&H)"));

   

    // 메뉴 모음을 SDLFrame에 추가

    SetMenuBar(mb);

 

이제 SDLFrame을 위한 메뉴 모음을 만듭니다. 파일과 도움말이라는 두 개의 메뉴가 있습니다. 파일 메뉴는 끝내기 항목이 있고 도움말 메뉴에는 정보 항목이 있습니다.

 

BEGIN_EVENT_TABLE(SDLFrame, wxFrame)

    EVT_MENU(IDM_FILE_EXIT, SDLFrame::onFileExit)

    EVT_MENU(IDM_HELP_ABOUT, SDLFrame::on_HelpAbout)

END_EVENT_TABLE()

 

메뉴 항목의 이벤트를 핸들하려면 이벤트 테이블을 설정하여야 합니다. 끝내기를 누르면 SDLFrame::onFileExit을 호출합니다. 정보를 누르면 SDLFrame::on_HelpAbout을 호출합니다.

 

    // SDLPanel 만들기

    panel = new SDLPanel(this);

 

 

끝으로 하나의 클라이언트 창을 만들어 봅시다. 이것은 SDLPanel입니다. wxPanel을 확장할 준비가 되었습니다.

 

이 세 개의 메쏘드는 매우 단순하므로 여기에 다시 서술하지는 않겠습니다. onFileExit()은 Close()를 호출하여 프레임을 닫습니다. on_HelpAbout()는 기본 정보가 있는 wxMessageBox를 보여 줍니다. getPanel()은 SDLPanel에 대한 레퍼런스를 반환합니다.

 

이제 SDLPanel을 만들 차례입니다. 이 줄을 enum 바로 아래에 삽입합니다.

 

class SDLPanel : public wxPanel {

    DECLARE_CLASS(SDLPanel)

    DECLARE_EVENT_TABLE()

   

private:

    SDL_Surface *screen;

 

    void onPaint(wxPaintEvent &event);

    void onEraseBackground(wxEraseEvent &event);

    void onIdle(wxIdleEvent &event);

    void createScreen();

   

public:

    SDLPanel(wxWindow *parent);

    ~SDLPanel();

};

 

inline void SDLPanel::onEraseBackground(wxEraseEvent &) {}

 

IMPLEMENT_CLASS(SDLPanel, wxPanel)

 

BEGIN_EVENT_TABLE(SDLPanel, wxPanel)

    EVT_PAINT(SDLPanel::onPaint)

    EVT_ERASE_BACKGROUND(SDLPanel::onEraseBackground)

    EVT_IDLE(SDLPanel::onIdle)

END_EVENT_TABLE()

 

SDLPanel::SDLPanel(wxWindow *parent) : wxPanel(parent, ID_PANEL), screen(NULL) {

    // wxPanel 크기 설정

    wxSize size(640, 480);

   

    SetMinSize(size);

    SetMaxSize(size);

}

 

SDLPanel::~SDLPanel() {

    if (screen != NULL) {

        SDL_FreeSurface(screen);

    }

}

 

void SDLPanel::onPaint(wxPaintEvent &) {

    // 화면이 없으면 그릴 수 없음

    if (screen == NULL) {

        return;

    }

   

    // lock the surface if necessary

    if (SDL_MUSTLOCK(screen)) {

        if (SDL_LockSurface(screen) < 0) {

            return;

        }

    }

   

    // 픽셀 데이터에서 비트맵 만들기

    wxBitmap bmp(wxImage(screen->w, screen->h,

                    static_cast<unsigned char *>(screen->pixels), true));

   

    //  화면 잠금 해제

    if (SDL_MUSTLOCK(screen)) {

        SDL_UnlockSurface(screen);

    }

   

    // 화면 그리기

    wxBufferedPaintDC dc(this, bmp);

}

 

void SDLPanel::onIdle(wxIdleEvent &) {

    // create the SDL_Surface

    createScreen();

   

    // 필요하면 surface 잠금

    if (SDL_MUSTLOCK(screen)) {

        if (SDL_LockSurface(screen) < 0) {

            return;

        }

    }

   

    // SDL에 요청 (밀리초)

    int tick = SDL_GetTicks();

   

    for (int y = 0; y < 480; y++) {

        for (int x = 0; x < 640; x++) {

            wxUint32 color = (y * y) + (x * x) + tick;

            wxUint8 *pixels = static_cast<wxUint8 *>(screen->pixels) +

                              (y * screen->pitch) +

                              (x * screen->format->BytesPerPixel);

 

            #if SDL_BYTEORDER == SDL_BIG_ENDIAN

                pixels[0] = color & 0xFF;

                pixels[1] = (color >> 8) & 0xFF;

                pixels[2] = (color >> 16) & 0xFF;

            #else

                pixels[0] = (color >> 16) & 0xFF;

                pixels[1] = (color >> 8) & 0xFF;

                pixels[2] = color & 0xFF;

            #endif

        }

    }

   

    // 필요하면 잠금 해제

    if (SDL_MUSTLOCK(screen)) {

        SDL_UnlockSurface(screen);

    }

   

    // 패널 새로 고침

    Refresh(false);

   

    // Sleep

    wxMilliSleep(33);

}

 

void SDLPanel::createScreen() {

    if (screen == NULL) {

        int width, height;

        GetSize(&width, &height);

       

        screen = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,

                                      24, 0, 0, 0, 0);    

    }

}

 

이 클래스는 가장 크고 어려워 보이지만 하나씩 따라가다 보면 어렵지 않습니다. 생성자 하나, 소멸자 하나, 네 개의 메쏘드가 있습니다. 생성자를 시작해 봅시다.

 

 

SDLPanel::SDLPanel(wxWindow *parent) : wxPanel(parent, ID_PANEL), screen(NULL) {

    // wxPanel 크기 설정

    wxSize size(640, 480);

   

    SetMinSize(size);

    SetMaxSize(size);

}

 

 

끝으로 올바른 창 크기를 갖도록 하기 위해 SDLPanel의 최소 및 최대 크기를 설정합니다.

 

BEGIN_EVENT_TABLE(SDLPanel, wxPanel)

    EVT_PAINT(SDLPanel::onPaint)

    EVT_ERASE_BACKGROUND(SDLPanel::onEraseBackground)

    EVT_IDLE(SDLPanel::onIdle)

END_EVENT_TABLE()

 

매우 직관적인 테이블입니다. Paint 이벤트는 onPaint()가 핸들합니다. 각 배경 이벤트는 onEraseBackground()가 핸들합니다. 끝으로 idle(유휴) 이벤트는 onIdle이 핸들합니다. onIdle()을 살펴봅시다.

 

    // SDL_Surface 만들기

    createScreen();

 

 

첫 번째 부분은 화면 생성에 관한 것입니다. createScreen()을 살펴 보면 화면이 어떻게 나타나는지 볼 수 있습니다.

 

    if (screen == NULL) {

        int width, height;

        GetSize(&width, &height);

       

        screen = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,

                                      24, 0, 0, 0, 0);    

    }

 

화면이 NULL이면 SDL_CreateRGBSurface를 이용한 것을 하나 만듭니다. 중요한 변수가 정의됩니다. SDL_SWSURFACE는 메모리 버퍼를 유지하는 데 사용합니다. width와 height는 SDLPanel에서 나옵니다. 24는 24비트 RGB 색을 이야기합니다. 다른 변수는 기본값으로 0으로 되어 있지만 아예 사용하지 않을 것입니다.

 

화면을 이제 가지게 되었는데 이것이 무엇인지 알았습니다. 이제 onIdle()로 되돌아갑시다.

 

    // 필요하면 surface 잠금

    if (SDL_MUSTLOCK(screen)) {

        if (SDL_LockSurface(screen) < 0) {

            return;

        }

    }

 

필요하면 이용하기 전에 SDL_Surface를 잠글(lock) 수 있게 해 줍니다.

 

    // SDL 요청 (밀리초)

    int tick = SDL_GetTicks();

   

    for (int y = 0; y < 480; y++) {

        for (int x = 0; x < 640; x++) {

            wxUint32 color = (y * y) + (x * x) + tick;

            wxUint8 *pixels = static_cast<wxUint8 *>(screen->pixels) +

                              (y * screen->pitch) +

                              (x * screen->format->BytesPerPixel);

 

            #if SDL_BYTEORDER == SDL_BIG_ENDIAN

                pixels[0] = color & 0xFF;

                pixels[1] = (color >> 8) & 0xFF;

                pixels[2] = (color >> 16) & 0xFF;

            #else

                pixels[0] = (color >> 16) & 0xFF;

                pixels[1] = (color >> 8) & 0xFF;

                pixels[2] = color & 0xFF;

            #endif

        }

    }

 

그렇게 복잡한 것은 아닙니다. 각 화소에 도형이 있는 빛깔을 만들어 냅니다. 24비트 색을 사용하고 있으므로 3개의 8비트 값을 가지게 됩니다.

 

    // 필요하면 잠금 해제

    if (SDL_MUSTLOCK(screen)) {

        SDL_UnlockSurface(screen);

    }

   

    // 패널 새로 고침

    Refresh(false);

   

    // Sleep

    wxMilliSleep(33);

 

화소에 대한 작업을 끝냈으면 surface의 잠금을 해제(unlock)합니다. 그 뒤 paint 이벤트를 만드는 Refresh()를 호출합니다. 끝으로 유휴 시간이 그리 자주 있겠지만 잠시 동안은 sleep로 처리해 두었습니다.

 

onIdle()은 paint 이벤트를 만들어 냈으므로 이제 onPaint()가 호출될 것입니다. onPaint()를 살펴 봅시다.

 

 

    // 화면이 없으면 그릴 수 없음

    if (screen == NULL) {

        return;

    }

   

    // 필요하면 surface 잠금

    if (SDL_MUSTLOCK(screen)) {

        if (SDL_LockSurface(screen) < 0) {

            return;

        }

    }

   

    // 픽셀 데이터에서 비트맵 만들기

    wxBitmap bmp(wxImage(screen->w, screen->h,

                    static_cast<unsigned char *>(screen->pixels), true));

   

    //화면 잠금 해제

    if (SDL_MUSTLOCK(screen)) {

        SDL_UnlockSurface(screen);

    }

 

오류 검사에 대한 것입니다. 화면이 존재하지 않은 패널에는 그리기(paint)를 할 수 없으므로 화면이 있는지부터 검사해 주어야 합니다. 접근하기 전에 화면을 잠가야 합니다.

 

다음에는 SDL 픽셀 데이터에서 만든 wxImage에서 wxBitmap을 을 만듭니다. 이 wxBitmap을 만든 뒤 화면의 잠금을 해제할 수 있습니다.

 

    // 화면 그리기

    wxBufferedPaintDC dc(this, bmp);

 

끝으로 wxBufferedPaintDC을 이용하여 화면의 bmp를 블릿(blit)처리합니다. 이것은 깜빡임을 줄여 줍니다. wxPaintDC를 사용하고 비트맵을 블릿시킬 수도 있었지만 비트맵으로는 wxBufferedPaintDC를 사용하는 것이 코드를 짜기가 더 쉽습니다.

배경을 그리고 지우는 이벤트가 이벤트 발생에 독립적으로 일어나므로 이들을 핸들해 주어야 합니다. paint 이벤트 쪽은 이미 다루었으므로 배경을 지우는 이벤트를 그냥 놔둡니다. 이렇게 하면 깜빡임을 줄일 수 있고 핸들러는 비어 있게 됩니다.

 

끝으로 소멸자를 살펴 봅시다.

 

    if (screen != NULL) {

        SDL_FreeSurface(screen);

    }

 

이 소멸자는 SDL_Surface의 할당을 제거합니다.

 

 

────────────────────────────────────────────

 

위의 강좌 글에 (최신 SDL에서 동작하지 않은 이유로) 약간 손을 본 소스 파일과 실행 파일을

첨부하였으니 도움이 되었으면 좋겠습니다. (Makefile은 컴파일에 필요하면 사용하십시오)

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
  • 작성자꺅~~!! | 작성시간 10.09.28 오랜만에 보네요. SDL라이브러리. 예전 리눅스 프로그래밍할때 관심있었는데 현실은.....ㅎㅎ 감사합니다~
  • 답댓글 작성자DOSBox─X 작성자 본인 여부 작성자 | 작성시간 10.09.29 도스박스는 기본적으로 SDL을 사용한답니다.^^
  • 작성자워이 | 작성시간 10.11.27 고맙습니다.
댓글 전체보기
맨위로

카페 검색

카페 검색어 입력폼