24/03/17
9화. Core 클래스 (2)
윈도우 사이즈 조정 :
윈도우 생성시 입력해준 윈도우 사이즈에는 출력 영역 외에도 제목표시줄, 메뉴바 영역도 포함한 사이즈이다.
이미지가 출력되는 영역을 "클라인트 영역" 이라고 하며 해당 영역의 사이즈를 윈도우 생성당시 입력해준 사이즈로 설정하기 위해선 추가적인 작업이 요구된다.
AdjustWindowRect( ) , SetWindowPos( ) 이 두 함수로 윈도우를 조정할 수 있다.
BOOL AdjustWindowRect(
_Inout_ LPRECT lpRect, // 값을 조정해줄 RECT 객체
_In_ DWORD dwStyle, // Window 스타일
_In_ BOOL bMenu // 메뉴바 존재 유무
);
AdjustWindowRect() 함수는 메뉴바와 제목표시줄, 테두리 영역을 고려하여 첫번째 인자로 들어온 RECT 객체의 사이즈를 조정해준다.
첫번째 인자는 SAL 주석을 통해 확인할 수 있듯이 입력과 출력 둘다를 담당하는 인자가 들어온다. 때문에 주소값을 받으며 앞에 const 키워드가 붙지 않는다. -> 반환값이 너무 큰 경우 주소값을 받아 직접 값을 반환하여 전달해주는 방식을 사용하기도 한다.
(Windows 의 이름작성법에서 "LP"가 앞에 붙는 이름들은 주소값을 뜻한다고 생각하면 된다.)
두번째 인자는 윈도우 스타일관련 비트값들의 조합이다.
DC 얻어오기 :
윈도우에 그리기 작업을 하기 위해서는 DC 를 얻어와야한다.
DC는 어디에 어떻게 그릴지에 대한 정보를 가지고 있는 도구 상자와도 같다.
main 함수의 메세지 처리영역을 보면 BeginPaint() 함수를 호출하여 DC를 얻어오는걸 확인할 수 있다.
하지만 해당 함수는 DC 를 얻어오는것 외에도 EndPaint() 함수를 호출하여 DC의 사용을 마치는 작업외에도 WM_PAINT 메세지를 제거해주는 역학을 하는 "메세지기반 함수" 이기에 게임 엔진에 사용하기엔 적절하지 않다.
메세지 존재유무와 상관없이 화면에 그려주기 위해서 "GetDC()" 함수를 사용해서 얻어온다.
GetDC()를 통해 DC를 얻어오면 DC는 "커널 오브젝트"이기에 사용을 마칠땐 ReleaseDC() 함수를 통해 OS에 메모리 해제 요청을 보내줘야한다.
비동기 키 입출력 :
메세지 기반이 아닌 방법으로 키 입력값을 확인하기 위해선 GetAsyncKeyState() 함수를 사용해야한다.
해당 함수는 "비동기 키 입출력 함수"라고 불린다.
update() 함수에서 해당 함수를 호출하여 사용하기에 프로그램 실행중엔 항상 호출된다. 떄문에 윈도우가 포커싱 되어 있는지 구분하지 않으며 백그라운드에 있더라도 키 입력을 받아온다는 단점이 있다.
반환값을 통해 키입력 상태를 알려준다.
10화. Win32API Timer (1) / 11화. Win32API Timer (2)
시간 동기화를 시키지 않고 입력값을 받아 처리하면 프레임 단위로 처리될 것이기에 컴퓨터의 성능에 따라 같은 시간동안 서로 다른 결과를 내놓게 된다.
이러한 이유 컴퓨터의 성능과는 상관없이 동일 시간동안 같은 결과 값을 도출하는 것을 보장하기위해 시간동기화 작업을 해줘야한다.
TimeMgr 클래스 추가 :
- 초당 프레임수 = FPS
- 1sec / FPS = 한 프레임에 걸리는 시간, 프레임 사이 시간 [sec]
- 이동량 / FPS = 프레임당 이동량 [px]
- 프레임당 이동량 x 한 프레임에 걸리는 시간 = 초당 이동량 [px·sec]
컴퓨터에서 시간을 측정하기 위해서 사용할 수 있는 함수 중 하나는 GetTickCount() 이다.
해당 함수는 초당 1000을 카운트한다. 하지만 컴퓨터는 초당 더 많은 횟수로 프레임을 만들어 내기에 GetTickCount() 함수로 시간을 계산하게 되면 0.001초(1ms)당 약 6프레임이 흘러가버린다...
현재 아무런 작업을 안하고 있는 중이긴 하지만, 노트북 기준으로 해당 프레임이 나오기에 성능좋은 컴퓨터면 더 많은 프레임을 놓치게될것...
1ms의 한계를 해결하기 위해 "고해상도 타임스탬프 : QueryPerformanceCounter (QPC)" 를 사용한다.
QPC함수 역시 GetTickCount() 와 마찬가지로 초당 카운트를 해주는 함수인데 초당 카운팅하는 횟수가 1000만회이다.
QPC의 초당 카운팅 횟수는 QueryPerformanceFrequency() 함수를 통해 확인할 수 있다.
때문에 QPC를 사용하면 프레임을 놓칠 걱정을 하지 않아도 된다.
QPC는 카운팅 값을 QuardPart 멤버에 LARGE_INTEGER 타입으로 저장한다.
Delta Time (델타타임) :
프레임 사이 걸린 시간을 "델타 타임 (Delta Time)"이라고 한다.
델타 타임을 구하기 위해선 아래와 같은 작업을 해준다.
// DT = 프레임 사이의 카운팅 횟수 / 초당 카운팅 횟수
m_dDT = static_cast<double>(m_llCurCount.QuadPart - m_llPrevCount.QuadPart)
/ static_cast<double>(m_llFrequency.QuadPart);
키입력을 받아 일정 거리를 이동하고자 할때, 초당 이동하고자 하는 거리에 DT를 곱해준 값을 이동하게 하면 컴퓨터 성능에 상관없이 초당 같은 거리를 이동하게된다.
24/03/18
12화. Win32API Double Buffering
문제점 :
화면 갱신을 안해줌으로인해 오브젝트의 이전위치에서의 모습이 그대로 남아 잔상이 남는 문제가 있다.
이를 해결하기 위해서 매 프레임마다 화면을 전부 지워주면 너무 빠른 작업으로인해 Rect 객체가 화면에 보일때도 있고 안보일때도 있는 상황이 발생한다. (window에 객체가 그려져 있는 타이밍, window의 모든 객체가 지워진 타이밍)
이러한 문제를 해결하기 위해 "이중 버퍼링 (Double Buffering)"을 사용한다.
이중 버퍼링 :
이중 버퍼링이란 Buffer를 두개 두고 렌더링을 양쪽으로 관리하는것을 말한다.
Window는 내부적으로 작업영역에 대해서 해당도 영역만큼의 픽셀을 보유하게되는데 이를 묶어서 Bitmap 이라고 한다.
main Window 와 호환되는 Bitmap 객체를 만든 뒤 Bitmap에 모든 객체들을 그려준다. 모든 객체가 다 그려지면 mainWindow에 Bitmap의 이미지를 복사해준다.
Window를 새로 생성하서 두개의 Window를 가지랴는게 아니라 main Window 와 같은 해상도를 가지는 Bitmap 데이터를 가지려고 하는것이라는걸 명심하자
해당 프레임이 끝나면 다음 프레임 시작시 Bitmap에 그려져 있는 이전 프레임의 이미지를 모두 지우고 update 된 객체들의 정보를 기반으로 해당 프레임에서의 객체들의 이미지를 다시 그려주기를 반복한다.
이중 버퍼링용 Bitmap, DC 생성 :
CreateCompatibleBitmap()은 첫번째 인자로 들어온 DC에 호환되는 Bitmap을 생성한다.
DC에는 목적지로 사용할 윈도우 핸들에 대한 정보도 들어있기에 DC에 호환된다는건 DC의 목적지 윈도우와 호환된다는 걸 의미하기도 한다.
HBITMAP CreateCompatibleBitmap( _In_ HDC hdc, _In_ int cx, _In_ int cy);
CreateCompatibleDC()는 인자로 들어온 DC와 같은 정보를 가진 DC를 생성해주는 함수다.
HDC CreateCompatibleDC( _In_opt_ HDC hdc);
Render 로직 :
#pragma region Double Buffering
// 화면 지우기
Rectangle(m_memDC, -1, -1, m_ptResolution.x + 1, m_ptResolution.y + 1);
#pragma endregion
// 그리기
Vec2 vPos = g_obj.GetPos();
Vec2 vScale = g_obj.GetScale();
HBRUSH newBrush = CreateSolidBrush(RGB(0, 100, 100));
HBRUSH oldBrush = (HBRUSH)SelectObject(m_memDC, newBrush);
Rectangle(m_memDC
, vPos.x - vScale.x / 2.f
, vPos.y - vScale.y / 2.f
, vPos.x + vScale.x / 2.f
, vPos.y + vScale.y / 2.f);
DeleteObject(SelectObject(m_memDC, oldBrush));
#pragma region BitBlt
BitBlt(m_hDC, 0, 0, m_ptResolution.x, m_ptResolution.y, m_memDC, 0, 0, SRCCOPY);
#pragma endregion
BitBlt :
하나의 DC에 있는 Bitmap을 다른 DC로 복사하는 Bitmap 고속 복사 함수
BOOL BitBlt( _In_ HDC hdc, _In_ int x, _In_ int y, _In_ int cx, _In_ int cy
, _In_opt_ HDC hdcSrc, _In_ int x1, _In_ int y1, _In_ DWORD rop);
처음 인자로 목적지 DC 와 이미지가 복사될 영역을 지정해준다. 그 다음 복사될 이미지가 존재하는 DC의 핸들과 복사될 영역의 좌상단 좌표를 입력해주고 동작 옵션(SRCCOPY : 복사)을 지정해준다.
Next Note
Win32API 강의_3
24/03/19 13화. Win32API Key Manager (1) 14화. Win32API Key Manager (2) 15화. Win32API Scene Manager (1) 16화. Win32API Scene Manager (2) 17화. Win32API Object (1) 18화. Win32API Object (2) 19화. Win32API 기초 수학 (1) 20화. Win32API 기초 수
coder-qussong.tistory.com
'Win32API' 카테고리의 다른 글
Win32API_StudyNote_6 (0) | 2024.03.24 |
---|---|
Win32API_StudyNote_5 (0) | 2024.03.22 |
Win32API_StudyNote_4 (0) | 2024.03.21 |
Win32API_StudyNote_3 (0) | 2024.03.19 |
Win32API_StudyNote_1 (0) | 2024.03.13 |