기본적인 DirectDraw 초기화가 완료되었으면 장면을 그릴 수 있다.
우선 이전에 설명했듯이 장면은 보조 표면 또는 오프스크린에 그려야 한다. 그리고 flip 또는 blit을 통해 미리 그렸던 장면을 보이게 하는 것이다. 따라서 우린 이전에 만들어 놓은 보조 표면(오프스크린)에 장면을 그릴 것이다.
IDirectDrawSurface7::Lock
보조 표면에 장면을 그리기 위해선 보조 표면에 대한 메모리 주소를 얻어와야 한다. 그리고 여기에 write을 하기 위해선 다른 곳에서 혹시나 해당 메모리에 접근하지 못하도록 적절한 제어가 필요하다. 이를 위해 DirectDraw는 Lock 함수를 제공한다. Lock 함수는 surface 객체를 통해 호출한다. Lock 함수의 역할은 해당 surface에 대한 메모리 주소를 얻고, 다른 곳에서 접근하지 못하도록 잠금을 거는 것이다. 만약 다른 곳에서 사용 중일 경우 독점권을 얻어올 때까지 wait 하는 것이 기본 동작이다.(즉 플래그를 통해 변경 가능) 독점권을 얻어 왔다면 해당 표면에 원하는 것들을 그리면 된다. 이 독점권은 Unlock 호출 전까지 유지된다. 즉 Lock을 통해 얻은 주소는 Unlock 호출 후 무효한 주소가 된다.
IDirectDrawSurface7::Unlock
해당 표면에 대한 작업이 끝나면 Unlock을 호출한다. 즉 표면에 대한 작업 완료를 의미한다. Unlock 호출 시 Lock을 통해 얻었던 surface에 대한 주소는 무효한 값이 된다. 따라서 Unlock 후 더 이상 해당 표면에 작업을 할 수 없다.
IDirectDrawSurface7::Blt
Bit block transfer의 줄임말로 Blit, Bitblit, blt 모두 동일한 의미이다. 해석하면 비트 블럭 전송으로 bit로 이루어진 블럭, 즉 비트맵과 같은 데이터를 전송한다는 뜻이다. Blt은 source 메모리의 Rect로 정의된 영역의 비트 블럭을 destination 메모리의 Rect로 정의된 영역에 전송한다. 해당 함수를 호출하는 인스턴스가 destination 표면이 된다. source와 destination 메모리 주소는 비디오 메모리이든 시스템 메모리든 상관없이 blit이 가능하다. 해당 함수는 기본적으로 async(비동기) 적이다. 즉 결과를 즉시 반환한다. 플래그를 사용하여 synchronized(동기)로 변경할 수 있다. 그러면 당장 blit이 불가능할 경우 가능할 때까지 wait 상태가 된다.
정리해보면 Drawing은 다음과 같은 절차를 거친다.
// 보조 표면에 잠금을 겁니다.
pDDBack->Lock(...);
//////////////////////////////////
// 보조 표면에 원하는 작업을 합니다.
//////////////////////////////////
// 작업이 끝났다면 잠금을 해제합니다.
pDDBack->Unlock(...);
// 주 표면에 보조 표면을 blit 합니다.
pDDPrimary->Blt(...);
HRESULT Lock( LPRECT, LPDDSURFACEDESC2, DWORD, HANDLE );
: 첫 번째 인자는 RECT* 타입을 받는다. 해당 표면의 RECT 영역만큼 잠금이 된다. 이 값이 NULL이면 표면 전체가 잠금이 된다. 일부 영역만 lock을 걸었을 경우 영역 밖의 access는 예기치 못한 결과를 발생시킬 수 있다. 두 번째 인자는 표면을 묘사하는 DDSURFACEDESC2* 타입의 값이다. Lock 함수 종료 시 표면 정보가 여기에 저장된다. 대표적인 정보들로는 해당 표면의 메모리 주소(lpSurface), 너비와 높이(dwWidth, dwHeight), pitch(lPitch) 정도가 있다. write을 위해 표면에 접근하고 싶을 땐 lpSurface를 이용하면 된다. 그리고 너비와 높이가 있다. 여기서 focusing할 대상은 width와 pitch이다. width는 단위가 pixel이다. width는 픽셀 당 비트 수(bpp)와 상관없이 항상 일정하다. 즉 width가 1080일 경우 8bit 표면이든 24bit 표면이든 width가 1080인건 변함이 없다. 만약 lpSurface가 0x0010이라 가정하자. width가 1080이고 한 픽셀은 3bytes(24bit)이다. 두 번째 행의 첫 주소에 접근하려면 lpSurface로부터 얼마를 이동해야 할까?
1개의 행에 픽셀이 1080개고 한 픽셀당 3bytes 이므로 두 번째 행의 시작 주소는 0x0010 + (1080 x 3bytes) 로 보인다. 하지만 실제론 이와 다를 수 있다. 즉 위에 표현된 표면은 추상적인 형태의 표면으로 실제로 메모리에 저장되는 표면과 다를 수 있다. 예를 들면 메모리 정렬(memory alignment)도 그 이유 중 하나이다. 즉 적절한 메모리 규격(?) 유지를 위해 패딩 데이터가 더해질 수 있으므로 기존의 width와 다를 수 있다. 또한 아래와 같이 예약된 캐시 메모리 공간의 영향을 받을 수도 있다.
이를 위해 pitch라는 값이 존재하며, pitch는 이러한 결과를 모두 반영하여 최종적으로 너비가 몇 byte인지를 나타낸 값이다. 즉 pitch의 단위는 byte이며, 각 행의 시작 주소에 pitch를 더하면 다음 행의 시작 주소에 도달하게 된다. pitch는 width x 픽셀 당 바이트 수와 같을 수도, 다를 수도 있다. 어쨌든 같던 다르던 표면에서 위치 이동 시 pitch를 사용해야 한다.
두 번째 인자 설명이 길었다. 세 번째 인자는 lock과 관련된 행위를 조정하는 플래그이다. 아까 처음에 lock은 기본적으로 곧바로 surface에 대한 포인터를 획득할 수 없다면 리턴한다고 했다. 만약 포인터를 획득할 때까지 waiting을 시키고 싶다면 DDLOCK_WAIT를 사용하면 된다. (대부분 사용하는 듯 하다.) 이외에도 다양한 플래그가 있으니 여기를 참고.
네 번째 인자는 현재 사용되지 않으므로 반드시 NULL이여야 한다. 해당 함수는 성공 시 DD_OK를 반환한다.
HRESULT Unlock( LPRECT );
: RECT* 타입을 인자로 받는다. 해당 표면의 RECT 영역만큼의 잠금을 해제한다. 이 RECT는 Lock에서 사용했던 영역과 동일해야 한다. 만약 이 값이 NULL일 경우 표면 전체로 인식한다. 즉 Lock에서 RECT에 NULL을 전달했다면 해당 인자에도 NULL을 전달해야 한다. 성공 시 리턴값은 DD_OK다.
HRESULT Blt( LPRECT, LPDIRECTDRAWSURFACE7, LPRECT, DWORD, LPDDBLTFX ) ;
: 해당 함수는 destination surface(목적지 표면)가 호출해야 한다. 첫 번째 인자는 목적지 표면의 영역을 정의하는 RECT의 포인터 타입이다. 이 값이 NULL이면 표면 전체가 타겟이 된다. 두 번째 인자는 source surface다. 이 표면의 영역이 목적지 표면으로 전송(복사)된다. 세 번째 인자는 source surface의 영역을 정의하는 RECT의 포인터 타입이다. 이 값이 NULL 이면 source surface 전체가 타겟이 된다. 네 번째 인자는 플래그를 지정한다. 컬러키(color key) 관련 등 여러 플래그가 있다. 그 중에서 Blt가 성공할 때까지 기다리는 DDBLT_WAIT는 꼭 써주는 것이 좋다. 나머지 플래그는 여기를 참고. 마지막 인자는 모양과 관련된 값을 설정해주는 구조체이다. 필요 시 위 링크에서 참고. 필요없다면 NULL을 사용. 성공 시 리턴값은 DD_OK다.
'게임 공부 > DirectX' 카테고리의 다른 글
DirectDraw 학습 (4) - 사각형 이동시켜보기 (1) | 2020.11.23 |
---|---|
DirectDraw 학습 (3) - Set pixel (0) | 2020.11.21 |
DirectDraw 학습 (1) - Initialization (0) | 2020.11.15 |
행렬 변환에 대해 알아보자 (0) | 2020.07.05 |
D3DXCreateTextureFromFileEx 함수의 color key (0) | 2020.06.23 |
댓글