본문 바로가기

프로그램

[펌] DirectDraw - Blit, Flip, RECT, DDBLTFX, Lock

출처 : http://blog.empas.com/uuzazuk/19664434

Blit, Flip, RECT
, DDBLTFX 그리고 Lock

게임 제작에서는 복사를 자주 하게됩니다. 왜냐하면, 단 1개의 표면으로 모니터에 그림을 나타내는 것이 아니라 우선 2차, 3차 표면에다가 그림이나 캐릭터를 준비해 두었다가 나중에 1차 화면으로 그림들을 보내어 우리가 눈으로 볼 수 있게 하는 방식을 쓰기 때문입니다. 따라서 이제는 표면 복사 개념을 가진 함수인 Blit와 Flip - 그리고 이와 같은 효과를 가지는 Lock 함수 - 을 공부해야 하며, 또 이 과정에서 사용되는 RECT와 DDBLTFX라는 구조체도 알아 두어야 합니다.

1. Flip에 대하여
쉬운 개념부터 먼저 정리한다. 결론적으로 Flip 함수는 오로지 2차 표면에 들어가 있는 그림 내용을 - 눈에 보이는 화면 즉, 모니터 화면으로 이해하여도 될 - 1차 표면으로 복사해 주는 함수로써 사용법은 다음과 같이 아주 간단하다.

) lpPrimary-> Flip(NULL, DDFLIP_WAIT):
앞에는 1차 표면을 나타내는 lpPrimary 객체가 나와, 그림 자료를 - 2차 표면에서 - 1차 표면으로 복사해 준다는 말이 되는데, 첫번째 매개변수의 NULL이란 뜻은 그림 자료 '전체' 크기를 전달하겠다는 의미이다. 그런데, 매개변수 안에는 2차 표면에 대한 언급 내용을 전혀 찾아 볼 수 없다. 그 이유는 2차 표면의 크기는 1차 표면과 같기에 - 이들이 이처럼 내부적으로 연관 되어있으므로 - 2차 표면에 대한 언급이 없다.

2. Blit에 대하여
한편, 3차 표면의 그림들을 2차 표면으로 복사해 주는 함수는 Blit인데, Blit해 주는 함수 종류로는 Blt 함수와 Bltfast 함수가 있다. 이들의 복사 과정은 다소 다르며, 약간은 복잡하다고 할 수 있겠다.

◈ 참고 : Blt 함수와 Bltfast 함수는 표면 복사에 제한이 없다
Blt 함수와 Bltfast 함수는 일반적으로 3차 표면의 그림을 2차 표면으로 복사시키는데 사용된다. 그러나, 때로는 2차 표면에서 1차 표면으로 복사할 때도 사용되는 등 이들 함수는 사실상 표면 복사에 제한이 없다. 따라서 3차 표면에서 다른 3차 표면으로 그림 자료를 복사할 수도 있고, 또 3차에서 바로 1
차 표면으로도, 심지어는 1차 표면에서 2차 표면으로 복사할 때도 사용된다.
1) Blt와 Bltfast의 차이점
예) lpSecondary-> Bltfast(100, 200, &lpThird, &Srect, DDBLTFAST_WAIT);
예) lpSecondary-> Blt(&Drect, lpThird, &Srect, DDBLT_WAIT, NULL);

이 Blt 함수와 Bltfast 함수는 3차 표면(lpThird)에 들어가 있는 그림 자료를 2차 표면(lpSecondary)으로 복사해 준다. 그러나 차이점은 - 매개변수들을 통해서도 알 수 있듯이 - Bltfast 함수는 그림 크기 조절이 되지 않는 반면, Blt 함수는 그림 크기 조절이 가능하며 또 다양한 효과도 함께 나타낼 수 있다는 점이다.

2) Bltfast 함수의 사용
예) lpSecondary-> Bltfast(100, 200, &lpThird, &Srect, DDBLTFAST_WAIT);

결론은 3차 표면(lpThird)에 있는 그림 자료를 2차 표면(lpSecondary)으로 복사해 준다는 점이다. 즉, 3차 표면(lpThird)에 있는 Srect 크기의 사각형을 2차 표면(lpSecondary)의 100, 200의 픽셀 위치에다가 복사해 준다.

이 함수는 Srect라는 사각형을 그대로 모셔다가 2차 표면의 좌표 100, 200에다가 복사만 해 주기 때문에 사각형 크기에는 변화가 없다. 이처럼 간단한 내용이므로, 속도면에서도 Blt 함수에 비해 10% 정도 빠르다고 한다. (DDBLTFAST_WAIT 라는 flag는 일단 중요하지 않다.)

3) Blt 함수의 사용
그러나, Blt 함수는 - 속도면에서 Bltfast 보다 다소 느리지만 - 다음과 같은 다양한 기능을 나타낼 수 있다.

예) lpSecondary-> Blt(&Drect, lpThird, &Srect, DDBLT_WAIT, NULL);

3차 표면(lpThird)에 있는 그림 자료를 2차 표면(lpSecondary)으로 복사해 준다는 점은 Blt 함수와 같다. 그렇다면 차이점은 무엇일까?

3차 표면(lpThird)에 있는 Srect라는 크기의 사각형을 복사한다는 점은 앞의 Blt 함수와 같지만, 다른 점은 2차 표면(lpSecondary)의 - 좌표가 아니라 - Drect라는 크기의 사각형으로 복사시킨다는 점이다. 이들 2개의 사각형 - 즉, 3차 표면의 Srect와 2차 표면의 Drect는 - 크기가 같을 수도 있지만, 사용자의 의도에 따라 크기를 달리 할 수도 있기 때문에, 결국 그림의 확대 또는 축소가 가능하다는 말이 된다. 정리하자면 Srect는 "복사시킬 소스(Source) 사각형"을 말하며, Drect는 "복사가 되는 목적(Destination) 사각형"을 말한다.

Blt 함수를 사용하여 변화를 줄 수 있는 또 하나의 것은 맨 끝에 있는 매개변수의 처리다. 그냥 NULL을 사용한다면 변화는 없다. 그러나, NULL 대신에 다른 flag값 - 흔히 fx으로 표현하고 있는데 - 으로 바꾸어 쓰면 "사각형에 색상채우기", "이미지의 반전" 등의 효과도 나타낼 수 있다.

4) RECT 구조체
위에서 rect라는 용어를 보았다. 이것은 RECT라는 구조체로써, 사각형을 만드는데, 많이 활용되고 있다.

RECT 구조체의 원형

typedef struct _RECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;

직관적이며, 이해하기 쉽다. rect는 "사각형(rectangle)"을 뜻하며, 사각형 영역을 만들기 위해 4개 점이 사용된다. 그런데 이때 숫자상의 차이가 있다. 즉, (10,10, 319, 249)와 같은 크기를 가진 일반적인 사각형을 RECT 구조체로 표현할 때는 (10,10, 320, 250)와 같이 1이 더해진다. 또한, 사각형의 가로 길이를 계산할 때 보통은 x2-x1 + 1과 같이 계산하지만, RECT 구조체를 사용할 때는 right - left와 같이 계산하므로 숫자상에 차이가 생길 것이다.

rect 구조체에 값을 넣는 - 즉, 초기화시킬 때는 - 다음과 같은 방법 3가지가 사용된다. 흔히 RECT rect; 처럼 구조체 변수를 선언하게 되는데,

① 선언과 함께 바로 초기화시키기 (관련 소스1), (관련 소스2)
RECT rect = {10, 10, 300, 200};
② SetRect 라는 전용 함수를 사용하기
RECT rect;
SetRect(&rect, 10, 10, 300, 200};

별도로 SetRect 함수를 사용하며 첫번째 매개변수에는 rect사각형 주소가 들어가는데, 이 형태가 결국 간단하기에 많이 사용되는 편이다.

③ RECT 구조체의 멤버변수에 값을 직접 넣기
RECT rect;
rect.left = 10;
rect.top = 10;
rect.right = 300; // 실제로는 left의 값을 더하여 표현한다.
rect.bottom = 200; // 실제로는 top의 값을 더하여 표현한다.

5) DDBLTFX 구조체
우리는 앞에서 GDI를 이용한 사각형 색상 채우기를 공부한 바 있다. 이번에는 이와 같은 효과를 DirectX의 Bltfast 함수를 통해 만들어보자. RECT 구조체와 함께 그 내부의 색상을 채우기 위해 DDBLTFX라는 구조체가 필요하다.

DDBLTFX 구조체의 원형

typedef struct _DDBLTFX
{
DWORD dwSize;
DWORD dwDDFX;
DWORD dwROP;
DWORD dwDDROP;
DWORD dwRotationAngle;
DWORD dwZBufferOpCode;
DWORD dwZBufferLow;
DWORD dwZBufferHigh;
DWORD dwZBufferBaseDest;
DWORD dwZDestConstBitDepth;
// 중략
DWORD dwFillColor;
} DDBLTFX, FAR* LPDDBLTFX;

자, 가로(99), 세로(99) 크기의 사각형에 힌색을 칠해보자.

① 사각형 내부에 힌색 칠하기
DDBLTFX fx;
RECT Drect = (0,0,100,100);
DDBLTFX 구조체의 변수 fx를 선언하고, 99, 99 크기의 사각형으로 초기화시켰다. 여기에서 필요로 될 DDBLTFX 구조체의 멤버변수는 dwSize와 dwFillColor 이므로

fx.dwSize = sizeof(fx);
fx.dwFillColor = RGB(255, 255, 255);
dwSize에는 구조체의 크기를, dwFillColor에는 사각형 내부에 들어갈 색깔을 넣게 되는데, 이미 앞에서 RGB 매크로에 대해서 공부한 바 있다. RGB(255, 255, 255)는 힌색이, RGB(0,0,0)은 검은색이 칠해 질 것이다.

lpSecondary -> Blt (&Drect, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &fx);
Blt 함수의 맨 끝의 매개변수에 fx의 주소를 위처럼 적어 주면 - 3차 표면에서 2차 표면으로 - 힌색 사각형이 복사될 것이다. 색깔 채우기와 관련하여 그 앞쪽 매개변수에는 DDBLT_COLORFILL라는 flag도 또한 첨가시켜 주어야 한다. (나중에 flip 함수를 사용하면 좌표 0, 0부터 시작되는 작은 크기(99,99)를 가진 내부가 힌색으로 채워진 사각형을 모니터로 볼 수 있게 될 것이다.)

그림을 좌우반전 시키기.(관련 소스1)
이번에는 그림을 좌우 반전 시키는 효과를 나타내 보자. 즉, 한 번 그렸던 그림을 통해 바로 좌우로 반전 시킬 수도 있는데 이러한 기능들은 DDBLTFX 구조체에 들어가 있다. 이번에는 사용하게 될 2개의 사각형을 SetRect 함수로 표현해 보자.

DDBLTFX fx;
RECT Srect, Drect;
DDBLTFX 구
조체 변수로 fx라고 선언하고, 소스와 목적 사각형을 각각 Srect, Drect라는 이름으로 변수 선언을 한 다음

SetRect(&Srect, 600, 500, 700, 600);
SetRect(&Drect, 10, 50, 110, 150);
사각형의 초기화를 시킬 때 SetRect 함수를 사용했다. 이때 사용되는 2개의 사각형 크기는 모두 가로, 세로 100 픽셀의 크기이다.

fx.dwSize = sizeof(fx);
fx.dwDDFX = DDBLTFX_MIRRORLEFTRIGHT;
dwSize 멤버변수에는 - 앞의 경우과 같이 - 구조체의 크기를 넣어주는데 이번에는 dwDDFX 라는 새로운 멤버변수가 활용된다. 그리고, 또한 넣어주어야 할 것은 좌우 반전의 기능을 가진 DDBLTFX_MIRRORLEFTRIGHT 라는 것이다. 그리고,

lpSecondary -> Blt (&Drect, lpThird, &Srect, DDBLT_DDFX | DDBLT_WAIT, &fx);
앞의 내용들과 모두 같은데 다른 점이란 - 사각형 내부에 색을 넣기 위해 DDBLT_COLORFILL 라는 flag값이 사용되었던 것처럼 - 좌우 반전을 위해 DDBLT_DDFX 라는 값이 필요하다는 것이다. (DDBLT_WAIT라는 flag의 기능은 내가 넣어준 정보들을 - 무시하지 말고, 컴퓨터 속도에 동조되도록 - 계속 대기시켜 두어 사용하겠다는 뜻이다.)
관련 소스2.
관련 소스3.
관련 소스4.
관련 소스5.
관련 소스6.

3. Lock 함수
앞에서 3차 표면의 자료를 2차 표면으로 보낼 때 Blt나 Bltfast 함수가 사용되며, 2차 표면에서 1차 표면으로 보낼 때는 Flip이 사용되는 것을 공부해왔다. 그러나 때로는 - 이 Blt나 Bltfast 함수 대신에 - Lock 함수를 사용하기도 하는데 왜 Lock 함수를 사용하며, 또 그 원리는 무엇일까?
◈ 참고 : Lock에 대해서
Lock은 '잠금'의 의미이다. 다른 것이 접근하지 못하도록 화면을 잠근다는 뜻이다. DirectX는 표면에 접근하는 것을 쉽게 허용하지 않는다. 그러기 때문에 보통 3차 표면을 만들고, Blt나 Bltfast 함수를 통해 자료를 2차 표면으로 복사하는 방법을 사용하게 된다. 그러나, Lock 함수를 사용한다면 예외가 된다.

Lock 함수의 방법이란 - Blit의 "복사" 방식이 아니라 - 잠그고 싶은 (어떤 종류의 표면이라도 가능하다고 판단되는데) 일부 또는 전체 (NULL로 표현)를 다른 것이 접근하지 못하도록 표면 자체를 잠그어 놓고는 내가 직접 그 표면을 마음대로 사용하는 방식이다. 대부분의 경우가 그렇지만 포인터를 사용하여 표현한다. Lock을 했던 표면은 나중에 Unlock 함수를 통해 다시 해제시켜 주어야 한다. Lock의 이러한 잠금의 기능은 다른 영역에도 많이 쓰이는 듯하다.

Lock 함수가 사용된 소스1, 소스2, 소스3
Lock 함수의 흐름은 다음과 같다. 우선 전역변수로 WORD *BOX;처럼 선언 해둔다. 즉, 내가 잠그고자 하는 표면을 가리키는 그 첫 주소로 - 즉, 메모리 포인터 - BOX라고 선언해 두자. BOX는 화면상에서 일단 한 개의 줄을 나타내고 있다. Lock 함수의 흐름은 다음과 같다.
◈ 참고 : Lock된 주소의 데이터형
화면 해상도 결정을 8비트로 했을 경우에는 BYTE형, 16비트 모드인 경우는 WORD, 32비트 모드일 때는 DWORD형이 사용된다. 여기서는 640* 480, 그리고 16비트로 설정한 경우이므로 WORD가 사용되었다.
DDSURFACEDESC2 ddsd;
ddsd.dwSize = sizeof(ddsd);
lpSecondary->Lock( NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );
표면을 잠그어야 하기에 DDSURFACEDESC2 구조체가 필요하며, 또 - 항상 그렇듯이 - 구조체 크기도 dwSize 매개변수에 알려주어야 한다. 여기서는 lpSecondary 즉, 2차 표면을 Lock하려고 하며, 첫번째 매개변수는 흔히 NULL을 사용하는데 전체 표면을 그 대상으로 하겠다는 뜻. 3번째 매개변수의 WAIT와 SURFACEMEMORYPTR에서 볼 수 있듯이 해당 표면에 대한 접근 허용에 있어서 Lock이 될 때까지 기다리다가, 성공하면 바로 표면 객체의 포인터(PTR)를 리턴하라는 뜻. 그리고 다음 내용으로 이어지는데.

// 이하 내용의 설명에 문제가 있는 것 같군요! 2002년 11월 9일
BOX =
(WORD *)ddsd.lpSurface;
이제 DDSURFACEDESC2 구조체에서 표면을 담당하는 lpSurface 매개변수를 BOX에 넣어둔다. 따라서 BOX라는 포인터를 실제로 얻게되면서 (WORD형 포인터인) BOX가 활용될 것이다.
참고 : 메모리의 좌표 계산
포인터는 배열로 표현이 가능하기에 결국 BOX[0]의 표현이 가능하며, 또 메모리 포인터의 시작 번지는 일단 (0,0)의 위치를 가리키게 된다. 즉, BOX[0]과 같은 의미이다. 또한, BOX[1]은 좌표로 (1,0), BOX[639]란 좌표로 (639,0)의 픽셀을 가리키는데 - 객체 표면 메모리 포인터 구조상 - BOX[640]이란 - 0부터 계산되므로 - 세로로 1줄 내려가 시작하니 좌표는 (0,1)이 되지만 offset값은 (0~639)에서 계속이어져 이 경우는 640이 된다. 따라서 BOX[641]이란 좌표 (1,1)이 되며 offset 값은 641이 된다. (offset값의 계산은 메모리 구조가 - 눈에 보이는 사각형이 아니라 - 이처럼 1개의 선(즉, "선형" 방식)으로 계속 이어져 있음을 확인시켜 주고 있다.)
for(int y = 0; y < 480; y++)
{
memset(BOX, 0, 640);
BOX += ddsd.iPitch;
}
iPitch란 - 역시 DDSURFACEDESC2 구조체에 있는 매개변수로써 - 픽셀과 픽셀 사이의 거리를 뜻한다. (모니터 해상도의 단위로 흔히 도트 피치 0.28mm라는 표현을 많이 들을 수 있을 것이다.) 이 iPitch를 Lock 시키고자는 하는 표면의 시작 주소에 넣으면서 계속 증가시키기에 - 선형 구조로 되어있는 메모리 즉, 여기서는 2차 표면인 - 640* 480의 크기까지 화면이 계속 잠그어지게 될 것이다.

참고로 저는 ddsd.dwWidth와
ddsd.dwHeight 매개변수을 사용하지 않고 화면 크기인 480, 640이란 값을 직접 넣었다.

lpSecondary->Unlock( NULL));
이처럼 잠그었던 표면 전체(NULL)를 해제시켜준다.

4. 5:5:5 모드와 5:6:5 모드
16비트 모드를 사용할 때 알아두어야 할 내용이다. 잘 아시다시피 256칼라란 8비트 모드 즉, 1점을 나타낼 때 8개 색상을 동시에 사용하는 것이다. 따라서
16비트 모드란 - 예: 640*480, 16비트 모드 - 1개 픽셀의 색상을 나타날 때 동시에 16개 색상을 사용하는 것이다.

그런데, 16비트에서는 다시 2가지의 모드 즉, 5:6:5 모드와 5:5:5 모드로 나뉜다. 이것은 카드 종류에 따라 분류가 되는 것으로 일반적인 카드는
5:6:5 모드를 사용하지만, 3D 가속 기능이나 TV 수신 카드인 경우는 - 이 중에서 Green 색상에서 1비트를 버리고 사용하므로 - 5:5:5 모드를 사용한다.

따라서 - 일반적인 경우에는 문제가 없겠지만 - Lock 함수를 사용하여 메모리 포인터에 직접 그림을 출력하는 경우라면 - 이러한 모드에 대해 알고 있어야 한다. 따라서 Lock 함수를 사용했을 경우에는 - "점찍기"를 통해 - 위에서 예를 들었던 내용을 사용하여 표현한다면 BOX[0]의 주소값을 아래와 같이 점검하므로써 해당 모드를 구별할 수 있다. 즉,

if (BOX[0] == 0xFFFF) // 좌표 (0,0)를 나타내는 번지 값이 0xFFFF라면
PixelMode = TRUE; // 5:6:5 모드이며,
else
PixelMode = FALSE; // 5:5:5 모드가 된다.

'프로그램' 카테고리의 다른 글

DDK 관련 자료들  (0) 2007.11.06
리눅스 그래픽 시스템  (0) 2007.11.06
객체지향 프로그래밍 튜터 자료  (0) 2007.10.30
인터넷 보안 10장  (0) 2007.10.30
인터넷보안 7,8,10  (0) 2007.10.23