아시는 대로, GetDC()는 단순하게 윈도우의 Device Context를 얻어오기만 하는 함수입니다. 우리가 화면에 어떤 글씨나 그림을 출력할 필요가 있을 때 이 DC를 얻어 DC를 통해 출력을 하게 되지요. 그런데, 단순히 필요한 순간만 GetDC()를 하여 출력을 하게 되면, 다른 윈도우에 의해 이 출력된 부분이 가려졌다 다시 드러나거나, 프로그램이 최소화됐다 다시 복원됐을 때, 출력된 내용이 지워지고 만다는 문제가 있습니다. 그래서 출력은 주로 WM_PAINT 메시지가 호출될 때 모아서 하게 되는데요, 이 안에서 사용하는 함수가 BeginPaint()입니다.
여기까지는 이미 알고 계신 것 같네요. 좀 더 본격적으로, BeginPaint()가 무엇에서 GetDC()와 차이가 나는지 말씀드리겠습니다. 아, 그 전에, WM_PAINT 메시지가 어떻게 발생되는지부터 얘기해야겠군요. WM_PAINT 메시지는, 해당 윈도우가 다시 그려져야 할 필요가 있을 때, 즉 위에서 언급한 것 처럼 다른 윈도우에 의해 가려졌다가 드러나거나, 최소화됐다가 복원됐거나, 최대화됐거나 하여 "무효화 영역"이 발생한 경우 메시지 큐 맨 끝에 부쳐집니다(post). 그럼 이 메시지를 처리하는 코드에서는 화면에 필요한 출력을 해야 하므로 DC를 얻어야 하는데, 여기서는 GetDC()를 쓰지 않고 BeginPaint()를 쓰지요. 바로 "무효화 영역" 때문입니다. BeginPaint()로 DC를 얻어 출력한 다음, EndPaint()로 그리리를 마치게 되면, 해당 윈도우에 설정된 무효화 영역이 해제되어 윈도우 전체가 "유효화" 됩니다. 그런데 GetDC()-ReleaseDC()는 무효화 영역에는 관여하지 않고 오직 DC만 얻었다 돌려줬다 하므로, WM_PAINT 메시지 처리 후에도 윈도우의 무효화 영역이 계속 남아있게 되어 무한 반복적으로 WM_PAINT 메시지가 발생하게 되는 것이지요.
보통, 화면에 출력을 담당하는 코드는 WM_PAINT 내에 작성하고, 화면에 출력할 내용이 변경되면, 출력 데이터를 변경한 후에 InvalidateRect() 함수를 호출해주어 윈도우 전체를 무효화영역으로 만들어 줌으로써 WM_PAINT 메시지가 발생하게 되고 화면이 다시 그려지는 형태로 프로그램을 작성합니다.
메시지에 의해 그제서야 while 루프가 한 번 혹은 두 번 돌 수 있었기 때문이었습니다.
다른 시험을 해볼 수도 있는데, 화면 위에서 마우스를 계속 움직이면 마우스 메시지가
계속 발생하므로 GetMessage()가 즉각즉각 메시지를 가져와 리턴하므로
while 루프가 비교적 원활히(?) 돌아가기 때문에 이 루프 안에 작성한 코드들이
수행되는 것을 즉시 확인할 수 있을 겁니다. 하지만 근본적인 해결책은 역시
GetMessage() 함수를 PeekMessage() 함수로 대체하는 것이 되겠네요.
PeekMessage() 함수를 사용하게 되면 while 루프의 구조가 약간 바뀝니다만,
그정도는 어렵지 않게 구현할 수 있을 것 같아서 설명을 생략하겠습니다.
PeekMessage() 함수의 레퍼런스를 찾아보는 것도 도움이 되겠지요. ^^
여담이지만 아까 CPU 이용 효율에 대해 잠깐 언급했었는데, 이렇게 구조를
바꾸면 무한루프가 계속 수행되기 때문에 CPU 사용률이 100%까지 올라갑니다.
당연한 결과죠.
암튼 좋은 결과 있으시길...
Tip.InvalidateRect를 하면 WM_PAINT를 호출하는 것이 아니라 단지 화면을 무효화하는 것뿐이고 화면이 무효화되면 WM_PAINT 메시지가 메시지 큐에 들어가게 된다. 따라서 큐에 있는 WM_PAINT를 처리하기 전에 다시 화면이 무효화된다면 윈도우는 그 이전 무효 영역과 다시 발생한 무효 영역을 합쳐서 한번에 그려주게 된다. 만약 무효화하자마자 WM_PAINT를 실행시키고 싶다면 InvalidateRect 호출 후에UpdateWindow를 호출해준다.
벼룩시장을 통해 9만9천원을 주고 산 XT 시대를 마감하고 VGA카드가 달린, 거기에 120메가라는 환상적인 하드디스크를 장착한 386이 내 앞에 왔다. 허큘레스카드(흑백)에서 디더링을 통해 표현되던 그림들은 VGA에서는 화려한 컬러로 나왔다. 당시에는 놀라울 정도로 느껴지던 256색상의 컬러가!
Turbo C 2.0 컴파일러가 컴퓨터에 설치되고 친구따라 강남간다고 친구가 열심히 공부하던 C언어를 따라 공부하면서 처음으로 텍스트가 아닌 컬러의 그림을 화면에 뿌릴수 있었는데 그것을 가능하게 한 함수가 putpixel() 로 기억된다.
putpixel( int x, int y, int color ). 나에게는 그래픽 이라는 화려한 세계에서 맘껏 휘두를수 있는 무기와도 같은 것이었다. 아니 무기라기 보다 마법봉에 가까운 것이리라~. 640x480 그래픽 모드로 세트한후(함수 이름이 생각 안나는군..) putpixel()로 좌표와 컬러값을 주면... 오옷!! 놀라워라! 모니터에 컬러의 점이 찍힌다! - -;
그때 나는 생각했다.. 당시 윈도3.0이 있었는데 putpixel()로 컬러 점을 찍어 윈도같은 쉘(당시엔 운영체제라고 생각도 안했다) 프로그램을 만들꺼라고.. 하핫~ 물론.. 놀랍게도? 만들어 내기는 했다.. ^^; 아주 아주 .. 쪽팔리는 쉘을..
시답잖은 놀라움에 두눈을 번쩍이기를 몇달후.. 한 선배가 나에게 더욱 놀라운 것을 보여주었다. 그것은 아이콘 에디터 였다.. 당시 내 선망의 대상은 이야기5.3을 만든 경북대학교 "하늘소"였는데 이야기5.3의 통신기능 보다 GUI 자체가 좋아서 였다. 게임과 같은 그래픽은 도저히 범접할수 없을것 같았고 그나마 어떻게 내손으로 만들수 있을것 같았던 것이 이야기5.3 정도였기에..
그런데 선배가 그보다 멋진 프로그램을 나에게 내민것이 아닌가!. 아! 이렇게 가까이 고수가 있었다니... 그 후로 하늘소를 선망하기를 그만뒀다.. 헤헤. 지조없이..쯧쯧.
64x64 면적에 바둑판 같은 칸이 나누어 져 있고, 마우스로 색을 선택해서 칸에 눌러주다보면 아이콘이 만들어 진다. 그리고 파일로 저장이 되며 선배가 만든 라이브러리로 화면에 출력할 수 도 있고. 그렇다. 이것이 내가 처음으로 느껴본 "비/트/맵"이다. 즉, 단순히 putpixel로 색을가진 한 점을 찍는것이 아니라 하나의 그림을 여러 픽셀값으로 나열해 뭉탱이로 파일에 저장하거나 메모리에 로드 하는것. 한 픽셀이 흑백이라면, 0과1로 한 픽셀을 표현할수 있기에 "비트". 그리고 그 픽셀들이 특정 크기의 사각형으로 나열되어 있는 "맵". 그래서 비트+맵=비트맵. - -;
내가 아이콘을 그리기 위해 엽기적으로 putpixel을 수백번 쓰는것이 아니라 각 픽셀의 값들을 데이터로 저장하고 그것을 불러와 순서데로 화면에 뿌려주는 형태.. 나는 이런 단순한 생각을 알기까지 얼마나 많은 밤을 putpixel 치기에 투자했었는가!
엽기 이야기는 여기까지 하고.. OO; 94년에 윈도3.1을 입수하면서 트루타입폰트에 아주 신기해 하고 있을즈음 윈도우즈 프로그래밍을 시작하게 되었다. 물론 가장 흥미를 가졌던것중 하나가 그림출력 이었다. 그런데 이게 왠일이냐. DOS 에서는 putpixel 로 점을 찍기만 하면 우옛든 그림을 그릴수 있었는데 윈도우즈에선 DC를 이용해란다. DC가 머꼬?
모른다..
이럴땐 하드웨어를 생각해보는게 상수다. 비디오카드가 뭔가.. 비디오카드엔 비디오메모리 라고 불리는 메모리가 있는데 그 메모리의 내용이 곧 모니터에 뿌려지는 화면이다.. 내가 640x480 에 256 컬러 모드로 세트한다고 해보자. 한개의 점이 256컬러를 표현해야 하므로 이백오십여섯 가지를 표현할수 있는 데이터양. 즉 8비트. (2의8승은 256) 따라서 한 픽셀은 8비트 이고, 그게 640x480개가 필요하니깐 8비트가 307200개 필요하네. 따라서 307200바이트가 필요하다는 거고, 그건 300k바이트. 내 비디오 카드가 512k바이트의 비디오메모리를 가지고 있으므로 비디오 메모리는 충분하군. 그럼 putpixel(100,100, 4); 하면 비디오 메모리의 10000번지 쯤에 컬러값 4가 기록 되는 거지. 그럼 모니터에 100,100 지점에 4번컬러가 표현되는것이고. 그런데...... 윈도우즈에선 왜 putpixel 이 없는 것이야. 아니야. 있긴 있는데 100,100 좌표에 찍어도 모니터 100,100에 찍히지 않는단 말씀이야. 내 윈도우의 클라이언트영역 100,100에 찍힌단 말이지... 그럼뭐야.. 내가 화면 100,100에 아무리 찍을려고 해도 그건 불가능 하단 말야?(불가능하진 않습 니다.) 음...
아니.. 잠깐. 내 윈도우는 마우스만 잡아끌면 수시로 움직이는데. 거꾸로 내 윈도우의 100,100에 계속 점을 찍으려면 일일이 윈도우의 위치를 계산해야 되는 것이야? 이건 더 최악의 상황이군.. 그럼.. 그럼 말야. DC를 이용해서 윈도우에 무엇을 그린는건.. 내가 윈도우 위치에 신경쓰지않고 윈도우 내의 좌표만 맞춰주면 항상 그 위치에 그려진다는걸 보장해 준다는 것인가? 또 비디오카드를 교체해서 그래픽 해상도가 바뀌더라도 내 코드를 손대지 않고 이전과 같이 그려준다는 ... 이건 논리적인 개념이군.. 즉 내가 물리적인 비디오카드의 좌표에 신경쓰지 않고 논리적으로 내가만든 윈도우의 좌표에 그림을 그리면 윈도우즈 운영체 제가 알아서 그려준다... 허허.. 그거 괜찮네. 일단 논리적은 개념은 좋다 이거야. 어쨋든 DC라는게 뭐냐는 거지.
DC. Device Context. 책을보자.. "DC는 모니터에 출력되는 그림판과 같은것." 그림판과 같은것... 즉 스케치북 같은것 이란 말이군. 아! 스케치북!!!!!!!!!!
신이시여... 제가 정녕 "스케치북"이란 단어를 생각해났단 말입니까! 흥할놈의 윈도우즈 제작자여! 너희는 왜 스케치북이라 칭하지 않고 DC라고 이름지어 많은 사람을 이 대목에서 괴롭게 했는가 말이냐!~ 흑흑..
즉 무슨말이냐... 화면에 보여지는 윈도우든 종이를 출력하는 프린터든 스케치북을 가지고 있단 말씀이야. 우리는 스케치북에 무언가를 그리기만 하면 윈도우즈는 스케치북에 그려진 것들을 윈도우에 뿌려주던지, 프린터의 종이에 그려주던지 한다는 거지. 호호..
음.. 시대를 후다닥 미래로 돌려서.. VC++의 MFC를 보자고. CDC클래스가 있네. 이건 DC와 관계되는 여러가지 작업을 묶은 클래스란 말이지. 즉 객체로 만들어 쓸수 있다는 것이고. 마치 DC와 관계한 작업들이 가능한 하나의 부품처럼 말이야. 자. 이제서야 내 숙원인 그림을 윈도우에 뿌려봐야 겠군. 으하핫~
자자... 마음을 진정시키고. 그림을 스케치북에 뿌려보자. 우째되는지. 음... 그림은 결국 비트맵이란 말이야.. 그럼 일단 비트맵 파일을 읽어서 뿌려볼까나? ... ... 헥..
비트맵 파일을 읽어오는 그런 함수는 없네.. 우짜지. 직접 파일을 읽어야 겠네. 근데 비트맵 파일의 구조를 알아야 말이지. 그렇다면 우리의 성경 MSDN을 볼까나?
먼저 목차에 가서 Platform SDK 와 Knowledge Base, 그리고 Technical Articles 를 뒤졌다. 음.. Multimedia 계통이나 GDI 쪽에 많은 정보가 있군. 이번엔 검색으로 찾아볼까? 내가 좋아하는 검색방법은 일단 howto를 꼭 넣구 관련단어를 모조리 적어서 검색해 보는거다. howto bitmap file structure palette dib ddb gdi mfc graphics ...등등등.. 그리고 점점 중요하지 않은 단어들을 없애 가는거지.. 약 100개까지만 나오도록. 여러가지 흥미로운 문서들이 나오는군..
이렇게 MSDN과 관련서적을 이용해 비트맵 파일구조를 습득했다. 대충보니 비트맵에 대한 전반적인 정보를 헤더로 앞에 저장하고 뒤에다가는 비트맵 각 픽셀의 값을 저장하는군. 내 그럴줄 알았지. 그런데 말야.. 팔레트 라는 녀석 말인데. 이게 좀 헷갈린단 말야. 256컬러에는 팔레트가 있는데, 트루컬러는 팔레트가 없단 말이야. 음... MSDN을 다시 검색해 봐야겠군.
오호.. 심오하네. 팔레트는 이런것이군.
일단 비트맵의 컬러는 RGB 값으로 표현된다 이거지. RGB가 뭐냐. R은 빨강 G는 녹색 B는 파랑. 초등학교 미술시간의 기억을 더듬어 보면 이게 빛의 삼원색인가 어둠의 삼원색인가.. 하옇든 무슨 삼원색이야. 요걸 적절히 섞으면 세상모든 컬러를 다 표현 한댔지. 그리고 RGB 각각은 8비트씩으로 256가지의 빨강색과 256가지의 녹색, 256가지의 파랑으로 조합해서 총 8x3 = 24비트를 가지고 총 천연색을 다 표현하시겠다... 오호~
그럼 한 픽셀의 색은 24비트로 표현되니 24비트를 표현할수 있는 변수는 32비트 짜리겠군. 24비트 짜리 변수는 없으니 말야. 32비트면 말이지. DWROD 나 int 같은걸 쓰면 되겠군. 픽셀 한개의 색깔 데이터로 말야. 즉 픽셀값이지. 부호가 없으니 int 는 unsigned 라고 살짝 붙여주고 말이지. ^_^
흐흐. 이제 팔레트의 비밀이 벋겨지는군. 무슨 말이냐면. 내가 화면모드를 256 모드로 했다고 봤을때, 비디오카드를 통해 동시에 표현할수 있는 컬러는 256가지 밖에 안됀단 말이지. 멋진 사진을 256컬러로 만들려면 결국 비슷한 색끼리는 대표값으로 통합해서 어떻게하든 256가지 색만 쓰도록 해야할 것이고 256가지 색만 표현한다면 굳이 한 픽셀의 색을 표현하는데 32비트가 필요없다는 결론. 따라서 256가지를 표현할수 있는 8비트면 한픽셀의 색을 표현하기에 충분하니깐. 따라서 32비트값 256개의 색깔정보를 담은 인덱스를 만들고 256개 각각은 32비트 컬러값으로 저장해 놓는거야. 그리고 픽셀값은 그 인덱스의 번호만 가리키는 거지. 32비트값 256개의 색깔 배열이 바로 팔레트 라는 말씀.
그렇지. 그렇지.. 가끔 화면을 256색으로 맞춰 놓고 그림판에서 256 컬러 그림을 불러오면 불러온 그림은 똑바로 보이는데 다른 윈도우의 컬러가 깨지는 현상을 발견하곤 했지.. 그건 현재 보여주고자 하는 비트맵의 256색에 맞는 팔레트를 세트해 버렸으니 다른 팔레트를 쓰는 나머지 256 컬러들은 색깔이 깨질수 밖에.
256색을 쓰는 비트맵이라면 파일로 저장할때 반드시 팔레트를 세트해야 되겠군. 팔레트에 들어가는 색은 엿장수 마음데로지.. 쿠쿠.
자.. 그러면 DDB와 DIB의 구분도 명확해 지는데. DDB는 장치의존적 비트맵. 즉 내 비디오카드에, 그리고 내가 제어판에서 택한 비디오 색상 모드에 맞게 색깔을 정해버린 비트맵이란 말이지. 만약 이 비트맵을 다른 컴퓨터에서 다른 색상모드의 윈도우즈에서 뿌린다면 색이 다 깨져 버릴꺼야. 그래서 어느 컴퓨터에 가든지 내 비트맵이 가리키고 있는 본래의 색을 유지하기 위해 DIB를 쓰는거군. 장치 독립적 비트맵 말야. 따라서 모든 비트맵 파일은 DIB이어야 만족스럽다는 결론을 얻게 되는군.
이제 진짜로 화면에 뿌려보자. 내 윈도우의 dc를 얻어서 그 dc에다가 비트맵을 뿌려 버리는거야. 먼저 비트맵 파일에서 읽은 팔레트를 dc에다가 등록해서 미리 알려주고 팔레드가 세트되면 StretchDIBblt() 로 뿌려 버리는 거지. 오옷. 멋진데.~ 음.. 흠.. 그런데 화면을 256컬러로 맞춰놓으니 예상했던데로 다른 그림들의 색이 깨어지는군. 모드를 트루컬러로 바꾸고 이번엔 DDB 로 바꾸어 뿌려보자. CBitmap 을 이용해 보자는 거지. 쓱쓱~ 뚝딱뚝딱~ CBitmap 객체로 바꾸고 BitBlt() 로 짜잔~~
속도가 왜이리 느린거야? 비트맵 출력되고 선들이 쑥쑥.. 그리고 점들이 팅팅팅... 내 그래픽카드는 꾀나 잘나가는 최신 카드인데.. 이상하네.. 책을 좀 보자..
음.. 이렇게 적혀있군..
"바보야. dc에다가 그리면 곧바로 윈도우즈는 그린걸 화면에 뿌리지? 그리고 니가 10000 번씩 돌며 선이랑 점을 계속 그렸지? 넌 이렇게 한거야..
윈도우즈야. 내 윈도우 표면에 비트맵을 그려줘. 응. 그려줄께. (비디오 메모리 쓴다.)번쩍! 윈도우즈야. 내 윈도우 표면에 선을 그려줘. 응. 그려줄께. (비디오 메모리 쓴다.)번쩍! 윈도우즈야. 내 윈도우 표면에 선을 그려줘. 응. 그려줄께. (비디오 메모리 쓴다.)번쩍! 윈도우즈야. 내....... .... ... 윈도우즈야. 내 윈도우 표면에 점을 찍어줘. 응. 그려줄께. (비디오 메모리 쓴다.)픽! 윈도우즈야. 내 윈도우 표면에 점을 찍어줘. 응. 그려줄께. (비디오 메모리 쓴다.)픽! 윈도우즈야. 내 윈도우 표면에 점을 찍어줘. ....
뭔가 느끼는거 없어 이넘아? " (참고 : 물론 책에는 언어순화가 되어있습니다.)
음.. 그래. 미안하다. 뭔가 느껴지는군. 내가 dc에다가 뭔가를 계속 그리면 그순간 윈도우즈는 계속 비디오 메모리에 그림을 그린다는 것.. 즉 계속해서 윈도우즈가 비디오 메모리를 엑세스 한다는건 좀 과중한 일이라는 거지.. 그래서 속도가 느리다는 결론.
책에서 말하기를.. BitBlt() 는 메모리 dc를 이용해서 대단히 속도가 빠르다네.
음.. 이제 memDC 라는게 이해가 가는군.. memDC 는 메모리dc 야.. CreateCompatibleDC() 를 하면 메모리 dc가 된다는군. 음. 그래.... 맞다. 메모리dc는 비디오메모리에 바로 쓰는 dc가 아닌것 같군. 그렇다면 빠른 속도를 자랑하는 메인메모리에 쓰는 dc? 그럼 메모리dc에 그림을 그리면 메인메모리에 그림을 그리는 거네? BitBlt() 는 메모리dc를 진짜dc에 복사하는 함수고..
핫! 그렇다면. LineTo MoveTo 의 선그리기와 SetPixel() 의 점찍기를 진짜dc에다가 그리지 않고 메모리dc에 그린뒤에.. 그 메모리dc를 BitBlt() 로 진짜 dc에 복사시켜 버리면.. 빠르겠지! 당근! 메모리dc에 그려진 메인메모리에 있는 정보가 통채로 한방에 비디오메모리에 옮겨 질테니 말야. 1번만 비디오 메모리로 간다는거지. 오옷! 정말 멋진데~
"메모리DC는 가상의 스케치북 같은것. 우리가 그리고 싶은것을 메모리DC에 마음껏 그린후 죽~ 뜯어서 진짜 DC에 BitBlt()를 이용해 복사만 하면 화면에 그림이 출력된다는 사실. 가상의 스케치북은 속도가 빠르다는 것......"
동시에 여러개를 누른 키보드 메시지가 전달되려면 GetKeyState , GetAsyncKeyState사용.
키입력에 바로 반응하려면 GetAsyncKeyState사용.
# FPS
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
MainGame.Update();
MainGame.Render();
}
}
이렇게 짰떠니 MainGame의 update와 render 계속 호출.... fps개념 검색. (winapi game fps)
참고 : http://data-forge.blogspot.kr/2013/09/fps.html
GetTickCount() : 윈도우가 시작돼서 지금까지 흐른 시간 1/1000초 단위로 DWORD형으로 리턴.
GetAsyncKeyState의 반환값은 아래와같이 4가지로 나뉩니다.
반환값
설명
0 (0x0000)
이전에 누른 적이 없고 호출 시점에서 안눌린 상태
0x8000
이전에 누른 적이 없고 호출 시점에서 눌린 상태
0x8001
이전에 누른 적이 있고 호출 시점에서 눌린 상태
1 (0x0001)
이전에 누른 적이 있고 호출 시점에서 안눌린 상태
이전에 누른 적이 없고 호출 시점에서 눌린 상태, 즉 키가 눌려진 시점에서 0x8000을 반환합니다. 만약 이전에도 누른 적이 있고 호출 시점에서도 눌렸으면 0x8001을 반환합니다. 이전에 키를 눌렀으면 0x0001을 반환하고, 눌리지 않았다면 0x0000을 반환합니다. 혹시, GetAsyncKeyState(VK_UP) & 0x8000 이런 코드를 보신적이 있으신가요? 0x8000과 GetAsyncKeyState 반환값을 AND 연산하는 이유는 정확한 시점에서 키의 상태를 확인하기 위함입니다. 만약에 키를 눌러 GetAsyncKeyState 함수가 0x8000을 반환한다면 이를 0x8000으로 AND 연산하여 키가 눌렸을때는 0x8000이 나옵니다. 그러나 눌려진 적이 있으면 GetAsyncKeyState 함수는 0x0001을 반환한다고 했었습니다. 0x0001에다 0x8000을 AND 연산하게되면 0이 나오게 됩니다. (만약에 AND 연산하지 않았을 경우에는 눌러진 적이 있는 상태도 참이 되어 동작을 수행할 것입니다.)