02 NDS 홈브루(Homebrew) - NDS 윈도우 시스템(Windows System)

원문 :  http://kkamagui.springnote.com/pages/417428

 

들어가기 전에...

 

 

0.버전관리

  • 2007/10/18 21:12:24 : MovableWnd가 없어지고 Move 기능이 별도의 클래스로 이동함. 타이틀바를 클릭했을 때, 윈도우 이동을 지원하고 싶으면 CMovable 클래스를 사용하면 됨

 

0.시작하면서...

  내가 만든 NDS 윈도우 시스템(Window System)에 대한 문서이다. NDS 윈도우 시스템은 최대한 윈도우의 MFC 구조와 비슷하게 하여 윈도우 프로그래밍에 익숙한 사람이라면 누구나 쉽게 접근할 수 있도록 하는 것이 목적이다.

 현재 Base Window와 DC 정도만 구현되어있으며, 이것을 활용하여 만들어진 홈브류는 00 KKAMAGUI NOTEPAD 가 있다.

 현재 전체적인 윈도우의 구조는 아래와 같이 구성되어있다.

 클래스다이어그램.PNG

 <전체 클래스 다이어그램>

 주의할 점은 아래와 같다. 

  • 최대한 비슷하게 만들려는 의도지만, 여건상 메시지 처리 부분과 함수 호출의 순서에 약간 차이가 있을 수 있다. 따라서 윈도우 프로그래밍에 대한 지식으로 접근하면 제대로 동작하지 않을 수 있다.
  • 자동으로  처리되는 것은 거의 없다. 하다못해 ShowWindow() 함수를 이용해서 윈도우를 숨겼을 때, 단순히 윈도우를 그려주지만 않을 뿐 나머지 처리는 알아서 다 해야한다. 즉 원하는 게 있으면 전부 손으로 작성해야 한다.

 

 참고할 점은 아래와 같다.

  • 라이브러리는 RGB555 포맷을 사용하는 Frame Buffer 모드를 기본으로 사용한다고 가정하고 제작되었다.

    • 만약 다른 모드를 사용한다면 2D Raster 함수 부분을 적절하게 수정해야 할 것이다. 
    • 아래는 Frame Buffer 모드로 설정하는 예제이다. 
    1. /**
          Main LCD 및 SUB LCD를 모두 16bit 256 * 196 로 설정한다.
              Frame Buffer와 같이 쓸 수 있도록 수정한다.
      */
      void InitVideoMode()
      {
          // 16bit Color Mode 5
          videoSetMode( MODE_5_2D | DISPLAY_BG3_ACTIVE );
          videoSetModeSub( MODE_5_2D | DISPLAY_BG3_ACTIVE );
          
          // Video Memory를 설정한다. MAIN 같은 경우는 2개의 VRAM이 맵핑되어있으므로
          // 더블 버퍼의 사용도 가능하다.
          vramSetMainBanks( VRAM_A_MAIN_BG_0x06000000, VRAM_B_MAIN_BG_0x06020000,
                            VRAM_C_SUB_BG, VRAM_D_LCD);
    2.     // Background에 대한 설정을 한다. BG_BMP_BASE를 조절하면 스크롤 및
          // 더블 버퍼를 구현할 수 있다.
          BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE( 0 ); //| BG_PRIORITY( 3 );
          // scale을 1, rotation을 0으로 설정하여 frame buffer와 같게 만듬
          BG3_XDX = 1 << 8;
          BG3_XDY = 0;
          BG3_YDX = 0;
          BG3_YDY = 1 << 8;
          // Translation(Reference Point X/Y-Coordinate)을 0으로 설정
          // 표시하는 위치를 옮기고 싶으면 조절
          BG3_CX = 0;
          BG3_CY = 0;
          // x축 및 y축으로 100 pixel 이동
          //BG3_CX = 100 << 8; 
          //BG3_CY = 100 << 8;
              
          SUB_BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE( 0 );// | BG_PRIORITY( 3 );
          SUB_BG3_XDX = 1 << 8;
          SUB_BG3_XDY = 0;
          SUB_BG3_YDX = 0;
          SUB_BG3_YDY = 1 << 8;
          SUB_BG3_CX = 0;
          SUB_BG3_CY = 0;
  • 화면의 크기는 매크로로 정의되어있다. 화면이 더 넓어지거나 좁아진다면 수정해야 한다.

 

1.2D 래스터 연산(Raster Operation)

 윈도우 라이브러리는 클립핑이 지원되는 라인 그리기 함수 및 사각형 그리기 함수를 지원한다.

 라인을 그리는 함수는 Bresenham 알고리즘을 사용하고 클립핑 알고리즘으로는 cohen-sutherland 알고리즘을 이용했다. 사각형 타입은 Fill Rect와 Rect 두가지 타입이 있는데, Rect는 라인을 그리는 함수를 이용하여 구현되었다.

 2D Raster 함수들은 클립핑 영역을 받도록 되어있는데, 일단 기본 설정은 스크린에 그리는 것으로 고정되어있다. 따라서 Y 좌표의 계산은 Y * SCREEN_WIDTH로 설정되어있으며 추후 스크린이 아닌 메모리 영역에 그릴 경우 이 부분을 수정할 필요가 있다.

 2D Raster 함수들은 아래와 같이 구성되어있고 CDC 클래스에서 사용된다.

  1. void DrawLine( RECT stClippingRect, void* pvAddr, int iX1, int iY1, int iX2,
            int iY2, unsigned short usColor );
    void DrawBox( RECT stClippingRect, void* pvAddr, int iX1, int iY1, int iX2,
            int iY2, unsigned short usColor, bool bFill );
    void DrawPixel( RECT stClippingRect, void* pvAddr, int iX, int iY,
            unsigned short usColor );

 

2.한글/영문 출력

 한글/영문 출력 부분은 자작한 폰트 생성 툴을 이용해서 비트마스크 폰트를 생성한 후 이를 출력하는 방법으로 구현하였다. 물론 클립핑에 대한 처리도 되어있다.

 현재 폰트는 12x12 크기의 한글 폰트와  6x12 크기의 영문 폰트로 구성되어있다. Font 출력에 대한 함수들은 아래와 같이 구성되어있고 CDC 클래스에서 사용된다.

  1. #define ENG_FONT_WIDTH  6
    #define HAN_FONT_WIDTH  12
    #define ENG_FONT_HEIGHT 12
    #define HAN_FONT_HEIGHT 12
  2. // 한글/영어 다 찍을 수 있다.
    void PrintChar( RECT stClippingRect, void* pvBaseAddr, int iX, int iY,
            unsigned short usForeColor, unsigned short usBackColor, char cChar );
    void PrintString( RECT stClippingRect, void* pvBaseAddr, int iX, int iY,
            unsigned short usForeColor, unsigned short usBackColor, char* pcString );

 한글 폰트 생성 및 출력에 대한 내용은 01 NDS 한글 출력 라이브러리 문서를 참고하자.

 

3.Z-Order 처리

 Z-Order는 간단하게 구현되어있다. 윈도우 메니져가 Z-Order의 첫번째 윈도우만 관리하고 나머지는 윈도우 자신이 다음에 위치하는 윈도우의 포인터를 가지는 방식으로 관리한다. 이렇게 함으로써 윈도우 메니져의 자료구조를 단순하게 하고 윈도우 관리에 필요한 추가적인 자료구조를 없앴다.

 Z-Order의 구성은 윈도우를 화면에 그리는 순서와도 관계가 있는데 Z-Order의 Bottom에서부터 Z-Order의 Top 순으로 OnPaint() 함수를 호출해서 그린다.

 아래는 Z-Order의 순서와 실제 화면과의 관계를 나타낸 그림이다.

윈도우라이브러리1.PNG

<Z-Order & Screen>

 

 

4.윈도우 그리기 및 그리는 최소 영역 설정

 윈도우즈나 X-Window 같은 윈도우 시스템의 경우, 무식하게 화면 전체를 계속해서 갱신하지 않는다. 또한 윈도우 하나의 내용이 갱신되었다고 해서 윈도우 전체를 다시 그리는 일도 하지 않는다. 변화되면 해당 부분만 그려서 그리는 영역을 최소화하고, 이를 효율적으로 관리하여 효율을 최대한 높이기 위해 노력한다(화면을 그리는데 너무 많은 processing power를 사용한다면 일은 언제하는가? ㅡ_ㅡ;;;)

 처음에는 세세한 부분까지 변화된 영역을 검출하여 처리하려 했다가 알고리즘이 너무 복잡해지는 바람에 생략했었다. 뭐 사실 대충만든 프로토타입이 잘 작동해준 원인도 있지만... 윈도우 라이브러리를 계속 개발하다보니 속도가 점점 떨어지는 것이 눈에 보여서 어쩔 수 없이 넣게 되었다.

 윈도우 라이브러리에서 효율을 높이기위해 사용하는 방식은 3가지다.

  • 더블 버퍼링 : 메인 메모리를 할당하여 윈도우를 그리고 완전히 그려진 뒤에 비디오 메모리로 고속 전송한다. 고속 전송 함수는 간단히 어셈으로 짰다. ㅡ_ㅡ;;; 속도가 의심스러운 사람도 있을텐데, memcpy 함수보다 2배는 빠르니 믿어도 된다.(프로파일링해서 나온 결과다. 프로파일러에 대해서는  22 타이머(Timer)를 이용한 프로파일러(Profiler) 만들기 를 참고하자.) 
  • 일괄 그리기 : 윈도우가 변화되었다고 해서 버퍼에 직접 그리지 않는다. 윈도우를 다시 그려야 한다는 플래그와 영역만을 윈도우 매니저에게 알리고 윈도우 매니져는 메시지 처리가 끝난 뒤 일괄적으로 윈도우를 다시 그린다.
  • 그리기 영역 설정 : 조그만 윈도우가 변했다고 전체를 다시 그리는건 문제가 있다. 따라서 그려야 할 영역이 있을 때 이 영역들을 단순한 사각형 더하기로 계산하여 해당 영역에 있는 윈도우만 다시 그리도록 한다. 간단하지만 의외로 효율이 괜찮다. 

  이제 하나하나에 대해서 자세히 알아보자.

 

4.1 더블 버퍼링(Double Buffering)

 게임을 개발한 사람 or 윈도우 그림판 같은 프로그램을 개발해본 사람이라면 더블 버퍼링을 모르는 사람이 없을 것이다. 다행이도 NDS에는 비디오 메모리가 많기 때문에 비디오 메모리를 사용한 더블 버퍼링을 사용할 수 있지만, 조사해본 결과 한가지가 부족했다. 2개의 화면이므로 최소 4개의 메모리가 필요한데, 실제로 그림을 표시하기위해 사용 가능한 메모리는 3개 였다. ㅡ_ㅜ...

 물론 화면 하나는 비디오 메모리를 사용해서 비디오 스위칭을 이용한 더블 버퍼링을 사용하고, 나머지 하나는 메모리 전송을 이용한 더블버퍼링을 사용하면 되지만 윈도우 라이브러리가 복잡해지는 문제가 있어서 둘다 메인 메모리를 이용한 더블 버퍼링을 하기로 결정했다.

참고. NDS 속도에 대한 몇가지 테스트 문서를 참고하면 NDS에서 memcpy, for를 사용한 메모리 전송에 대한 결과를 알 수 있다. 실제 memcpy를 사용하면 7 ~ 8 ms정도가 걸리는데 자작한 고속 전송 함수를 사용하면 4 ms정도에 출력할 수 있다.

 

4.2 메시지 처리(Message Processing) 및 일괄 그리기(Batch Drawing)

 일괄로 그리기란 윈도우가 변경되는 시점에 그래픽 버퍼에 그리는 것이 아니라 변경된 윈도우에 상태를 저장한 뒤에 나중에 일괄적으로 모든 윈도우를 Z-Order의 역순으로 그려나가는 것을 말한다. 이렇게 한 이유는 NDS의 빈약한(??) 프로세싱 능력과 작은 메모리 때문에 윈도우들을 개별 태스크 or 개별 메시지 큐로 구분하지 못했기 때문이다.

 그렇다면 어떻게 멀티 윈도우를 구현한 것일까? 약간의 꼼수를 이용했다. 아래를 보자.

 

4.2.1 메시지 처리(Message Processing) 

 방법은 간단하다. 원래 윈도우 시스템이 개별 윈도우의 메시지 큐에 메시지를 던져줘서 처리하던 방식이라면 내가 만든 윈도우 시스템은 메시지를 Z-Order 순서로 전달하는 방식이라 할 수 있다. 다시 말하면 하나의 메시지를 전체 윈도우가 돌아가면서 사용한다는 말인데, 별도의 처리가 없으면 메시지가 전체에게 전달되므로 동작에 문제가 생긴다.

 따라서 이를 규제하는 방식이 윈도우 영역 및 메시지 종류, 그리고 윈도우 핸들을 이용해서 비교하여 자신의 핸들 or 자신의 윈도우 영역에서 발생한 것이면 메시지의 전달을 더이상 하지 않고 삭제함으로써 무한 메시지 전달을 막았다.

 아래는 이것을 그림으로 나타낸 것이다.

윈도우라이브러리2.PNG

<메시지 처리>

 좌측 상단의 4번 윈도우 자리에 클릭이 일어났을 때 해당 메시지가 어떻게 전달되고 어디서 처리가 완료되는가 하는 그림이다. 메시지는 Z-Order 순서로 전달되다가 4번 윈도우의 처리 루틴에서 자신의 영역에서 발생한 메시지 임을 깨닿고 메시지 처리를 완료한 후 Background 윈도우로 전달하지 않았다.

 

 위와 같은 알고리즘을 사용하기위한 전제조건은 무엇일까?

  • 한가지는 어플리케이션 프로그래머가 OnMessage() 함수에서 적당히 잘 처리한 다음, 메시지를 다른 윈도우에 전달해줘야 한다는 점이다. 위와 같은 상황에서 1번 윈도우에서 메시지를 더이상 뒤로 전달하지 않는다면 다른 윈도우는 메시지를 받지 못하기 때문에 굶주림 상태에 빠진다. 하지만 너무 잘 전달해서 자신의 메시지를 뒤로 전달하면 동작에 문제가 발생한다. 신중한 처리가 필요하다.
  • 다른 한가지는 윈도우는 메시지 처리 루틴에서 너무 시간을 끌면 뒤에 윈도우에게 메시지가 전달되는 시간이 더 걸리기 때문에 시간에 민감한 프로그램을 작성할 때 주의해야 한다는 점이다. 특히 윈도우를 다시 그리거나 하는 작업은 시간이 많이 걸리기 때문에 OnPaint() 같은 함수를 직접 호출해서 버퍼에 다시 그리는 일은 하지 말고 RedrawWindow() 함수를 사용해서 윈도우 매니져가 일괄로 화면을 그리도록 해야 한다.

 

4.2.2 일괄 그리기(Batch Drawing) 

 NDS의 빈약한 처리 능력 때문에 일괄 그리기를 선택했다고 앞서 이야기 했다. 일괄 그리기를 하면 좋은 점이 뭘까?

  • 첫째는 윈도우가 겹겹이 쌓여있을 때 끼어있는 윈도우의 "일부"를 그리는 문제를 간단히 처리할 수 있다. 위의 <메시지 처리>그림에서 2번 윈도우가 갱신되었다면 실제로 그려야하는 영역은 2번 윈도우의 영역에서 1번 윈도우의 영역을 뺀 부분이다. 이 시점에서 버퍼를 바로 갱신하려면 2번 윈도우를 그리고 1번 윈도우를 그 위에 다시 그리면 된다. 이러한 단순한 경우라면 괜찮지만 만약 윈도우가 겹겹이 쌓여있고 다들 일부분이 보이는 상태인데, 그 일부분만 보이는 화면이 계속 갱신되는 상황이라면??? 윈도우 위치에 따른 영역 계산 + 일부분 Draw 등등의 작업을 수행해야 한다. 알고리즘이 점점 복잡해지고 계산량도 점점 늘어난다. 실제로 이러한 방식으로 이전 KKAMAGUI OS를 개발했었는데, 계산량이 많아지다보니 오히려 성능이 그리 좋지 않았다. 그래서 고민한 끝에 일괄 그리기를 이용하여 처리했다.
  •  둘째는 윈도우 메시지를 전달하는 시간을 줄일 수 있다. 현재의 윈도우 라이브러리는 윈도우가 다른 윈도우에게 메시지를 전달하는 구조로 되어있기 때문에, 시간이 많이 걸리는 작업을 메시지 처리 루틴에서 하게되면 치명적이다. 윈도우를 다시 그리는 역할을 윈도우 매니져로 넘김으로써 메시지를 좀 더 빠르게 전달할 수 있고, 윈도우를 계속해서 다시 그리는 일을 하지 않음으로써  응답속도의 향상을 노릴 수 있다.

 

4.3 그리는 영역 설정

 위에서 일괄 그리기의 장점을 이야기 했다. 그렇다고 무턱대고 화면 전체를 다시 그려서 전송하면 효율이 좋을까? 전체 영역 중에 작은 윈도우 일부만 변했는데, 윈도우 전체를 다시 그리는 것은 비효율적이다. 따라서 그리는 영역의 범위를 정하여 해당 범위에 들어있는 윈도우만 다시 그린 후, 그리는 영역이 현재 윈도우 내부에 포함된다면 더이상 그리지 않도록 한다. 이렇게 하면 조금이나마 효율을 높일 수 있다.

 그리는 영역의 설정은 간단한 사각형 더하기로 해결했는데, 알고리즘은 아주 간단하다. 아래의 그림과 같이 각 영역의 Min/Max값을 얻어서 해당 영역 안을 그리도록 했다.

 윈도우라이브러리3.PNG

<그리는 영역 설정>

  위의 그림과 같이 1번 윈도우와 2번 윈도우가 변했다면 두 윈도우의 범위를 Min/Max한 붉은색 사각형이 다시 그려질 영역이 된다. 따라서 이 영역으로 Z-Order를 따라가면서 이 영역을 포함한 윈도우를 찾게되고 해당 윈도우를 그리는 것이다. 위에서 보면 1번 윈도우 및 2번 윈도우 그리고 3번 윈도우가 위 영역에 포함되므로 그려야 된다. 하지만 3번 윈도우 영역이 그리는 영역을 완전히 포함하므로 Background 윈도우는 다시 그릴 필요가 없으므로 3번 윈도우까지만 그린다.

 위와 같이 간단한 알고리즘을 이용해서 다시 그릴 영역을 어느정도 줄일 수 있다. 효율은... 그저 그렇다.. ㅡ_ㅡ;;; 하지만 안하는 것 보다는 좋다.

 

 

 

5.윈도우 관련 클래스 구현

5.1 CWnd 구현 

 CWnd 클래스는 아래와 같이 정의되어있다.

  1. // 기본 윈도우 클래스
    class CWnd
    {
    protected:
        RECT m_stWindowRect;
        bool m_bShow;
        WORD* m_wDisplayAddr;
        char m_vcTitle[ 33 ];
        // 윈도우에 그리기위해 넣음
        CWindowDC m_clWindowDC;
        // ZOrder를 위해 넣음
        CWnd* m_pclZOrderNext;
        CWnd* m_pclParent;
       
        //--------------------------------------------------------------------------
        // 아래는 Window Manager 관련 또는 기타 함수들
        // 쓰는 쪽에서는 아래의 함수를 건드릴 필요 없음
        //--------------------------------------------------------------------------
        void AddWindowToManager( CWnd* pclWnd );
        void DeleteWindowFromManager( CWnd* pclWnd );
        RECT CalcXButtonPosizion( void );
        CWindowManager* GetWindowManagerPtr( void );

  2.  

  3.     //--------------------------------------------------------------------------
        // 아래는 오버로딩할 수 있는 함수 목록
        // 아래의 함수만 상속받은 클래스에서 필요하면 재정의하자.
        //--------------------------------------------------------------------------
        virtual void OnEraseBkgnd( CDC* pclDC );
        virtual bool OnMessage( PMESSAGE pstMessage );
           
       
    public:
        CWnd();
        virtual ~CWnd();
        //--------------------------------------------------------------------------
        // 아래는 윈도우 메니져에서 사용할 용도로 설정된 함수들
        // 상속을 받아서 사용하는 경우라면 쓸 필요 없다.
        //--------------------------------------------------------------------------
        void RedrawZOrderBehindWindow( RECT stRect );
        void SetZOrderNext( CWnd* pclWindow );
        CWnd* GetZOrderNext( void );
       
        //--------------------------------------------------------------------------
        // 아래는 윈도우 사용에 관련된 함수들
        // 상속을 받아서 사용하는 경우라면 쓸 필요 없다.
        //--------------------------------------------------------------------------
        void Create( void* pvAddr, RECT stRect, char* pcName, bool bShow,
                CWnd* pclParent );
        void SetDisplayAddr( void* pvAddr );
        void* GetDisplayAddr( void );
       
        bool MoveWindow( int iX1, int iY1, int iX2, int iY2 );
        bool MoveWindow( RECT stRect );
        void ShowWindow( bool bShow );
        void RedrawWindow( void );
       
        void SetWindowText( char* pcTitle );
        char* GetWindowText( void );
        RECT GetWindowRect( void );
        RECT GetClientRect( void );
       
        void SetActiveWindow( void );
       
        void SetParent( CWnd* pclParent );
        CWnd* GetParent( void );
       
        void ScreenToClient( PPOINT pstPoint );
        void ScreenToClient( PRECT pstRect );
        void ClientToScreen( PPOINT pstPoint );
        void ClientToScreen( PRECT pstRect );
       
        void DestroyWindow( void );
        bool IsInWindowRect( int iX, int iY );
        bool IsInWindowRect( RECT stRect );
        bool IsInXButtonRect( int iX, int iY );
        bool IsInTitleBarRect( int iX, int iY );
        bool IsXButtonDownMessage( PMESSAGE pstMessage );
       
        bool DispatchMessage( PMESSAGE pstMessage );
        bool SendMessage( WORD wType, WPARAM wParam, LPARAM lParam );
        bool IsBelongMainWindowManager( void );
        bool IsBelongSubWindowManager( void );
        void GetXYFromMessage( PMESSAGE pstMessage, int* piX, int* piY );

  4.  

  5.     //--------------------------------------------------------------------------
        // 아래는 오버로딩할 수 있는 함수 목록
        // 아래의 함수만 상속받은 클래스에서 필요하면 재정의하자.
        //--------------------------------------------------------------------------
        virtual void OnPaint( void );
        virtual void OnDestroy( void );
        virtual void DrawXButton( CDC* pclDC );
        virtual void DrawTitleBar( CDC* pclDC );
    };

 최대한 MFC 클래스와 비슷하게 구현하려 노력했다. 낯익은 함수들이 반가울 것이다. CWnd 클래스는 모든 윈도우의 기본이 되는 윈도우로써 윈도우 기본 프레임을 그려주는 역할과 윈도우 메니져의 일부로써 자료구조 역할을 수행한다. 하지만 사실 거의 Draw 쪽 함수만 처리가 되어있을 뿐, 메시지 처리라든지 윈도우 이동에 관한 부분은 거의 가지고 있지 않다.

 정말 "기본" 적인 윈도우 구성에 대한 정보만 가지고 있다. 

 

5.2 CSkinWnd 구현 

 CSkinWnd는 그래픽 파일을 넣어주면 해당 그래픽 파일을 BitBlt 해서 윈도우에 스킨을 씌우는 프로그램이다. 스킨을 씌우는 방법은 향후 Library Tutorial을 다루면서 설명할 것이다. 그래픽 포맷인 KNG(KKAMAGUI NDS Graphic)에 대해서는 추후에 다룰 예정이다. 뭐 사실 RGB555 포맷으로 바꿔주는 것 밖에는 없다.

  1. class CSkinWnd : public CWnd
    {
    private:
        BYTE* m_pbKNGBuffer;
     
    public:
        CSkinWnd( void );
        ~CSkinWnd( void );
  2.     void SetKNGSkin( BYTE* pbKngBuffer );   
    protected:
        void OnPaint( void );
    }; 
  3. ... 구현 ... 
  4. /**
        화면 그리기 처리
            배경 그림을 그려준다.
    */
    void CSkinWnd::OnPaint( void )
    {
        CBitmap clBitmap;
        CDC clBitmapDC;
        CWindowDC clWindowDC( this );
        RECT stRect;
  5.     if( m_pbKNGBuffer == NULL )
        {
            return ;
        }
       
        // Bitmap 클래스에 KNG를 로드한 후 윈도우 전체에 bitblt 한다.
        clBitmap.LoadKNG( m_pbKNGBuffer );
        clBitmapDC.SelectObject( &clBitmap );
        stRect = GetWindowRect();
  6.     clWindowDC.BitBlt( 0, 0, GETWIDTH( stRect ), GETHEIGHT( stRect ),
            &clBitmapDC, 0, 0, 0 );

  윈도우 프로그래밍할 때 다이얼로그에 스킨을 씌워본 사람이라면 낯이 익은 코드일 것이다. 그래픽 포맷과 이미지 크기의 제한만 제외하면 거의 윈도우 프로그램하는 것과 같다.

 

5.3 CListWnd 구현 

 CListWnd는 윈도우의 List Box와 비슷한 기능을 하는 클래스다. 각 라인에 항목을 표시하고 선택된 항목을 표시해주는 기능을 한다. 부가적으로 Call Back을 지원하여 그려질 라인별로 그리는 방식에 대한 처리나 선택되었을 때 처리에 대한 구현도 표함하고 있다. CListWnd 자체는 Text String 기반으로 동작하지만, 구조체와 같은 정보를 삽입하고 Call Back만 잘 처리해 준다면 기타 여러가지 자료 구조도 표시할 수 있다. 이 부분에 대해서는 홈브루 중에 File Explorer를 참고하면 된다.

  1. class CListWnd : public CWnd
    {
    protected:
        CList m_clContentList;
  2.     int m_iTouchX;
        int m_iTouchY;
        int m_iLastX;
        int m_iLastY;
        int m_iViewItemNumber;
        bool m_bSelectMode;
        bool m_bLButtonDown;
  3. private:
        bool ProcessScrollMode( PMESSAGE pstMessage );
        bool ProcessSelectMode( PMESSAGE pstMessage );
        bool ProcessTitleBarMessage( int iX, int iY  );
       
    public:
        CListWnd( void );
        ~CListWnd( void );
  4.     bool AddItem( void* pvContent );
        void DeleteAllItem( void );
        bool DeleteItem( void* pvContent );
        int GetItemCount( void );
       
        void* GetItem( int iIndex );
        int GetSelectedItemIndex( void );
        bool IsSelectionMode( void );
        int GetMaxVisibleLineCount( void );
        void SetSelectionMode( bool bSelectMode );
        void SetFirstVisibleItem( int iIndex );
       
    protected:
        void OnPaint( void );
        bool OnMessage( PMESSAGE pstMessage );
  5.     //--------------------------------------------------------------------------
        // 리스트에 Item을 다른 방식으로 표시하고 싶으면 아래의 함수를 오버로딩해서
        // 원하는 대로 그리면 된다.
        //  한 라인 라인을 그릴때 호출되는 Call back 함수
        //--------------------------------------------------------------------------
        virtual void RenderContent( RECT stDrawRect, CDC* pclDC, void* pvContent,
                int iItemIndex );
       
        // 리스트의 Item이 선택되었을 때 호출되는 함수
        virtual void OnSelectCursorDown( void );
        virtual void OnSelectCursorUp( void );
    }; 

 

5.4 CChildWnd 구현 

 CChildWnd는 CWnd와 같으나 다른 점은 Window Manager에 등록이 되지 않기 때문에, 메시지에 대한 처리는 부모 윈도우가 Child Window에 넣어줘야 한다는 점이다. 이렇게 하면 편리한 점은 자식 윈도우를 부모 윈도우에 다 맡김으로써 Window Manager에 로드를 줄일 수 있고 Z-Order의 처리가 아주 간단하기 때문이다.

  1. // Child Wnd는 윈도우 매니저에 속하지 않는다는 점 말고는 CWnd와 동일하다.
    class CChildWnd : public CWnd
    {
    private:
        BYTE* m_pbKNGBuffer;
       
    public:
        CChildWnd( void );
        ~CChildWnd( void );
        
        // Override된 함수들
        void AddWindowToManager( CWnd* pclWnd );
        void DeleteWindowFromManager( CWnd* pclWnd );
    }; 

 

5.5 CDC 구현 

 CDC는 윈도우의 대표적인 그래픽 관련 클래스이다. CPaintDC와 CWindowDC도 구현되어있으며, 차의점은 CPaintDC 같은 경우 Title Bar의 높이를 자동으로 빼서 계산해 준다는 점이다.

  1. // 윈도우 관련 DC를 처리하는 Base Class
    // 하는일 없음
    class CDC
    {
    protected:
     CWnd* m_pclWindow;
     CBitmap* m_pclBitmap;
     int m_iLastX;
     int m_iLastY;
     int m_iTitleHeight;
     
    protected:
     void DrawRect( PRECT pstRect, WORD wColor, bool bFill );
     WORD* GetBufferAddr();
     RECT GetBufferRect();
     
    public:
     CDC();
     ~CDC();
     
     void SetWindow( CWnd* pclWindow );
     void SelectObject( CBitmap* pclBitmap );
     
     bool MoveTo( int iX, int iY );
     bool LineTo( int iX, int iY, WORD wColor );
     
     void FillRect( PRECT pstRect, WORD wColor );
     void FillRect( int iX1, int iY1, int iX2, int iY2, WORD wColor );
     void FrameRect( PRECT pstRect, WORD wColor );
     void FrameRect( int iX1, int iY1, int iX2, int iY2, WORD wColor );
     void TextOut( int iX, int iY, char* pcString, WORD wForeColor,
             WORD wBackColor );
     void BitBlt( int iDstX, int iDstY, int iWidth, int iHeight,
      CDC* pclDC, int iSrcX, int iSrcY, int iMode );
     void SetPixel( PPOINT pstPoint, WORD wColor );
    };
  2. // Paint DC
    class CPaintDC : public CDC
    {
    public:
     CPaintDC( CWnd* pclWindow );
     ~CPaintDC();
    };
  3. // Window DC
    class CWindowDC : public CDC
    {
    public:
     CWindowDC();
     CWindowDC( CWnd* pclWindow );
     ~CWindowDC();
    }; 

 

5.6 CBitmap 구현 

 CBitmap은 간단히 Bitmap 파일을 관리하는 클래스로 단순한 버퍼 역할을 한다. 지금은 KNG 파일 포맷을 사용하도록 되어있는데 아래에 KNG 구조체의 정보를 앞에 포함하고 내부 데이터는 RGB555 포맷을 사용하는 아주 간단한 구조의 파일이다.

  1. #define KNG_SIGNATURE "KNG"
  2. // KKAMAGUI NDS GRAPHIC 파일의 헤더 구조체
    typedef struct kkamaguiNdsGraphicStruct
    {
        char vcSignature[ 3 ]; // KNG 설정
        BYTE bVersion;         // 이미지 포맷 버전, 0x00 설정
        int iWidth;            // 이미지 넓이
        int iHeight;           // 이미지 높이
    } KNG, * PKNG;
  3. // Bitmap 관련 클래스
    class CBitmap
    {
    private:
        WORD* m_pwBitmap;
        SIZE m_stSize;
       
    public:
        CBitmap();
        ~CBitmap();
        
        WORD* GetBufferAddr();
        RECT GetBitmapRect();
       
        bool LoadKNG( BYTE* pbKNG );
    };

 

5.7 비디오 메모리 고속 전송 구현

 ARM 레지스터를 최대한 사용해서 전송하는 방식을 사용한다. 나름 빠르다.

  1. //  Frame Buffer 통짜를 빠르게 LCD로 복사해 주는 함수
    //      256 * 192 * 2 = 98304Byte
    //      r12 => Loop Count r2~r11 => 40Byte
    //      98304Byte = 40 * 2457(Loop Count) + 24(Remain)
    FastFrameBufferDump:
        push { r4 - r12 }
        // 2457 값을 바로 넣을 수 없어서 쉬프트 연산으로 넣는다.
        mov r4, #153
        mov r5, #9
        add r12, r5, r4, LSL #4
       
        // 40Byte Block씩 전송한다.
        Loop:
            ldmia r1!, { r2 - r11 }
            stmia r0!, { r2 - r11 }
            subs r12, #0x01
            bne Loop   
       
        // 남은 24Byte를 전송한다.
        ldmia r1, { r2 - r7 }
        stmia r0, { r2 - r7 }
           
        pop { r4 - r12 }
        bx lr

 

6.Graphic 기본 함수들 

6.1 Line 함수 

 Line 함수는 Bresenham 알고리즘으로 그리도록 되어있다. 물론 클립핑 부분은 여기서 하지 않고 따로 cohen-sutherland 알고리즘을 이용한다.

  1. //=============================================================================
    //
    //  cohen-sutherland 알고리즘을 이용한 클립핑
    //
    //=============================================================================
    #define LEFT_EDGE 0x01
    #define RIGHT_EDGE 0x02
    #define BOTTOM_EDGE 0x04
    #define TOP_EDGE 0x08
  2. //0000은 중앙
    #define INSIDE(a) ( !a )
    #define REJECT( a, b ) ( a & b )
    #define ACCEPT( a, b ) ( !( a | b ) )
  3. /**
        좌표가 MIN/MAX의 밖인지 안인지 판단
    */
    BYTE Encode( RECT stClippingRect, POINT stPt )
    {
        BYTE bCode;
       
        bCode = 0;
       
        if( stPt.iX < stClippingRect.iX1 )
        {
            bCode = bCode | LEFT_EDGE;
        }
        if( stPt.iX > stClippingRect.iX2 )
        {
            bCode = bCode | RIGHT_EDGE;
        }
        if( stPt.iY < stClippingRect.iY1 )
        {
            bCode = bCode | TOP_EDGE;
        }
        if( stPt.iY > stClippingRect.iY2 )
        {
            bCode = bCode | BOTTOM_EDGE;
        }
        return bCode;
    }
  4. /**
        두점을 교환
    */
    void SwapPoints( POINT* pstP1, POINT* pstP2 )
    {
        POINT stTemp;
       
        stTemp = *pstP1;
        *pstP1 = *pstP2;
        *pstP2 = stTemp;
    }
  5. /**
        두 속성을 교환
    */
    void SwapCodes( BYTE* bC1, BYTE* bC2 )
    {
        BYTE bTemp;
       
        bTemp = *bC1;
        *bC1 = *bC2;
        *bC2 = bTemp;
    }
  6. /**
        Line을 클립핑한다.
    */
    void ClipLines( RECT stClippingRect, void* pvAddr, POINT stP1, POINT stP2,
            WORD wColor )
    {
        BYTE bCode1, bCode2;
        int iDone = FALSE, iDraw = FALSE;
        float m;
  7.     if( stP2.iX != stP1.iX )//수직선이 아닐때
        {
            m = ( float ) ( stP2.iY - stP1.iY ) / ( stP2.iX - stP1.iX );
        }
        else
        {
            m = 0;
        }
       
        while( !iDone )
        {
            bCode1 = Encode( stClippingRect, stP1 );
            bCode2 = Encode( stClippingRect, stP2 );
           
            if( ACCEPT( bCode1, bCode2 ) )
            {
                //두점 다 안에 있으면 끝
                iDone = TRUE;
                iDraw = TRUE;
            }
            else
            {
                if( REJECT( bCode1, bCode2 ) )
                {
                    //두점다 화면 밖이면 끝
                    iDone = TRUE;
                }
                else
                {
                    // 만약 점 1이 안에 있으면 점 2를 사용한다.
                    if( INSIDE( bCode1 ) )
                    {
                        //p1이 중앙에 있으면 p2와 교환
                        SwapPoints( &stP1, &stP2 );
                        SwapCodes( &bCode1, &bCode2 );
                    }
                   
                    if( bCode1 & LEFT_EDGE )
                    {
                        stP1.iY += ( int ) ( ( stClippingRect.iX1 - stP1.iX ) * m );
                        stP1.iX = stClippingRect.iX1;
                    }
                    else if( bCode1 & RIGHT_EDGE )
                    {
                        stP1.iY += ( int ) ( ( stClippingRect.iX2 - stP1.iX ) * m );
                        stP1.iX = stClippingRect.iX2;
                    }
                    else if( bCode1 & TOP_EDGE )
                    {
                        if( stP2.iX != stP1.iX )//수직선이 아닐때
                        {
                            stP1.iX += ( int ) ( ( stClippingRect.iY1 - stP1.iY ) /
                                    m );
                        }
                        stP1.iY = stClippingRect.iY1;
                    }
                    else if( bCode1 & BOTTOM_EDGE )
                    {
                        if( stP2.iX != stP1.iX )//수직선이 아닐때
                        {
                            stP1.iX += ( int ) ( ( stClippingRect.iY2 - stP1.iY ) /
                                    m );
                        }
                        stP1.iY = stClippingRect.iY2;
                    }
                }
            }
           
            if( iDraw )
            {
                //선그리기
                BresenhamLine( pvAddr, stP1.iX, stP1.iY, stP2.iX, stP2.iY, wColor );
            }
        }
    }
  8. //=============================================================================
    //
    //  Bresenham 알고리즘을 이용한 클립핑
    //
    //=============================================================================
  9. /**
        라인을 그린다.
    */
    void BresenhamLine( void* pvAddr, int iX1, int iY1, int iX2, int iY2,
        unsigned short usColor )
    {
        int dy = iY2 - iY1;
        int dx = iX2 - iX1;
        int stepx, stepy;
        unsigned short* pusStartAddr = ( unsigned short* ) pvAddr;
       
        if( dy < 0 )
        {                                 // 기울기를 양수처리함
            dy = -dy;
            stepy = -1;
        }
        else
        {
            stepy = 1;
        }
     
        if( dx < 0 )
        {
            dx = -dx;
            stepx = -1;
        }
        else
        {
            stepx = 1;
        }
     
        dy <<= 1;                                     // dy*2 와 같은 의미(비트연산)
        dx <<= 1;                                     // dx*2 와 같은 의미(비트연산)
       
        pusStartAddr[ iX1 + SCREEN_WIDTH * iY1 ] = usColor;
        if( dx > dy )
        {
            int fraction = dy - (dx >> 1);     // dx>>1 은 dx/2와 같은 의미(비트연산)
            while( iX1 != iX2 )
            {
                if (fraction >= 0)
                {
                    iY1 += stepy;
                    fraction -= dx;                 // fraction -= 2*dx 과 같은 의미
                }
                iX1 += stepx;
                fraction += dy;                     // fraction -= 2*dy 과 같은 의미
               
                pusStartAddr[ iX1 + SCREEN_WIDTH * iY1 ] = usColor;
            }
        }
        else
        {
            int fraction = dx - (dy >> 1);
            while (iY1 != iY2)
            {
                if (fraction >= 0)
                {
                    iX1 += stepx;
                    fraction -= dy;
                }
                iY1 += stepy;
                fraction += dx;     
               
                pusStartAddr[ iX1 + SCREEN_WIDTH * iY1 ] = usColor;
            }
        }

 

6.2 Box 함수 

 Box 함수는 속이 찬 사각형과 빈 사각형 두가지 타입이 있다.

  1. /**
        Box를 그린다.
    */
    void DrawBox( RECT stClippingRect, void* pvAddr, int iX1, int iY1, int iX2,
            int iY2, unsigned short usColor, bool bFill )
    {
        int j;
        int iTemp;
        int iCount;
        WORD* pwAddr = ( WORD* ) pvAddr;
        WORD* pwTemp;
       
        // 속을 체우게 되어있으면 영역을 체운다.
        if( bFill == true )
        {
            // 영역 클립핑을 한다.
            MinMaxClipping( stClippingRect, &iX1, &iY1 );
            MinMaxClipping( stClippingRect, &iX2, &iY2 );
            iCount = iX2 - iX1 + 1;
            iTemp = iY1 * SCREEN_WIDTH + iX1;
           
            for( j = iY2 - iY1 ; j > 0 ; j-- )
            {
                pwTemp = pwAddr + iTemp;
                FastWordMemSet( pwTemp, usColor, iCount );
                iTemp += SCREEN_WIDTH;
            }
        }
        // 안 채우게 되어있으면 라인만 그린다. 클립핑은 DrawLine에서 해준다.
        else
        {
            DrawLine( stClippingRect, pvAddr, iX1, iY1, iX2, iY1, usColor );
            DrawLine( stClippingRect, pvAddr, iX1, iY2, iX2, iY2, usColor );
            DrawLine( stClippingRect, pvAddr, iX1, iY1, iX1, iY2, usColor );
            DrawLine( stClippingRect, pvAddr, iX2, iY1, iX2, iY2, usColor );
        }

 

6.3 Pixel 함수 

 Pixel 함수는 해당 위치에 RGB555 포맷으로 점을 찍는 역할을 한다. 

  1. /**
     점을 찍는다.
    */
    void DrawPixel( RECT stClippingRect, void* pvAddr, int iX, int iY,
            unsigned short usColor )
    {
        // 화면을 벗어나면 찍지 않는다.
        if( IsInRange( stClippingRect, iX, iY ) == false )
        {
            return ;
        }
       
     *( ( unsigned short* ) pvAddr + ( iY * SCREEN_WIDTH ) + iX ) = usColor;

 

6.4 한글/영문 출력 

 한글/영문 출력 부분은 01 NDS 한글 출력 라이브러리 를 참고하자.

 

7.설치 

7.1 Include 설치 

 devkitPro가 설치된 폴더에 libnds/include 폴더를 찾아서 하위에 window 폴더를 생성한뒤 첨부에 있는 include.zip 파일을 해제한다.

include_설치.PNG

<include 설치>

 

7.2 Library 설치 

 devkitPro가 설치된 폴더에 libnds/lib 폴더를 찾아서 libwindow.a 파일을 복사한다.

 

8.마치면서... 

 이상으로 윈도우 라이브러리 구성 및 구현 방법, 그리고 설치 방법까지 알아보았다. 실제 사용방법은 추후의 Tutorial을 통해 알아보도록 하자. 아래는  NDS 윈도우 시스템을 이용해서 만들어진 홈브루의 실제 화면이다.

화면5.PNG 화면1.PNG 화면2.PNG 화면3.PNG 화면4.PNG

 

9.첨부

 

 

 

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts