WIN32 Timer, Double Buffering
카테고리: WINAPI
6일차
- Default
- Client.h
- framework.h
- Resouce.h
- targetver.h
- Engine
- Header
- define.h
- struct.h
- Core
- CCore.cpp
- CCore.h
- Manager
- KeyMgr
- CkeyMgr.cpp
- CkeyMgr.h
- TimeMgr
- CTimeMgr.cpp
- CTimeMgr.h
- KeyMgr
- Object
- CObject.cpp
- CObject.h
- Header
- 리소스 파일
- main.cpp
- pch.h.
- struct.h
- 거의 시간 동기화에 대한 내용이다. 또 Double Buffering인데.
- 시간 동기화는 update함수에서 += 0.01f로 움직이면, 어떤 컴퓨터에서는 50프레임, 다른 곳에서는 150프레임 이렇게 나는데 그에 대한 차이가 발생하면 안되므로, 1초에 얼만큼 가도록 Delta Time이라는 1프레임당 걸리는 시간을 계산하여 곱해준다.
- Double Buffering은 이전 시간에도 그랬지만, 윈도우 화면을 렌더링하면 계속 지우고 그리는 과정에서 중간 과정이 보이게 되어서 뭔가 화면이 깜빡이는 느낌이 들게 된다.
- 그래서 그리는 용도의 하나의 캔버스(BITMAP)을 둔다. 거기에서 다 그린 후에, 사용자가 보게 되는 Window에 한 번에 가져온다. 즉, 가볍게 말하면 그리기 전용 캔버스, 보여주기 전용 캔버스. 캔버스 2개를 가지고 있는 것이다.
- CCore.h
#pragma once
#include "define.h"
class CCore {
SINGLE(CCore);
private:
HWND m_hWnd; //메인 윈도우 핸들.
POINT m_ptResolution; //메인 윈도우 해상도
HDC m_hDC; //메인 윈도우에 Draw 할 DC
HBITMAP m_hBit; //사본용 .
HDC m_memDC;
public:
int init(HWND _hWnd, POINT _ptResolution);
void progress();
private:
void update();
void render();
public:
HWND GetMainHwnd() { return m_hWnd; }
};
- m_Bit은 새로운 ‘그려주기 용’ 캔버스이다. m_memDC는 수업 초기에 있었던 한 윈도우(캔버스)에는 그려주기 용 도구? 그런 구조체가 있다고 했었다. 그것의 값이다.
- GetMainHwnd()는 메인 윈도우의 Key값을 보내주는 함수이다.
- Define.h
#pragma once
#define SINGLE(type)public: \
static type* GetInstance()\
{\
static type mgr;\
return &mgr;\
}\
private:\
type();\
~type();
#define fDT CTimeMgr::GetInstance()->GetfDT()
#define DT CTimeMgr::GetInstance()->GetDT()
- fDT나 DT는 후술할 CTimeMgr에서 DeltaTime(1프레임 당 걸리는 시간)을 반환하는 것을 축약한 매크로이다.
- CTimeMgr.h
#pragma once
class CTimeMgr
{
SINGLE(CTimeMgr);
private:
//이동량을 프레임으로 쪼개서 이동을 시키면, 이번 프레임에 움직이는 양이 나옴.
//이동량 * 1프레임에 걸리는 시간(delta Time).
LARGE_INTEGER m_llCurCount;
LARGE_INTEGER m_llPrevCount;
LARGE_INTEGER m_llFrequency;
double m_dDT; //프레임 간의 시간값.
double m_dAcc; //1초 체크를 위한 누적 시간.
UINT m_iCallCount; //1초당 호출 횟수.
UINT m_iFPS; //1초당 프레임.
public:
//GetTick은 1초당 1000이라서 정확도가 조금...
//게다가 오차도 있어서 훨씬 더 정밀한 함수가
//QueryPerformanceCounter(); -> 1000만 단위.
void init();
void update();
public:
double GetDT() { return m_dDT; }
float GetfDT() { return (float)m_dDT; }
};
- CTimeMgr.cpp
#include "pch.h"
#include "CTimeMgr.h"
#include "CCore.h"
CTimeMgr::CTimeMgr()
:m_llCurCount{}
, m_llPrevCount{}
, m_llFrequency{}
,m_dDT(0.)
,m_iCallCount(0)
,m_dAcc(0.)
{
}
CTimeMgr::~CTimeMgr() {
}
void CTimeMgr::init()
{
//현재 카운트
QueryPerformanceCounter(&m_llPrevCount);
//초당 카운트가 고정되어 있는것도 아님.
//초당 카운트 횟수(1000만)
QueryPerformanceFrequency(&m_llFrequency);
}
void CTimeMgr::update()
{
QueryPerformanceCounter(&m_llCurCount);
//이전 프레임의 카운팅고, 현재 프레임 카운팅 값의 차이를 구한다.
m_dDT = (double)(m_llCurCount.QuadPart - m_llPrevCount.QuadPart) / (double)m_llFrequency.QuadPart;
//이전 카운트 값을 현재값으로 갱신(다음번에 계산을 위해서)
m_llPrevCount = m_llCurCount;
m_iCallCount++;
m_dAcc += m_dDT; //DT 누적.
if (m_dAcc >= 1.) {
m_iFPS = m_iCallCount;
m_dAcc = 0.;
m_iCallCount = 0;
wchar_t szBuffer[255] = {};
swprintf_s(szBuffer, L"FPS : %d, DT : %f", m_iFPS, m_dDT);
SetWindowText(CCore::GetInstance()->GetMainHwnd(), szBuffer);
}
}
- QueryPerformanceCounter(1초당 1000만회 셈)을 통해 이전 프레임과 현재 프레임 카운팅의 값 차이를 구한다. (DeltaTime)
- 또한 그걸 이용해 1초가 지날때마다, m_iCallCount가 ++된 값을 구해, 1초당 얼마나 많은 CTimeMgr::update()가 실행됐는지 알 수 있다.
- CTimeMgr.cpp
#include "pch.h"
#include "CCore.h"
#include "CTimeMgr.h"
#include "CkeyMgr.h"
#include "CObject.h"
//CCore* CCore::g_pInst = nullptr;
CObject g_obj;
CCore::CCore()
: m_hWnd(0), m_ptResolution{}, m_hDC(0)
, m_hBit(0), m_memDC(0)
{
}
CCore::~CCore() {
//m_hWnd에 엮여있던 m_hDC를 해제해준다.
ReleaseDC(m_hWnd, m_hDC);
//Create로 만든 것은 DeleteDC로 해야한다.
DeleteDC(m_memDC);
DeleteObject(m_hBit);
}
int CCore::init(HWND _hWnd, POINT _ptResolution) {
m_hWnd = _hWnd;
m_ptResolution = _ptResolution;
//해상도에 맞게 윈도우 크기 조정.
RECT rt = { 0,0,m_ptResolution.x, m_ptResolution.y };
//테두리 포함해서, 크기 보정해주는 것.
//rt 포인터에다가 다시 값을 변환해서 넣어줌.
AdjustWindowRect(&rt, WS_OVERLAPPEDWINDOW, true);
//타이틀이나 그런 걸 제외한, 순수하게 물체가 그려질 수 있는 영역.
//첫 번째로 들어가는 건, 어떤 윈도우인지.
//SetWindow는 타이틀창이나 테두리까지 포함한 크기임.
SetWindowPos(m_hWnd, nullptr, 100, 100, rt.right - rt.left, rt.bottom - rt.top, 0);
//어디다가 그려요? m_hWnd에다가 그려요.
m_hDC = GetDC(m_hWnd);
//이중 버퍼링 용도의 비트맵과 DC를 만든다.
//그림을 그릴 수 있는 건 윈도우에 달려있는, 해상도만큼의 픽셀(묶어서 비트맵이라고 부름)
//결국 똑같은 해상도로 비트맵을 가지면 되는 거 아닌가? 해서 똑같은 해상도의 비트맵을 만듬.
//Compatible은 호환성. (호환성을 가지고 있는 비트맵)
//비트맵 아이디 값을 받았음.
m_hBit = CreateCompatibleBitmap(m_hDC, m_ptResolution.x, m_ptResolution.y);
//새로 만든 목적지의 DC가 필요함. (어떤 구조인지에 대한 Device Context가 필요함)
m_memDC = CreateCompatibleDC(m_hDC);
//이번엔 그림을 그릴 타겟, 목적지를 교체해주는 것. 위에서 만든 비트맵을 전달해줌.
//되돌아 리턴되는 건 원래 비트맵이지 않을까? 기본적으로 들어가있는 1픽셀짜리 Default 비트맵.
HBITMAP holdBit = (HBITMAP)SelectObject(m_memDC, m_hBit);
DeleteObject(holdBit);
//Manager initialize
CTimeMgr::GetInstance()->init();
CkeyMgr::GetInstance()->init();
g_obj.SetPos(Vec2((float)(m_ptResolution.x / 2), (float)(m_ptResolution.y / 2)));
g_obj.SetScale(Vec2(100, 100));
return S_OK;
}
void CCore::progress() {
//Manager Update
CTimeMgr::GetInstance()->update();
update();
render();
}
void CCore::update()
{
Vec2 vPos = g_obj.GetPos();
//비동기 키 입출력 함수. <- 코드가 바로 실행됨. 백그라운드여도 바로 수행됨
//return 되는 건 어느정도 상태 값을 알려줌.
if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
//왼쪽 키가 눌렸을 경우.
//정수대로 움직이면, 엄청난 이동속도가 됨.
//107.7이 좌표더라고 107이나 108이 됨.
//컴퓨터 성능에 따라 움직이는 양이 달라질 것.
//그래서 시간 동기화가 필요함. 1초마다 움직이는 양이 정해져야만 함.
vPos.x -= 200.f * fDT;
}
if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
vPos.x += 200.f * fDT;
}
//물체들의 변경점을 체크하는 곳(좌표나 여러가지 변경점)
g_obj.SetPos(vPos);
}
void CCore::render()
{
//그리기 작업 하기 전, 화면 Clear
//커다란 사각형으로 덮어버리기. 이러면 원래 자각형이 보이지도 않으니까.
//우리는 2개의 캔버스를 사용하여, 한 곳에다가 다 그린 후 -> 보여지는 화면에 옮기기. (이중 버퍼링)
Rectangle(m_memDC, -1, -1, m_ptResolution.x + 1, m_ptResolution.y + 1);
//그리기 작업.
Vec2 vPos = g_obj.GetPos();
Vec2 vScale = g_obj.GetScale();
Rectangle(m_memDC, int(vPos.x - vScale.x / 2.f),
int(vPos.y - vScale.y / 2.f),
int(vPos.x + vScale.x / 2.f),
int(vPos.y + vScale.y / 2.f));
Rectangle(m_memDC, int(vPos.x - vScale.x),
int(vPos.y - vScale.y),
int(vPos.x + vScale.x),
int(vPos.y + vScale.y));
//검은 잔상이 남는데, 움직일 때마다 전부 안지워줘서 그럼.
//물체가 많아지고 여러개가 되면 에러가 있늠
//1. 잔상이 계속 남는 문제.
//다 그린 걸 한 픽셀씩 열심히 옮기는 것 -> 여기까지만 해도 프레임이 많이 떨어졌네.
//반복문이 너무 많음. -> 픽셀 한땀한땀 전부 옮기기 때문... 너무 혹사함.
BitBlt(m_hDC, 0, 0, m_ptResolution.x, m_ptResolution.y, m_memDC, 0, 0, SRCCOPY);
//이런 반복적인 렌더링 처리를 하기 위한 장치가 GPU이다.
//DirectX는 GPU를 컨트롤하는 처리.
}
- 주석을 전부 달아놓긴 했지만, 중요한 부분만 보자면 ~CCore에서 Delete로 새로운 캔버스를 소멸자에서 지워줘야 한다.
- init함수에서 새로운 Bitmap(캔버스)와 DC(어떤 구조인지에 대한 Device Context)를 만들어서 받아온다.
- SelectObject로 그리기 전용 캔버스에다가 그리라고 선택한다. 그러면 기존에 사용하던 1픽셀짜리 Default 비트맵이 나오는데, 그건 Delete로 지워준다.
- update문에서 기존에 좌표를 움직이던 함수에 *fDT로 델타타임을 곱해 시간 동기화를 해준다.
- BitBlt()로 그리기 전용 비트맵에 그려놓은 그림을 사용자가 보는 캔버스에 옮겨놓는다.
댓글 남기기