'framebuffer'에 해당되는 글 1건

  1. 2007/04/23 프레임 버퍼 컨트롤하기 (2)

참고로 말씀드리면 http://www.double.co.nz/nintendo_ds/nds_develop2.html (새 창으로 열기) 보고 따라하고 있습니다. 내가 나중에 볼려고 쓰다 보니 말투가 이랬다 저랬다 하는군요. 양해바랍니다.

DS상의 각각 스크린은 다양한 다른 모드로 설정할수 있는데.. 각각의 모드는 장단점이 있지만
프레임버퍼 모드가 직접 그릴수 있는 가장 간단한 방법이라고 합니다.
-. 프레임버퍼
프레임버퍼는 스크린과 맵핑된 메모리영역인데.. 그 메모리 영역에 데이타를 쓰면 스크린상에 결과가 나타납니다. 이 모드를 사용할때 각각의 픽셀은 2바이트로 표현됩니다.
C 언어상으로 16bit unsigned integer와 상응하는 그 데이타는 555 포맷으로 표현됩니다.
이 555 포맷으로 직접 색을 변환할 필요는 없고 RGB15 라는 바로 쓸수 있는 매크로 함수가 있습니다. 각각의 픽셀은 red, green, blue로 표현 되는데 0 에서 31까지의 범위를 갖습니다. 0 은 색이 없는 것이고 31은 최대 컬러값 입니다. 예를 들자면 아래와 같습니다.
RGB15 Color
RGB15(31,0,0) Red
RGB15(0,31,0) Green
RGB15(0,0,31) Blue
RGB15(0,0,0) Black
RGB15(31,31,31) White

아래 코드는 프레임버터의 시작 포인터에 blue 색을 지정하는 예입니다.
uint16* framebuffer = ...;
  for(int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; ++i)
    *framebuffer++ = RGB15(0,0,31);
닌텐도DS의 2D 하드웨서 가속기능을 바로 사용할수 있습니다.
하지만 프레임버퍼의 단점이라면 sprites, tiled maps, scrolling, etc 등을 모두 직접 코딩해야 한다는 것입니다.

-. 스크린
닌텐도 DS는 하드웨어적으로 대개의 스크린을 가지는데 아래 스크린만이 터치기능이 있는 스크린입니다. 메인 스크린과 서브 스크린이라고 부르는 각각의 스크린을 프로그래밍해줘야 합니다.
이 예제에서는 하드웨어의 위에 화면인 메인 스크린만을 사용할 것이다.
videoSetMode 라는 함수를 이용해아 모드를 설정할 것인데 이것은 double buffering, page flipping 같은 것을  할 수 있다. MODE_FB0 로 프레임버퍼를 사용할 것이다.
videoSetMode(MODE_FB0);
프레임버퍼의 메모리영역은 여러개의 VRAM 이라고 불리는 영역으로 설정되어 있다. 첫 VRAM 영역은 VRAM_A 로 불린다.
 vramSetBankA(VRAM_A_LCD);
-. shape 그리기
단색으로 간단한 사각형을 스크린에 그려볼 것이다.
void draw_shape(int x, int y, uint16* buffer, uint16 color)
{
  buffer += y * SCREEN_WIDTH + x;
  for(int i = 0; i < shape_height; ++i) {
    uint16* line = buffer + (SCREEN_WIDTH * i);
    for(int j = 0; j < shape_width; ++j) {
      *line++ = color;
    }
  }
}
프레임버퍼는 메모리상에 여러 행으로 배치된다. 그래서 200픽셀이상된다면 프레임버퍼상의
첫 200개의 unit16은 첫 스크린행이 되고  두번째 200개의 uint16이 두번째 행이 된다.
참고로 SCREEN_WIDTH 와 SCREEN_HEIGHT 는 ndslib에서 매크로로 제공하여 스크린 넓이와
높이를 반환한다.
shape_height 와 shape_width 는 테스트 목적으로 알맞은 정적변수로 변경가능하다.
static int shape_width = 10;
static int shape_height = 10;
 
-. shape 움직이기
스크린을 가로지르는 움직임을 표현하기 위해서는 현재 위치의 shape 를 지우고 새로운 위치에 다시 그릴 필요가 있다.
static int old_x = 0;
static int old_y = 0;
static int shape_x = 0;
static int shape_y = 0;
지우려는 배경색을 가진 (이 경우엔 검정) draw_shape 함수에게는 간단한 문제이다. 새로운 위치와 shape 색을 (이 경우엔 빨강) 지정하고 다시 이 함수를 호출하면 된다.
draw_shape(old_x, old_y, VRAM_A, RGB15(0, 0, 0));
draw_shape(shape_x, shape_y, VRAM_A, RGB15(31, 0, 0));
여기서 주의할 점은 프레임 버퍼를 초기에 설정했던 VRAM_A 값을 넘겨줘야 한다는 것이다.

간단한 main 함수로 만들어서 실행해보면 비스듬한 모양이 화면을 빠르게 가로지르는 것을 볼수 있을 것이다.

코드보기..

-. 좋지 않은 결과

사용자 삽입 이미지
DeSmuME 로는 보이지 않아서 iDeaS 에뮬을 사용했습니다.

 -. Vertical Blank Interrupt
비스듬한 모양으로 보이는 이유는 스크린 표시방식 때문이다. 하드웨어 장치는 매 1/60초 마다 다시 그린다. 각각의 픽셀과 행과 행을 찾아 다니며 프레임 버퍼의 내용을 하드웨어 스크린 픽셀에 복사한다.
이런 작업이 일어나는 동안, main 내부에서 스크린에 써진 프레임버퍼 내용을 변경한다. 그래서 하드웨어가 지우기전에 막 다시 그린다면, 즉시 지워지지 않을 것이다.하드웨어가 그리기 바로전에 새로운 shape를 그린다면 전에 shape의 부분과 새로운 shape의 부분이 남아 있을 것이다.
고맙게도 하드웨어는 스크린에 그리는 것을 끝마쳤을때 우리에게 알려주는 방법을 가지고 있다.
이것을 Vertical Blank Interrrupt 라고 부른다. 우리는 이것이 발생할때 불려지는 함수를 기록할수 있다.
이 인터럽트는 또다른 뭔가를 하도록 빠르게 함수를 호출하기위해 현재(예에서 main 함수에서 루프안에서 실행되는 동안) 행해지는 것을 막을수 있는 하드웨어 매커니즘이다.  인터럽트 함수가 반환 되었을때 전에 활동이 결코 막지 않는다면 계속 진행될 것이다.
전에 본것 같은 그리는 문제를 막기 위해서는, 하드웨어가 스크린상에 프레임버퍼의 내용을 넣지 않을때 한번에 프레임버퍼에 그리기를 원한다. 최고의 타이밍은 vertical blank interrupt 하는 동안이다.

-. 인터럽트 셋업
인터럽트 작동 방법에 관한 자세한 내용은 나중에 다루겠지만, 인터럽트 코드에서 발생하는 것을 간단하게 개요를 말하겠다.
먼저 인터럽트가 발생했을때 우리가 호출하길 원하는 함수를 닌텐도 DS에 알려줄 필요가 있다.
void InitInterruptHandler()
{
  REG_IME = 0;
  IRQ_HANDLER = on_irq;
  REG_IE = IRQ_VBLANK;
  REG_IF = ~0;
  DISP_SR = DISP_VBLANK_IRQ;
  REG_IME = 1;
}
이 간단한 코드에서 우린 단지 VBlank 인터럽트 발생을 원한다. on_irq 함수는 그것이 발생했을때 알려 줄것이다. on_irq 함수는 main 함수에서 앞에서 했던 프레임버퍼에 그리는 것을 할 것이다.
void on_irq()
{
  if(REG_IF & IRQ_VBLANK) {
    draw_shape(old_x, old_y, VRAM_A, RGB15(0, 0, 0));
    draw_shape(shape_x, shape_y, VRAM_A, RGB15(31, 0, 0));

    // Tell the DS we handled the VBLANK interrupt
    VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
    REG_IF |= IRQ_VBLANK;
  }
  else {
    // Ignore all other interrupts
    REG_IF = REG_IF;
  }
}
이 함수의 주요 부분은 main 함수의 while 루프에서 전에 했던 그리기이다. 이 함수의 나머지는 인터럽트를 다루는 것이다.
우린 또한 VBLANK 인터럽트를 다룰 DS 하드웨어에게 알려줄 필요하다. 나중에 이유를 설명하겠지만 이것은 나중에 사용하여 호출할 swiWaitForVBlank를 요구한다. 그 코드는 다음과 같다.
// Tell the DS we handled the VBLANK interrupt
    VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
    REG_IF |= IRQ_VBLANK;

-. 여전히 좋지 않은 결과
우리는 main 함수에서 그리는 코드를 제거 할수 있다.

코드보기..

불행히도 실행에는 문제가 있다. 체커보드 모양이 스크린상에 반복에서 나타난다.
다행이도 이유는 명확하다. 하드웨어는 vertical blank 인터럽트가 발생할때 매 1/60초마다 on_irq 함수를 호출합니다. shape 가 지워지고 다시 그려질때 입니다.
shape가 그려질 좌표가 증가할때 가능한 빠르게 실행됩니다. on_irq 루틴이 호출되기전 50회가 될것입니다. 그 결과로 그리는 루틴은 마지막으로 shape가 그려진후 50번 갱신된 old_x, old_y 에서의 shape를 지운다. 그래서 잘못된 영역을 지운다.

-. 올바른 결과
우리는 인터럽트가 발생할때 까지 sleep 하도록 while 루프에 요청하는 것으로 고칠수 있다. 이것은 while 루프가 덜 바쁘게 만드는 부가적인 효과를 얻을수 있다. ARM8 프로세서는 인터럽트가 발생할때 까지 효과적으로 속도를 줄일수 있다. 이 함수가 swiWaitForVBlank 이다.
VBLANK 인터럽트를 처리하여 register를 세팅하는 on_irq 내부를 앞서 기억할 것이다. 만일 register를 세팅하지 않았다면 swiWaitForBlank는 결과 발생하지 않을 인터럽트를 핸들링하기 위한 통지를 기다리며 하드웨어는 hang 걸릴 것이다.
예제 프로그램에 이 한 라인을 추가해야 한다.

코드보기..

인터럽트 부분을 추가한 소스는 컴파일시 에러가 났다. Makefile이 최신버젼의 devkitpro와 맞지 않는 것 같다. 컴파일 되어 있는 nds 파일을 dualis 에서 실행해 봤다.

사용자 삽입 이미지


BLOG main image
닌텐도 DS 관련해서 Palib를 소개하고 제가 개발한 홈브류와 다른 개발자의 홈브류를 소개하고자 합니다. NDS 자체 제작(Homebrew)에 관심있는 다른 분들의 길잡이가 되고 싶습니다. by 버들피리

카테고리

전체 (36)
따라하기 (5)
PA_lib 소개 (10)
Homebrew 소개 (7)
나의 Homebrew (12)
기부하기(Donate) (1)
Wii (1)
Total : 66480
Today : 43 Yesterday : 69