* DirectDraw 의 소개 * DirectDraw 의 구조 * DirectDraw Objects * DirectDrawSurface Objects * DirectDrawPalette Objects * DirectDrawClipper Objects * DirectDraw 의 사용법(인터페이스) * DirectDrawCreate * IDirectDraw::SetCooperativeLevel * IDirectDraw::SetDisplayMode * Creating Flippable Surfaces * Defining the Surface Reqirement * System memory vs. Video memory * Creating the Surfaces - Primary Surfaces * Creating the Surfaces - BackBuffers * Rendering to the Surfaces(using GDI) * Writing and Flipping the Surfaces * Flipping Surfaces * Deallocating the DirectDraw Objects * DirectDraw 의 소개
다이렉트드로우는 다이렉트엑스의 구성요소 중의 하나로서, 그래픽 장치에 대한 직접적인 제어를 가능하게 하는 기능을 가지고 있다. 기존의 윈도우에서의 그래픽은 GDI(Graphic Device Interface)를 통하여 이루어져 왔다. 그러나 GDI를 사용하여 우리가 어떤 그래픽 작업을 수행하는 것은 비디오 메모리 같은 장치에 직접적으 로 출력을 하는 것이 아니라, 여러가지 그래픽 장치를 다루는 방법을 통일하기 위해 만든 새로운 가상의 장치 인 DC(Device Context)에 대한 출력이 행해진다. 물론, 결국 운영체제에서 위의 DC를 이용하여 실제 비디오 메모리와 같은 장치에 출력을 하게 된다. 즉, GDI를 이용하는 경우 당신이 어떤 출력을 한다면 그 효과는 운영체제에서 실제적인 작업을 할 때까지 지 연되게 된다. 다이렉트드로우를 사용하면 화면이나 프린터 같은 그래픽 장치에 직접 출력을 할 수 있다. 다이렉트드로우를 포함하여 다이랙트엑스전체는 COM이라는 기술을 이용하여 만들어진 것입니다. 이것은 매우 강력한 기술로 현재, 혹은 미래에 있어서 매우 중요한 역할을 할 것입니다. 빨리 익혀두시길.. 또한 위의 목차 또한 COM의 개념에 입각한 순서로 구성된 것입니다. * COM 을 사용하는 방법(DirectX를 이용하는데 필요한 수준) * DirectDraw 의 작동 구조
내부적으로 DirectDraw 는 오브젝트(객체)들로 만들어져 있다. 이중에서 프로그래머에게 노출되어 있는 것 은 다음의 4가지이다. COM에서 하나의 구성요소에는 여러개의 객체가 있을 수 있습니다.
* DirectDraw object * DirectDrawSurface object * DirectDrawPalette object * DirectDrawClipper object 참고로 Direct3D에서는 더 많은 객체가 사용된다.
* DirectDraw Objects
다이렉트드로우의 실질적인 인터페이스를 구현하는 객체입니다. 혹은 응용프로그램이 DirectDraw에 접근이 시작되는 부분에 있는 객체입니다. 이 오브젝트는 디스플레이 어댑터를 나타냅니다. 혹은(기술, 사상) 보통의 경우는 단 하나의 DirectDraw object(혹은 인스턴스) 만 있습니다. 왜냐면 디스플레이 어댑터가 하나 밖에 없기 때문입니다. 그러나, 그 개수에는 어떠한 제한이 있는 것은 아닙니다. 많은 수의 모니터/ 어댑터의 시스템들이 있기도 하기 때문입니다. 그럴 때는 한쌍의 모니터/어댑터 마다 하나 의 DirectDraw object의 인스턴스를 만들어 주면 될 것 입니다. DirectDraw object는 디스플레이 환경을 설정하는 메소드와 DirectDraw 내의 다른 객체를 설정하거나 생성 해내는 메소드를 가지고 있습니다. 그야말로 인터페이스 객체의 구현물이죠!(빨랑 COM을 배우세요^^)
서피스의 핵심은 임의의 메모리의 블록이란 사실이다. 그 외에도 수행하려는 작업을 편하게 하기 위한 몇 가 지의 멤버가 더 있다. 그럼 메모리의 블록을 왜 서피스라고 할까? 서피스는 그래픽 이미지를 저장하는 메모리 를 말한다. 또한 이 메모리 블록과 관련된 작업은 그래픽 작업 중의 하나임을 가정하는 것이다. 서피스에는 크게 몇가지 종류가 있다. * Primary Surfaces 프라이머리 서피스는 현재 모니터상에 나타나는 서피스이다. * Back Buffers 백버퍼는 현재 모니터상에는 나타나진 않지만 나타날 것으로 계획되는 서피스이다. * Offscreen Surfaces 오프스크린 서피스는 프라이머리 서피스, 백버퍼가 아닌 모든 서피스이다. 프라이머리 서피스와 백버퍼는 비디오 메모리 상에 존재해야 한다. 반면, 오프스크린 서피스는 비디오 메모 리나, 시스템 메모리 어느 측에라도 상주할 수있다. 이 점을 잘 이용해야한다. 즉, 비디오 메모리는 상당히 적 은 양으로 한정 되어 있으나 화면으로의 입출력 속도는 빠르다. 반대로, 시스템 메모리는 양은 풍부하지만 화 면으로의 입출력 속도는 비디오 메모리 보다 느리다. 프로그램을 할 때, 바로 프라이머리 서피스에 직접 쓰는 것 보다는 백버퍼에 그림을 그린 후에 그 백버퍼를 프라이머리로 지정하는 방법을 쓴다. 이처럼 프라이머리 서피스를 백버퍼 중에서 선택하는 작업을 플립핑이 라고 한다. 그렇게 하지 않는 다면, 화면상에 그림이 그려지는 동안 화면이 깜박거리거나 눈이 내리는 것을 보 게 될것이다.(하얀 점이 의도치 않게 나타나는 현상) 여기서 구현하는 인터페이스로는,
* DirectDrawPalette Objects 이 객체는 팔레트들을 서피스로 옮겨 붙이는데(attatch)사용된다. 서피스에 팔레트를 붙인다는 말은 그 서피 스에 저장된 이미지를 그릴 때 지정된 팔레트를 사용하게 한다는 것이다. 각각의 서피스는 자신만의 16- 혹은 256- 색깔 팔레트를 갖을 수있다. 이 이상의 수의 색깔을 사용하는 설정 에서는 실제적으로 팔레트를 쓰지 않는다. 팔레트를 쓴다면 오히려 메모리 낭비일 뿐이다. 서피스 마다 가지고 있는 팔레느는 프라이머리 서피스의 팔레트를 공유할 필요도 없다. 그러나, 각각의 서로 다른 형태의 픽셀 포멧과 팔레트의 정보를 하드웨어가 올바로 인식할 수있는 형태로 변환시킬 수 있어야한 다. 이것은 프라이머리 서피스로 블리팅시 필요할 수도 있다. 이러한 작업이 팔레트 객체에 구현 되어 있다. 물론, 서피스사이에 서로 같은 팔레트 하나를 공유하는 것도 가능하다. 이 때는 참조 카운팅수를 증감시키는 방법이 사용된다. 이 기법은 아주 많이 쓰이는 기법 중에 하나이다. 똑같은 팔레트 오브젝트(인스탄스)를 여러 서피스에 붙이면 된다. 이 작업을 attatch 라고 한다. 참조 카운터는 IDirectDrawSurface::SetPalette 메소드 를 사용하는 경우마다 1 씩 증가한다. 반대로 팔레트가 서피스로 부터 제거(release)될 때마다 1 씩 감소하 고 , 다 제거한경우 0이 된다.
Clipper objects 는 (풀 스크린이 아니라) 윈도우드 된 응용프로그램이 실행될 때 관계되는 오브젝트이다. 성 능향상을 위해, DirectDraw는 GDI를 이용치 않고(bypass) 직접 비디오 메모리의 프라이머리 서피스에 쓰기 를 행한다. 이 서피스가 다른 윈도우에 의해서 일부라도 겹칠 때, 디렉트드로우에서는 더이상 GDI를 사용할 수 없기 때 문에 쉽게 클리핑 처리가 불가능하다. 이때 관계된 서피스 윈도우 사이의 문제를 해결하기 위한 객체가 클리 퍼 객체이다. 특히, 이런 클리핑 기능은 hWnd를 DirectDrawClipper object에 추가하면 해결될 수있다. 이 후에 관련된 서 피스에 클리핑 오브젝트에 추가함으로써, 클립핑과 관계된 고민은 해결된다.
* DirectDrawCreate 이 함수는 DirectDraw Object 의 인스턴스를 만들어 그 객체의 인터페이스 포인터를 반환한다. 원래 COM 객체를 사용할 때는(즉, 클라이어트 측에서는), 가급적 C++을 사용하지만 실제로는 C, Pascall, 심지어는 어 셈블리 언어까지도 사용가능하다. 사실 COM 프로그래밍의 목적 중의 하나가 언어 독립성의 구현이다. IUnknown * lpUnknown; IDirectDraw * lpDD; HRESULT hr = CoInitialize(NULL); hr = CoCreateInstance(CLSID_DIRECTDRAW, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&lpUnknown); hr = lpUnknown->QueryInterface(IID_DIRECTDRAW, (void**)&lpDD); pUnkown->Release(); 의 순서를 밟게 되어있다. 위의 코드는 DirectDraw Object를 생성하고 그것의 사용자 인터페이스인 IDirectDraw 를 얻는다. 이 코드는 COM의 원리에 충실하지만 COM 개발자가 아닌 일반 유저에게는 좀 어렵 게 느껴진다. 그래서 다이렉트 드로우 오브젝트 인스탄스를 만들기 위해서 위의 코드를 매크로 함수로, 혹은 헬퍼함수를 정의 하여 제공한다 그것이 DirectDrawCreate 함수이다. 사용예는 다음과 같다. IDirectDraw *lpDD; int ddrval; ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
* IDirecDraw::SetCooperativeLevel 다이렉트 드로우의 수행 모드는 여러가지가 있다. 수행 모드는 다이렉트 드로우가 각 자원을 지배하는 방식 에 따라 결정된다. 이 모드를 선택하는 데에는 2개의 메소드가 사용된다. IDirectDraw::SetCooperativeLevel IDirectDraw::SetDisplayMode SetCooperaitveLevel 의 사용법은 다음과 같다. HRESULT ddrval; LPDIRECTDRAW lpDD; ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); 참고로 디스플레이의 해상도를 바꾼다는 것은 다른 프로그램(윈도우)의 권리를 침범하는 효과가 있는 작업 이므로, 디스플레이의 해상도를 바꾸기 전에는 적어도 DDSCL_EXCLUSIVE 와 DDSCL_FULLSCREEN 플랙 을 지정해주어야한다. 즉, 위의 설정은 디스플레이에 대한 전적인 권한을 다이렉트 드로우 오브젝트가 갖게 설정한다. DDSCL_FULLSCREEN 플랙을 세트하는 것은 응용프로그램이 전화면 모드에서 동작하도록한다. 비록 데스크탑은 게임응용프로그램을 실행시키는 중에 이용가능하지만(ALT + TAB 으로) 화면 만큼은 게임 응용프로그램에서 전적으로 사용한다. 만약 IDirectDraw::SetCooperativeLevel 이 DD_OK를 반환치 않으않아도, 게임프로그램을 계속 수행하게 할 수있다. 그러나 그런 것을 추천하고 싶지는 않다. 작성하는 응용프로그램은 이경우 풀스크린 모드가 아니 게된다. 또한 나중에 우리가 원하는 화면 쓰기가 대 부분 수행 되지 않을 것이다. 그래도 계속 응용프로그램을 수행 시키길 원한다면, 적어도 에러가 발생했음을 알리고 유저가 그 상황을 알게 끔해야한다. 그리고 가능하 다면그들이 응용프로그램을 끝낼지, 지속할 지를 결정케 해야한다. IDirectDraw::SetCooperativeLevel 의 한가지 필수사항은 게임프로그램이 갖는 윈도우로의 핸들(hWnd)을 인자로 넘겨주어, 당신의 프로그램이 비정상적으로 끝나는 경우, 다이렉트 드로우가 그 사실을 오퍼레이팅시 스템에 통보하는 메시지 구성에 필요한 정보를 제공하는 것이다. 예를 들면, 일반 보호 에러가 발생하고 GDI가 백 버퍼로 플립되었다고 하면, 최종 유저는 이후로는 결코 윈도 우의 화면을 다시 볼 수가 없게 된다. 이런 일이 일어나는 것을 방지키위해서, DirecDraw는 그러한 윈도우로 보내지고있는 메시지를 트랩하는 백그라운드 대기 프로세스 를 가지고 있다. 이 메시지를 사용하여 언제 응용프로그램이 종료되었는지 결정할 수가 있다. 즉, 당신이 하나의 다른 윈도우를 생성시킨다면, 나머지 하나를 액티브로 지정할 것을 잊으면 안된다. 안그러 면, 잘동작하지 않는 사항이 하나 둘이 아닐 것이다. 게임 응용프로그램의 종료시, GDI가 엉망이 될 가능성도 있고, ALT + TAB기능이 듣지 않는 사항도 발생할 수 있다.
* IDirectDraw::SetDisplayMode 응용프로그램의 동작을 설정한 후에는, 이 메소드를 수행시켜 디스플래이의 해상도를 바꿀 수 있다. 다음 예 는 디스플레이모드를 640'480'8 bpp 로 바꾸는 것을 보여준다: HRESULT ddrval; LPDIRECTDRAW lpDD; // already created ddrval = lpDD->SetDisplayMode( 640, 480, 8 ); 디스플레이 모드를 설정할 때, 최종사용자의 하드웨어가 어느 해상도까지 지원하는 지 알고 있어야 할 필요 가 있다. 이것을 알고 있다면 지원 불가능한 하드웨어 해상도와 칼라수가 지정된경우 무시하고 스탠다드모드 에서 프로그램이 돌아가게 하면된다. IDirectDraw::SetDisplayMode 는 수행에 실패한 경우, DDERR_INVALIDMODE 에러를 리턴한다. 이경우에는 IDirectDraw::EnumDisplayMode메소드를 사용하여 현재 기계상에서 가능한 모드를 알아낸후 그 모드로 다시 디스플레이 모드설정을 다시 시도 하도록한다.
* Creating Flippable Surfaces
디스플레이모드를 설정했으면, 응용프로그램이 출력을 행할 서피스들을 생성해야한다. IDirectDraw::SetCooperativeLevel 메소드로 풀스크린-익스클루시브 모드로 결정했다면, 서피스간의 플립 핑이 허용되는 서피스를 만들수 있다. 그러나, IDirectDraw::SetCooprerativeLevel 설정시 모드를 DDSCL_NORMAL로 결정했다면, 서피스간의 블리팅만이 가능한 서피스 밖에 만들 수 없다. 이 두가지는 속도 차이가 현저하다.(플리핑이 훨씬 빠름)
* Defining the Surface Requirements
플리핑 가능한 서피스 생성의 1 단계는 만들어 질 서피스의 정보를 올바르게 설정하는 것이다. 이 서피스에 관한 정보는 서피스 디스크립터(기술자)에 저장된다. * DDSURFACEDESC structure 다음 예는 이 스트럭쳐의 구조와 플리핑 가능한 서피스를 만들 때 지정해 줄 플랙 값들을 보여준다. // Create the primary surface with 1 back buffer ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; dwSize 멤버는 DDSURFACEDESC 스트럭쳐의 크기를 저장한다. 이렇게 하면 다른 DD 메소드가 적절치 않 은 멤버 에러를 가지고 리턴한 경우 이 값을 사용하는 것을 방지할 수 있다.( 추후 더 큰 값의 dwSize 멤버는, 이후의 DDSURFACEDESC 스트럭쳐의 확장을 허락한다.) dwFlags 멤버는 DDSURFACEDESC structure의 어느 필드에 기입을 할지를 결정한다. 즉, 일단 이 플랙을 세팅함으로써 다음에 준비하는 데이타가 써질 목적지를 정하는 것이다. 그 옛날의 VGA Graphic Adapter 의 포트를 프로그램 할 때, 한 쌍의 포트가 있고, 그 중 하나는 레지스터 선택에, 나머지 하나는 데이타 전달에 사 용한 것과 같은 원리이다. 이 경우는 dwFlags로 DDCAPS structure를 사용하고 백버퍼를 사용할 것이라고 설정한다. (DDSD_BACKBUFFERCOUNT) dwFlags 멤버는 예제에서 DDSCAPS structure를 사용하게 될 것임을 의미한다. 이 경우, 프라이머리 서피 스(DDSCAPS_PRIMARYSURFACE), 플리핑 서피스(DDSCAPS_FLIP), 복합 서피스(DDSCAPS_COMPLEX) 를 지시한다. 그리고 복합 서피스는 실제로는 1개이상의 서피스 모임을 말한다. 마지막으로, 예제에서는 백버퍼의 지정을 하고 있다. 백버퍼에 출력할 그래픽이 준비된이후에 백버퍼는 프라이머리 서피스로 플립되는 방식이다. 예제에서, 백버퍼의 갯수는 1개 뿐이다. 그러나 원한다면, 메모리가 허락하는 한 많은 수의 백버퍼를 사용할 수있다. 이러한 기능을 원한다면, 트리플 버퍼링 이라는 주제를 읽어보라.
* 시스템 메모리 vs. 비디오 메모리 서피스의 실제 메모리는 비디오 메모리 혹은 시스템 메모리에 할당 된다. DirectDraw는 디스플레이 메모리 가 고갈 되었을 때, 시스템메모리를 사용한다. 물론, 사용할 시스템메모리의 특정 주소를 지정하는 것도 가능 하다. 위처럼 비디오 메모리와 시스템 메모리를 섞어 쓸것인지, 아니면 어느 한 가지만 사용 할 것인지를 dwCaps 멤버를 설정함으로써 지정할 수 있다. DDSCAPS_SYSTEMMEMORY 혹은 DDSCAPS_VIDEOMEMORY로 지정한다. (만약 DDSCAPS_VIDEOMEMORY 로 설정 했을 때, 요청한 양의 비디오 메모리가 존재치 않으면, IDirectDraw::CreateSurface 는 DDERR_OUTOFVIDEOMEMORY 를 반환한다.)
* Creating the Surfaces - Primary Surfaces
DDSURFACEDESC 스트럭쳐가 기입되었다면, 그것과 lpDD를 사용하여 IDirectDraw::CreateSurface 메소 드를 호출할 수 있다. 다음처럼: ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); if( ddrval == DD_OK ) { //good } else { return FALSE;} lpDDSPrimary 파라미터는 성공적 리턴시, 프라이머리 서피스를 가리키 고 있게 될것이다.
* Creating the Surfaces - BackBuffers 프라이머리 서피스로의 포인터를 이용하여, IDirectDrawSurface::GetAttachedSuface 메소드를 이용 백버 퍼로의 포인터를 얻을 수 있다. 이렇게 하여 플리핑 시킬 대기열 같은 것을 만드는 것이다. 다음: ddscaps.dwCaps = DDSCAPS_BACKBUFFER; ddrval = lpDDSPrimary->GetAttachedSurface( &ddcaps, &lpDDSBack ); if( ddrval == DD_OK ) {/* good */} else { return FALSE;} 서피스의 프라이머리 서피스의 주소를 공급하고, 능력설정(capabillities) 구조를 포인터를 이용하여 공급하 고 있다. 성공적인 리턴시 lpDDSBack는 백버퍼로의 포인터 값을 지니고 리턴한다.
* Rendering to the Surfaces (Using standard GDI on the Surfaces)
프라이머리 서피스와 백 버퍼가 준비되면 본격적인 그림 그리기가 가능하다. 다음은 몇 개의 텍스트 글자를 프라이머리 스크린과 백버퍼에 기존의, 옛날의, 표준적인 GDI를 이용하여 그린다. 다음: if (lpDDSPrimary->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); lpDDSPrimary->ReleaseDC(hdc); } if (lpDDSBack->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); lpDDSBack->ReleaseDC(hdc); } 위에서는 IDirectDrawSurface::GetDC 메소드를 사용하여 디바이스 컨텍스트로의 핸들을 얻고 내부적으로 서피스를 잠근다. 당신이 디바이스 컨텍스트를 얻기위해 윈도우의 함수를 이용하지 않을 것이라면, IDirectDrawSurface::Lock 메소드와 IDirectDrawSufface::Unlock 메소드를 이용하여 프라이머리 서피스, 백버퍼, 오프스크린 버퍼들을 열고 잠글 수 있다 . 서피스 메모리를 잠그는 것은 우리가 만든 응용프로그램과 시스템의 블리터가 동시에 서피스에 기록하는 것 을 방지한다. 상호배제를 시키는 것이다. 사실상 서피스는 내부에 참조 카운터를 가지고 있고, 이 참조 카운터 는 세마포어의 역할과 뮤텍스의 역할을 한다. 즉, 서피스에 기록을 원한다면 그전에 Lock 을 할것, 기록이 끝나면 Unlock 할 것. 서피스가 잠겨있는 동안은 페이지 플립핑도 불가능하다. 이것은 자주 저지르는 실수이므로 명심해 두자! (GetDC는 Lock과 ReleaseDC는 Unlock과 비슷하다.) 아마도 이런 초기화 함수가 프라이머리 서피스에 출력을 하는지 의아해 할 것 같다. 일반적으로 , 서피스에 출력을 할 경우, 그 것은 백 버퍼에 하는데 반해서... 그러나, 프로그램 수행 초기의 경우, 상당한 량의 지연이 첫번 째 플립이 일어나기 전에 존재하므로, 프라이 머리 서피스에 직접 출력하여 첫번째 화면이 나오기 까지 화면이 노는 갭을 줄이고자 이렇게 한것이다. 이 기 법을 기억해 두었다 사용하면 좋을 것이다. 타이틀 페이지는 아마도 프라이머리 서피스에 쓰기를 할 필요가 있는 유일한 경우일 것 같다.
* Writing and Flipping the Surfaces
백버퍼에 출력을 하고 서피스들을 플립시킨다. 보통 메인 메시지 루프에 들어 간다. 이 루프 안에서는 백버퍼 가 잠기게 된다. 예제에서는 새로운 텍스트가 출력되고, 백버퍼는 다시 열린다. 마지막으로 페이지 플립핑. WM_TIMER는 서피스에 출력하고, 플립핑하는 대부분의 코드를 가지고 있다.(이 메시지는 윈도우 메시지 중 의 하나로, 일정시간 마다 날라오는 메시지이다.) Writing to the Surface WM_TIMER 메시지의 전반부는 백버퍼에 쓰기에 관련된다. 대부분 여기 쓰인 기법은 이미 "Rendering to the Surfaces"섹션에서 이미 다루었다. case WM_TIMER: // Flip surfaces if( bActive ) { if (lpDDSBack->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); if( phase ) { TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); phase = 0; } else { TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); phase = 1; } lpDDSBack->ReleaseDC(hdc); // code for flipping the surfaces comes here!! } 'phase'라는 변수는 프라이머리 서피스인지 백버퍼 메시지인지를 구분한다. 'phase'값이 1이면 프라이머리 서피스에 출력이 된다. 그 값이 0 이면 백 버퍼에 출력이다. 각각의 경우 출력후 'phase'값을 상대방 값으로 토글 해둔다. 그러나 두 경우 모두 실제 출력은 백버퍼로 출력된다는 것을 눈치 채길 바란다. (이 단락의 첫 문 장에서의 프라이머리, 백버퍼 등의 단어는 생성시 그랬다는 것이다. 프로그램 수행과 함께, 둘의 관계는 뒤집 어진다. by Flip() ) 다음에 플리핑을 하면 되는데 플리핑은 바로 아래에 설명되어 있다.
* Flipping Surfaces
서피스 Unlock 한 후에, IDirectDrawSurface::Flip메소드를 사용하여 백버퍼를 프라이머리 서피스로 전환할 수가 있다. 다음처럼: while( 1 ) { HRESULT ddrval; ddrval = lpDDSPrimary->Flip( NULL, 0 ); if( ddrval == DD_OK ) { break; } if( ddrval == DDERR_SURFACELOST ) { ddrval = lpDDSPrimary->Restore(); if( ddrval != DD_OK ) { break; } } if( ddrval != DDERR_WASSTILLDRAWING ) { break; } } 위의 예를 보면, lpDDSPrimary는 프라이머리 서피스와 거기에 "부속된" 백버펄를 모두 대표한다. IDirectDrawSurface::Flip이 호출되면, 내부적으로 프라이머리 서피스와 백버퍼가 뒤바뀐다. (lpDDSPrimary 포인터에 의해 가리켜지는 서피스 객체 내부의 값인, 각각의 서피스로의 포인터만 바뀐다. 실 제 서피스상의 데이타는 이동하지 않는다. 또한 lpDDSPrimary 포인터은 값도 바뀌지 않는다. 당연하다. 계속 그 구조를 가리키고 있어야 한다.) 페이지 전환이 성공적이면 리턴 DD_OK, 이제 응용프로그램은 while 루프를 빠져나온다. 만약 페이지전환 메소드가 DDERR_SURFACELOST 라는 값을 리턴하면, IDirectDrawSurface::Restore 메 소드를 사용하여 복구하려 시도해 본다. 이것이 성공적이라면 응용프로그램은 다시 페이지 플립을 시도한 다.(DD_OK 에서 break 하면 여전히 while 루프 내부이므로) 페이지 복구조차 실패한다면, while 루프를 빠져나와서 에러를 리턴한다. IDirectDrawSurface::Flip을 호출한 후라도, 전환이 그 즉각 일어나지는 않는다는 것에 주의 해야한다. 이 메 소드는 다음 수직동기중에 페이지 변환이 일어나도록 스케줄링을 하는 것이다. 즉, 저번의 플립핑이 아직 일 어나지 않았다면, IDirectDrawSurface::Flip는 DDERR_WASSTILLDRAWING을 리턴한다(사실 이메시지를 본 다는 것은 반가운 일이다. 그만큼 빠른 쓰기를 하고 있다는 얘기니깐!) 예제에서는 IDirectDrawSurface::Flip 호출은 DD_OK를 리턴할때 까지 계속 호출 된다.
* Deallocating the DirectDraw Objects
응용 프로그램은 종료전에 WM_DESTROY로 진행한다. 이 메시지에서 finiObjects 함수를 호출하고, 이함수 는 다음과 같이 우리의 호프인!! DirectDraw Object lpDD 를 IUnknown::Release 시킨다. 다음처럼: static void finiObjects( void ) { if( lpDD != NULL ) { if( lpDDSPrimary != NULL ) { lpDDSPrimary->Release(); lpDDSPrimary = NULL; } lpDD->Release(); lpDD = NULL; } } /* finiObjects */ DirectDraw object와 DirectDrawSuface object로의 포인터가 NULL인지 검사하고, 그렇지 않다면 IDirectDrawSurface::Release 메소드를 불러 참조 카운터 값을 감소 시킨다. 이러다 보면 참조 카운터가 0가 되면, DirectDrawSurface 는 할당해제 된다. DirectDrawSurface 포인터는 0을 대입해 둠으로써 파괴되어진다(스스로!!). 응용프로그램은 다음으로 IDirectDraw::Release를 호출하여 서피스와 똑같은 방법으로 DirectDraw object를 디알록시킨다. 역시 이것으로의 포인터도 0을 대입 디스트로이한다.
첫댓글 그냥 볼만한 내용이길래 올려봤습니다.. 프린트하셔서 대중교통수단을 이용하실때 보시면 좋겠네여~ ^^