Thinking Different




Tutorial 15 - FPS, CPU 사용량, 타이머



원문 : http://www.rastertek.com/dx11tut15.html



 이 튜토리얼에서는 fps 카운터와 cpu 사용량 측정, 고해상도 시간 측정 기능을 캡슐화하는 세 클래스를 소개할 것입니다. 이 튜토리얼의 소스코드는 지난 글꼴 엔진 튜토리얼에서 이어집니다.


 첫번째 클래스는 FpsClass입니다. FpsClass는 프로그램이 실행되는 동안의 초당 프레임 수(FPS, frames per second)를 기록하는 일을 담당합니다. 초당 몇 프레임을 그리는지에 대한 정보는 프로그램의 성능을 측정하는 데 좋은 기준이 되기 때문에 실무에서는 이 그래픽 렌더링 속도가 받아들여질 만한지 판단하는 거의 표준적인 기준이기도 합니다. 또한 새로운 기능을 넣었을 때 이것이 렌더링 속도에 얼마나 영향을 미치는 지 확인할 대에도 유용합니다. 만약 새로운 기능이 프레임수를 반토막낸다면 즉각적으로 이 숫자만 가지고도 중대한 문제가 생겼음을 알 수 있습니다. 참고로 현재 일반적으로 받아들여지는 fps는 60입니다. 만약 이 밑으로 떨어진다면 뭔가 위화감이 느껴지고, 30 밑으로 내려가는 것은 사람 눈에도 뚝뚝 끊기는 것이 보이게 됩니다. 일반적으로는 프로그래밍을 할 때 fps를 최대가 되도록 하고 새로운 기능이 심각한 속도의 저하를 가져온다면 그 이유가 무엇인지 확인하거나 최소한 관심이라도 가지도록 해야 합니다.


 두번째 클래스는 CpuClass입니다. 이 클래스는 화면에 현재 cpu 사용률을 표시할 수 있도록 cpu 사용률을 기록하는 일을 담당합니다. cpu 사용량을 아는 것도 fps의 경우처럼 코드의 새로운 변화를 디버깅할 때 유용합니다. 이것 역시 최근에 작성한 좋지 않은 코드나 알고리즘이 있는지 확인하는 데 좋은 판단 정보를 제공합니다.


 마지막으로 TimerClass가 있습니다. 이것은 고해상도 타이머가 되는데, 이를 시간에 따른 이벤트에 적용하거나 프로그램과 그에 붙은 다양한 요소들이 잘 동기화될 수 있도록 하는데 사용할 수 있습니다.





기존 듀토리얼에 FpsClass, CpuClass, TimerClass 가 포함된 프레임워크는 아래와 같습니다.



프레임워크






FpsClass는 단순히 타이머가 있는 카운터입니다. 1초의 시간 동안 얼만큼의 프레임들이 그려지는지 세고 정기적으로 이 숫자를 갱신합니다.



FpsClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once
 
/////////////
// LINKING //
/////////////
#pragma comment(lib, "winmm.lib")
 
 
class FpsClass
{
public:
    FpsClass();
    FpsClass(const FpsClass&);
    ~FpsClass();
 
    void Initialize();
    void Frame();
    int GetFps();
 
private:
    int m_fps, m_count;
    unsigned long m_startTime;
};
cs



FpsClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "stdafx.h"
#include <mmsystem.h>
#include "FpsClass.h"
 
 
FpsClass::FpsClass()
{
}
 
 
FpsClass::FpsClass(const FpsClass& other)
{
}
 
 
FpsClass::~FpsClass()
{
}
 
 
void FpsClass::Initialize()
{
    m_fps = 0;
    m_count = 0;
    m_startTime = timeGetTime();
    return;
}
 
 
void FpsClass::Frame()
{
    m_count++;
 
    if (timeGetTime() >= (m_startTime + 1000))
    {
        m_fps = m_count;
        m_count = 0;
 
        m_startTime = timeGetTime();
    }
}
 
 
int FpsClass::GetFps()
{
    return m_fps;
}
cs






CpuClass는 매 초마다 cpu 사용량의 총량을 재는 데 사용됩니다.


CpuClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#pragma once
 
/////////////
// LINKING //
/////////////
#pragma comment(lib, "pdh.lib")
#include <pdh.h>
 
class CpuClass
{
public:
    CpuClass();
    CpuClass(const CpuClass&);
    ~CpuClass();
 
    void Initialize();
    void Shutdown();
    void Frame();
    int GetCpuPercentage();
 
private:
    bool m_canReadCpu = true;
    HQUERY m_queryHandle;
    HCOUNTER m_counterHandle;
    unsigned long m_lastSampleTime = 0;
    long m_cpuUsage = 0;
};
cs



CpuClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "stdafx.h"
#include "CpuClass.h"
 
 
CpuClass::CpuClass()
{
}
 
 
CpuClass::CpuClass(const CpuClass& other)
{
}
 
 
CpuClass::~CpuClass()
{
}
 
void CpuClass::Initialize()
{
    // CPU 사용을 폴링하는 쿼리 개체를 만듭니다.
    PDH_STATUS status = PdhOpenQuery(NULL0&m_queryHandle);
    if (status != ERROR_SUCCESS)
    {
        m_canReadCpu = false;
    }
 
    // 시스템의 모든 CPU를 폴링하도록 쿼리 개체를 설정합니다.
    status = PdhAddCounter(m_queryHandle, TEXT("\\Processor(_Total)\\% processor time"), 0&m_counterHandle);
    if (status != ERROR_SUCCESS)
    {
        m_canReadCpu = false;
    }
 
    m_lastSampleTime = GetTickCount();
 
    m_cpuUsage = 0;
}
 
 
void CpuClass::Shutdown()
{
    if (m_canReadCpu)
    {
        PdhCloseQuery(m_queryHandle);
    }
}
 
 
void CpuClass::Frame()
{
    PDH_FMT_COUNTERVALUE value;
 
    if (m_canReadCpu)
    {
        if ((m_lastSampleTime + 1000< GetTickCount())
        {
            m_lastSampleTime = GetTickCount();
 
            PdhCollectQueryData(m_queryHandle);
 
            PdhGetFormattedCounterValue(m_counterHandle, PDH_FMT_LONG, NULL&value);
 
            m_cpuUsage = value.longValue;
        }
    }
}
 
 
int CpuClass::GetCpuPercentage()
{
    int usage = 0;
 
    if (m_canReadCpu)
    {
        usage = (int)m_cpuUsage;
    }
 
    return usage;
}
cs




TimerClass는 실행 중 매 프레임 간의 정확한 시간을 재는 고해상도 타이머 클래스입니다. 이것의 주요 용도는 오브젝트의 이동을 위해 프레임의 표준 시간대와의 동기화입니다. 이 튜토리얼에서는 직접 써먹을 일은 없지만 어떻게 적용할 수 있는지 보여주는 코드를 작성할 것입니다. 대개 TimerClass는 프레임간 간격을 1초에 대한 %로 바꿔서 물체를 그만큼 움직이도록 하는 경우에 많이 사용합니다.



TimerClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once
 
class TimerClass
{
public:
    TimerClass();
    TimerClass(const TimerClass&);
    ~TimerClass();
 
    bool Initialize();
    void Frame();
 
    float GetTime();
 
private:
    INT64 m_frequency = 0;
    float m_ticksPerMs = 0;
    INT64 m_startTime = 0;
    float m_frameTime = 0;
};
cs



TimerClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "stdafx.h"
#include "TimerClass.h"
 
 
TimerClass::TimerClass()
{
}
 
 
TimerClass::TimerClass(const TimerClass& other)
{
}
 
 
TimerClass::~TimerClass()
{
}
 
 
bool TimerClass::Initialize()
{
    // 이 시스템이 고성능 타이머를 지원하는지 확인하십시오.
    QueryPerformanceFrequency((LARGE_INTEGER*)&m_frequency);
    if (m_frequency == 0)
    {
        return false;
    }
 
    // 빈도 카운터가 매 밀리 초마다 틱하는 횟수를 확인합니다.
    m_ticksPerMs = (float)(m_frequency / 1000);
 
    QueryPerformanceCounter((LARGE_INTEGER*)&m_startTime);
 
    return true;
}
 
 
void TimerClass::Frame()
{
    INT64 currentTime = 0;
 
    QueryPerformanceCounter((LARGE_INTEGER*)&currentTime);
 
    float timeDifference = (float)(currentTime - m_startTime);
 
    m_frameTime = timeDifference / m_ticksPerMs;
 
    m_startTime = currentTime;
}
 
 
float TimerClass::GetTime()
{
    return m_frameTime;
}
cs




위 클래스를 사용하도록 설정하겠습니다.


SystemClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#pragma once
 
 
class InputClass;
class GraphicsClass;
class FpsClass;
class CpuClass;
class TimerClass;
 
 
class SystemClass
{
public:
    SystemClass();
    SystemClass(const SystemClass&);
    ~SystemClass();
 
    bool Initialize();
    void Shutdown();
    void Run();
 
    LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
 
private:
    bool Frame();
    void InitializeWindows(int&int&);
    void ShutdownWindows();
 
private:
    LPCWSTR m_applicationName;
    HINSTANCE m_hinstance;
    HWND m_hwnd;
 
    InputClass* m_Input = nullptr;
    GraphicsClass* m_Graphics = nullptr;
    FpsClass* m_Fps = nullptr;
    CpuClass* m_Cpu = nullptr;
    TimerClass* m_Timer = nullptr;
};
 
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static SystemClass* ApplicationHandle = 0;
cs



SystemClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#include "stdafx.h"
#include "inputclass.h"
#include "graphicsclass.h"
#include "FpsClass.h"
#include "CpuClass.h"
#include "TimerClass.h"
#include "systemclass.h"
 
 
SystemClass::SystemClass()
{
}
 
 
SystemClass::SystemClass(const SystemClass& other)
{
}
 
 
SystemClass::~SystemClass()
{
}
 
 
bool SystemClass::Initialize()
{
    // 윈도우 창 가로, 세로 넓이 변수 초기화
    int screenWidth = 0;
    int screenHeight = 0;
 
    // 윈도우 생성 초기화
    InitializeWindows(screenWidth, screenHeight);
 
    // m_Input 객체 생성. 이 클래스는 추후 사용자의 키보드 입력 처리에 사용됩니다.
    m_Input = new InputClass;
    if (!m_Input)
    {
        return false;
    }
 
    // m_Input 객체 초기화
    if(!m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight))
    {
        MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
        return false;
    }
 
    // m_Graphics 객체 생성.  그래픽 랜더링을 처리하기 위한 객체입니다.
    m_Graphics = new GraphicsClass;
    if (!m_Graphics)
    {
        return false;
    }
 
    // m_Graphics 객체 초기화.
    if(!m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd))
    {
        return false;
    }
    
    // fps 객체를 만듭니다.
    m_Fps = new FpsClass;
    if(!m_Fps)
    {
        return false;
    }
 
    // fps 객체를 초기화합니다.
    m_Fps->Initialize();
 
    // cpu 객체를 만듭니다.
    m_Cpu = new CpuClass;
    if(!m_Cpu)
    {
        return false;
    }
 
    // cpu 객체를 초기화합니다.
    m_Cpu->Initialize();
 
    // 타이머 객체를 만듭니다.
    m_Timer = new TimerClass;
    if(!m_Timer)
    {
        return false;
    }
 
    // 타이머 객체를 초기화합니다.
    if(!m_Timer->Initialize())
    {
        MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK);
        return false;
    }
 
    return true;
}
 
 
void SystemClass::Shutdown()
{
    // 타이머 객체를 해제합니다.
    if(m_Timer)
    {
        delete m_Timer;
        m_Timer = 0;
    }
 
    // cpu 객체를 해제합니다.
    if(m_Cpu)
    {
        m_Cpu->Shutdown();
        delete m_Cpu;
        m_Cpu = 0;
    }
 
    // fps 객체를 해제합니다.
    if(m_Fps)
    {
        delete m_Fps;
        m_Fps = 0;
    }
 
    // m_Graphics 객체 반환
    if (m_Graphics)
    {
        m_Graphics->Shutdown();
        delete m_Graphics;
        m_Graphics = 0;
    }
 
    // m_Input 객체 반환
    if (m_Input)
    {
        m_Input->Shutdown();
        delete m_Input;
        m_Input = 0;
    }
 
    // Window 종료 처리
    ShutdownWindows();
}
 
 
void SystemClass::Run()
{
    // 메시지 구조체 생성 및 초기화
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
 
    // 사용자로부터 종료 메시지를 받을때까지 메시지루프를 돕니다
    while (true)
    {
        // 윈도우 메시지를 처리합니다
        if (PeekMessage(&msg, NULL00, PM_REMOVE))
        {
            // 종료 메시지를 받을 경우 메시지 루프를 탈출합니다
            if (msg.message == WM_QUIT)
                break;
 
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            // 그 외에는 Frame 함수를 처리합니다.
            if (!Frame())
            {
                MessageBox(m_hwnd, L"Frame Processing Failed", L"Error", MB_OK);
                break;
            }
        }
        
        // 사용자가 ESC키를 눌렀는지 확인 후 종료 처리함
        if(m_Input->IsEscapePressed() == true)
        {
            break;
        }
    }
}
 
 
bool SystemClass::Frame()
{
    // 시스템 통계를 업데이트 합니다
    m_Timer->Frame();
    m_Fps->Frame();
    m_Cpu->Frame();
 
    // 입력 프레임 처리를 수행합니다
    if(!m_Input->Frame())
    {
        return false;
    }
 
    // 그래픽 객체에 대한 프레임 처리를 수행합니다.
    if(!m_Graphics->Frame(m_Fps->GetFps(), m_Cpu->GetCpuPercentage(), m_Timer->GetTime()))
    {
        return false;
    }
 
    return m_Graphics->Render();
}
 
 
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    return DefWindowProc(hwnd, umsg, wparam, lparam);
}
 
 
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
    // 외부 포인터를 이 객체로 지정합니다
    ApplicationHandle = this;
 
    // 이 프로그램의 인스턴스를 가져옵니다
    m_hinstance = GetModuleHandle(NULL);
 
    // 프로그램 이름을 지정합니다
    m_applicationName = L"Dx11Demo_15";
 
    // windows 클래스를 아래와 같이 설정합니다.
    WNDCLASSEX wc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = m_hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    wc.hIconSm = wc.hIcon;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = m_applicationName;
    wc.cbSize = sizeof(WNDCLASSEX);
 
    // windows class를 등록합니다
    RegisterClassEx(&wc);
 
    // 모니터 화면의 해상도를 읽어옵니다
    screenWidth = GetSystemMetrics(SM_CXSCREEN);
    screenHeight = GetSystemMetrics(SM_CYSCREEN);
 
    int posX = 0;
    int posY = 0;
 
    // FULL_SCREEN 변수 값에 따라 화면을 설정합니다.
    if (FULL_SCREEN)
    {
        // 풀스크린 모드로 지정했다면 모니터 화면 해상도를 데스크톱 해상도로 지정하고 색상을 32bit로 지정합니다.
        DEVMODE dmScreenSettings;
        memset(&dmScreenSettings, 0sizeof(dmScreenSettings));
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth;
        dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight;
        dmScreenSettings.dmBitsPerPel = 32;
        dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
 
        // 풀스크린으로 디스플레이 설정을 변경합니다.
        ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
    }
    else
    {
        // 윈도우 모드의 경우 800 * 600 크기를 지정합니다.
        screenWidth = 800;
        screenHeight = 600;
 
        // 윈도우 창을 가로, 세로의 정 가운데 오도록 합니다.
        posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2;
        posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2;
    }
 
    // 윈도우를 생성하고 핸들을 구합니다.
    m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName,
        WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP,
        posX, posY, screenWidth, screenHeight, NULLNULL, m_hinstance, NULL);
 
    // 윈도우를 화면에 표시하고 포커스를 지정합니다
    ShowWindow(m_hwnd, SW_SHOW);
    SetForegroundWindow(m_hwnd);
    SetFocus(m_hwnd);
}
 
 
void SystemClass::ShutdownWindows()
{
    // 풀스크린 모드였다면 디스플레이 설정을 초기화합니다.
    if (FULL_SCREEN)
    {
        ChangeDisplaySettings(NULL0);
    }
 
    // 창을 제거합니다
    DestroyWindow(m_hwnd);
    m_hwnd = NULL;
 
    // 프로그램 인스턴스를 제거합니다
    UnregisterClass(m_applicationName, m_hinstance);
    m_hinstance = NULL;
 
    // 외부포인터 참조를 초기화합니다
    ApplicationHandle = NULL;
}
 
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
    switch (umessage)
    {
        // 윈도우 종료를 확인합니다
    case WM_DESTROY:
    {
        PostQuitMessage(0);
        return 0;
    }
 
    // 윈도우가 닫히는지 확인합니다
    case WM_CLOSE:
    {
        PostQuitMessage(0);
        return 0;
    }
 
    // 그 외의 모든 메시지들은 시스템 클래스의 메시지 처리로 넘깁니다.
    default:
    {
        return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
    }
    }
}
cs






가능한 빠르게 프로그램이 실행되도록 하기 위해 이 튜토리얼에서는 수직동기화를 끄도록 합니다.



GraphicsClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#pragma once
 
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = false;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
 
 
class D3DClass;
class CameraClass;
class TextClass;
 
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();
 
    bool Initialize(intint, HWND);
    void Shutdown();
    bool Frame(intintfloat);
    bool Render();
 
private:
    D3DClass* m_Direct3D = nullptr;
    CameraClass* m_Camera = nullptr;
    TextClass* m_Text = nullptr;
};
cs



GraphicsClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include "stdafx.h"
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"
#include "graphicsclass.h"
 
 
GraphicsClass::GraphicsClass()
{
}
 
 
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
 
 
GraphicsClass::~GraphicsClass()
{
}
 
 
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    // Direct3D 객체 생성
    m_Direct3D = new D3DClass;
    if(!m_Direct3D)
    {
        return false;
    }
 
    // Direct3D 객체 초기화
    if(!m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR))
    {
        MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
        return false;
    }
 
    // m_Camera 객체 생성
    m_Camera = new CameraClass;
    if (!m_Camera)
    {
        return false;
    }
 
    // 카메라 포지션 설정
    XMMATRIX baseViewMatrix;
    m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
    m_Camera->Render();
    m_Camera->GetViewMatrix(baseViewMatrix);
 
    // m_Text 객체 생성
    m_Text = new TextClass;
    if(!m_Text)
    {
        return false;
    }
 
    // m_Text 객체 초기화
    if (!m_Text->Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), hwnd, screenWidth, screenHeight,
 baseViewMatrix))
    {
        MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
        return false;
    }
 
    return true;
}
 
 
void GraphicsClass::Shutdown()
{
    // m_Text 객체 반환
    if (m_Text)
    {
        m_Text->Shutdown();
        delete m_Text;
        m_Text = 0;
    }
 
    // m_Camera 객체 반환
    if (m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }
 
    // Direct3D 객체 반환
    if (m_Direct3D)
    {
        m_Direct3D->Shutdown();
        delete m_Direct3D;
        m_Direct3D = 0;
    }
}
 
 
bool GraphicsClass::Frame(int fps, int cpu, float frameTime)
{
    // 초당 프레임 수를 설정합니다.
    if(!m_Text->SetFps(fps, m_Direct3D->GetDeviceContext()))
    {
        return false;
    }
 
    // cpu 사용을 설정합니다.
    if(!m_Text->SetCpu(cpu, m_Direct3D->GetDeviceContext()))
    {
        return false;
    }
 
    // 카메라 위치 설정
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
 
    return true;
}
 
 
bool GraphicsClass::Render()
{
    // 씬을 그리기 위해 버퍼를 지웁니다
    m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
 
    // 카메라의 위치에 따라 뷰 행렬을 생성합니다
    m_Camera->Render();
 
    // 카메라 및 d3d 객체에서 월드, 뷰 및 투영 행렬을 가져옵니다
    XMMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
    
    m_Camera->GetViewMatrix(viewMatrix);
    m_Direct3D->GetWorldMatrix(worldMatrix);
    m_Direct3D->GetProjectionMatrix(projectionMatrix);
    m_Direct3D->GetOrthoMatrix(orthoMatrix);
 
    // 모든 2D 렌더링을 시작하려면 Z 버퍼를 끕니다.
    m_Direct3D->TurnZBufferOff();
 
    // 텍스트를 렌더링하기 전에 알파 블렌딩을 켭니다
    m_Direct3D->TurnOnAlphaBlending();
 
    // 텍스트 문자열을 렌더링 합니다
    if(!m_Text->Render(m_Direct3D->GetDeviceContext(), worldMatrix, orthoMatrix))
    {
        return false;
    }
 
    // 텍스트를 렌더링 한 후 알파 블렌딩을 해제합니다
    m_Direct3D->TurnOffAlphaBlending();
 
    // 모든 2D 렌더링이 완료되었으므로 Z 버퍼를 다시 켜십시오.
    m_Direct3D->TurnZBufferOn();
 
    // 버퍼의 내용을 화면에 출력합니다
    m_Direct3D->EndScene();
 
    return true;
}
cs



TextClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#pragma once
 
class FontClass;
class FontShaderClass;
 
class TextClass : public AlignedAllocationPolicy<16>
{
private:
    struct SentenceType
    {
        ID3D11Buffer *vertexBuffer, *indexBuffer;
        int vertexCount, indexCount, maxLength;
        float red, green, blue;
    };
 
    struct VertexType
    {
        XMFLOAT3 position;
        XMFLOAT2 texture;
    };
 
public:
    TextClass();
    TextClass(const TextClass&);
    ~TextClass();
 
    bool Initialize(ID3D11Device*, ID3D11DeviceContext*, HWND, intint, XMMATRIX);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, XMMATRIX, XMMATRIX);
 
    bool SetFps(int, ID3D11DeviceContext*);
    bool SetCpu(int, ID3D11DeviceContext*);
 
private:
    bool InitializeSentence(SentenceType**int, ID3D11Device*);
    bool UpdateSentence(SentenceType*char*intintfloatfloatfloat, ID3D11DeviceContext*);
    void ReleaseSentence(SentenceType**);
    bool RenderSentence(ID3D11DeviceContext*, SentenceType*, XMMATRIX, XMMATRIX);
 
private:
    FontClass* m_Font = nullptr;
    FontShaderClass* m_FontShader = nullptr;
    int m_screenWidth = 0;
    int m_screenHeight = 0;
    XMMATRIX m_baseViewMatrix;
    SentenceType* m_sentence1;
    SentenceType* m_sentence2;
};
cs




TextClass.cpp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#include "stdafx.h"
#include "FontClass.h"
#include "FontShaderClass.h"
#include "TextClass.h"
 
 
TextClass::TextClass()
{
}
 
TextClass::TextClass(const TextClass& other)
{
}
 
TextClass::~TextClass()
{
}
 
bool TextClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, HWND hwnd, int screenWidth,
 int screenHeight, XMMATRIX baseViewMatrix)
{
    // 화면 너비와 높이를 저장합니다.
    m_screenWidth = screenWidth;
    m_screenHeight = screenHeight;
 
    // 기본 뷰 매트릭스를 저장합니다.
    m_baseViewMatrix = baseViewMatrix;
 
    // 폰트 객체를 생성합니다.
    m_Font = new FontClass;
    if (!m_Font)
    {
        return false;
    }
 
    // 폰트 객체를 초기화 합니다.
    bool result = m_Font->Initialize(device, "../Dx11Demo_15/data/fontdata.txt", L"../Dx11Demo_15/data/font.dds");
    if (!result)
    {
        MessageBox(hwnd, L"Could not initialize the font object.", L"Error", MB_OK);
        return false;
    }
 
    // 폰트 쉐이더 객체를 생성합니다.
    m_FontShader = new FontShaderClass;
    if (!m_FontShader)
    {
        return false;
    }
 
    // 폰트 쉐이더 객체를 초기화 합니다.
    result = m_FontShader->Initialize(device, hwnd);
    if (!result)
    {
        MessageBox(hwnd, L"Could not initialize the font shader object.", L"Error", MB_OK);
        return false;
    }
 
    // 첫 번째 문장을 초기화합니다.
    result = InitializeSentence(&m_sentence1, 16, device);
    if (!result)
    {
        return false;
    }
 
    // 문장 정점 버퍼를 새 문자열 정보로 업데이트합니다.
    result = UpdateSentence(m_sentence1, "Fps: "20200.0f, 1.0f, 0.0f, deviceContext);
    if (!result)
    {
        return false;
    }
 
    // 첫 번째 문장을 초기화합니다.
    result = InitializeSentence(&m_sentence2, 16, device);
    if (!result)
    {
        return false;
    }
 
    // 문장 정점 버퍼를 새 문자열 정보로 업데이트합니다.
    result = UpdateSentence(m_sentence2, "Cpu: "20400.0f, 1.0f, 0.0f, deviceContext);
    if (!result)
    {
        return false;
    }
 
    return true;
}
 
 
void TextClass::Shutdown()
{
    // 첫번째 문장을 반환합니다.
    ReleaseSentence(&m_sentence1);
 
    // 두번째 문장을 반환합니다.
    ReleaseSentence(&m_sentence2);
 
    // 폰트 쉐이더 객체를 반환합니다.
    if (m_FontShader)
    {
        m_FontShader->Shutdown();
        delete m_FontShader;
        m_FontShader = 0;
    }
 
    // 폰트 객체를 반환합니다.
    if (m_Font)
    {
        m_Font->Shutdown();
        delete m_Font;
        m_Font = 0;
    }
}
 
 
bool TextClass::Render(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX orthoMatrix)
{
    // 첫번째 문장을 그립니다.
    if (!RenderSentence(deviceContext, m_sentence1, worldMatrix, orthoMatrix))
    {
        return false;
    }
 
    // 두번째 문장을 그립니다.
    return RenderSentence(deviceContext, m_sentence2, worldMatrix, orthoMatrix);
}
 
 
bool TextClass::InitializeSentence(SentenceType** sentence, int maxLength, ID3D11Device* device)
{
    // 새로운 문장 개체를 만듭니다.
    *sentence = new SentenceType;
    if (!*sentence)
    {
        return false;
    }
 
    // 문장 버퍼를 null로 초기화합니다.
    (*sentence)->vertexBuffer = 0;
    (*sentence)->indexBuffer = 0;
 
    // 문장의 최대 길이를 설정합니다.
    (*sentence)->maxLength = maxLength;
 
    // 정점 배열의 정점 수를 설정합니다.
    (*sentence)->vertexCount = 6 * maxLength;
 
    // 인덱스 배열의 인덱스 수를 설정합니다.
    (*sentence)->indexCount = (*sentence)->vertexCount;
 
    // 정점 배열을 만듭니다.
    VertexType* vertices = new VertexType[(*sentence)->vertexCount];
    if (!vertices)
    {
        return false;
    }
 
    // 인덱스 배열을 만듭니다.
    unsigned long* indices = new unsigned long[(*sentence)->indexCount];
    if (!indices)
    {
        return false;
    }
 
    // 처음에는 정점 배열을 0으로 초기화합니다.
    memset(vertices, 0, (sizeof(VertexType) * (*sentence)->vertexCount));
 
    // 인덱스 배열을 초기화합니다.
    for (int i = 0; i<(*sentence)->indexCount; i++)
    {
        indices[i] = i;
    }
 
    // 동적 인 정점 버퍼의 설명을 설정한다.
    D3D11_BUFFER_DESC vertexBufferDesc;
    vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * (*sentence)->vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;
 
    // subresource 구조에 정점 데이터에 대한 포인터를 제공합니다.
    D3D11_SUBRESOURCE_DATA vertexData;
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;
 
    // 정점 버퍼를 만든다.
    if (FAILED(device->CreateBuffer(&vertexBufferDesc, &vertexData, &(*sentence)->vertexBuffer)))
    {
        return false;
    }
 
    // 정적 인덱스 버퍼의 설명을 설정합니다.
    D3D11_BUFFER_DESC indexBufferDesc;
    
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long* (*sentence)->indexCount;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
    indexBufferDesc.StructureByteStride = 0;
 
    // 하위 리소스 구조에 인덱스 데이터에 대한 포인터를 제공합니다.
    D3D11_SUBRESOURCE_DATA indexData;
    indexData.pSysMem = indices;
    indexData.SysMemPitch = 0;
    indexData.SysMemSlicePitch = 0;
 
    // 인덱스 버퍼를 만듭니다.
    if (FAILED(device->CreateBuffer(&indexBufferDesc, &indexData, &(*sentence)->indexBuffer)))
    {
        return false;
    }
 
    // 더 이상 필요하지 않은 정점 배열을 해제합니다.
    delete[] vertices;
    vertices = 0;
 
    // 더 이상 필요하지 않은 인덱스 배열을 해제합니다.
    delete[] indices;
    indices = 0;
 
    return true;
}
 
 
bool TextClass::UpdateSentence(SentenceType* sentence, char* text, int positionX, int positionY, float red, float green,
 float blue, ID3D11DeviceContext* deviceContext)
{
    // 문장의 색을 저장한다.
    sentence->red = red;
    sentence->green = green;
    sentence->blue = blue;
 
    // 가능한 버퍼 오버 플로우를 확인합니다.
    if ((int)strlen(text) > sentence->maxLength)
    {
        return false;
    }
 
    // 정점 배열을 만듭니다.
    VertexType* vertices = new VertexType[sentence->vertexCount];
    if (!vertices)
    {
        return false;
    }
 
    // 처음에는 정점 배열을 0으로 초기화합니다.
    memset(vertices, 0, (sizeof(VertexType) * sentence->vertexCount));
 
    // 그리기를 시작할 화면에서 X 및 Y 픽셀 위치를 계산합니다.
    float drawX = (float)(((m_screenWidth / 2* -1+ positionX);
    float drawY = (float)((m_screenHeight / 2- positionY);
 
    // 폰트 클래스를 사용하여 문장 텍스트와 문장 그리기 위치에서 정점 배열을 만듭니다.
    m_Font->BuildVertexArray((void*)vertices, text, drawX, drawY);
 
    // 버텍스 버퍼를 쓸 수 있도록 잠급니다.
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    if (FAILED(deviceContext->Map(sentence->vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0&mappedResource)))
    {
        return false;
    }
 
    // 정점 버퍼의 데이터를 가리키는 포인터를 얻는다.
    VertexType* verticesPtr = (VertexType*)mappedResource.pData;
 
    // 데이터를 정점 버퍼에 복사합니다.
    memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * sentence->vertexCount));
 
    // 정점 버퍼의 잠금을 해제합니다.
    deviceContext->Unmap(sentence->vertexBuffer, 0);
 
    // 더 이상 필요하지 않은 정점 배열을 해제합니다.
    delete[] vertices;
    vertices = 0;
 
    return true;
}
 
 
void TextClass::ReleaseSentence(SentenceType** sentence)
{
    if (*sentence)
    {
        // 문장 버텍스 버퍼를 해제합니다.
        if ((*sentence)->vertexBuffer)
        {
            (*sentence)->vertexBuffer->Release();
            (*sentence)->vertexBuffer = 0;
        }
 
        // 문장 인덱스 버퍼를 해제합니다.
        if ((*sentence)->indexBuffer)
        {
            (*sentence)->indexBuffer->Release();
            (*sentence)->indexBuffer = 0;
        }
 
        // 문장을 해제합니다.
        delete *sentence;
        *sentence = 0;
    }
}
 
 
bool TextClass::RenderSentence(ID3D11DeviceContext* deviceContext, SentenceType* sentence, XMMATRIX worldMatrix,
 XMMATRIX orthoMatrix)
{
    // 정점 버퍼 간격 및 오프셋을 설정합니다.
    unsigned int stride = sizeof(VertexType);
    unsigned int offset = 0;
 
    // 렌더링 할 수 있도록 입력 어셈블러에서 정점 버퍼를 활성으로 설정합니다.
    deviceContext->IASetVertexBuffers(01&sentence->vertexBuffer, &stride, &offset);
 
    // 렌더링 할 수 있도록 입력 어셈블러에서 인덱스 버퍼를 활성으로 설정합니다.
    deviceContext->IASetIndexBuffer(sentence->indexBuffer, DXGI_FORMAT_R32_UINT, 0);
 
    // 이 정점 버퍼에서 렌더링 되어야 하는 프리미티브 유형을 설정합니다.이 경우에는 삼각형입니다.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 
    // 입력 된 문장 색상으로 픽셀 색상 벡터를 만듭니다.
    XMFLOAT4 pixelColor = XMFLOAT4(sentence->red, sentence->green, sentence->blue, 1.0f);
 
    // 폰트 셰이더를 사용하여 텍스트를 렌더링합니다.
    if (!m_FontShader->Render(deviceContext, sentence->indexCount, worldMatrix, m_baseViewMatrix, orthoMatrix,
 m_Font->GetTexture(), pixelColor))
    {
        false;
    }
 
    return true;
}
 
bool TextClass::SetFps(int fps, ID3D11DeviceContext* deviceContext)
{
    // fps를 10,000 이하로 자릅니다.
    if(fps > 9999)
    {
        fps = 9999;
    }
 
    // fps 정수를 문자열 형식으로 변환합니다.
    char tempString[16= { 0, };
    _itoa_s(fps, tempString, 10);
 
    // fps 문자열을 설정합니다.
    char fpsString[16= { 0, };
    strcpy_s(fpsString, "Fps: ");
    strcat_s(fpsString, tempString);
 
    float red = 0;
    float green = 0;
    float blue = 0;
 
    // fps가 60 이상이면 fps 색상을 녹색으로 설정합니다.
    if(fps >= 60)
    {
        red = 0.0f;
        green = 1.0f;
        blue = 0.0f;
    }
 
    // fps가 60보다 작은 경우 fps 색상을 노란색으로 설정합니다.
    if(fps < 60)
    {
        red = 1.0f;
        green = 1.0f;
        blue = 0.0f;
    }
 
    // fps가 30 미만이면 fps 색상을 빨간색으로 설정합니다.
    if(fps < 30)
    {
        red = 1.0f;
        green = 0.0f;
        blue = 0.0f;
    }
 
    // 문장 정점 버퍼를 새 문자열 정보로 업데이트합니다.
    return UpdateSentence(m_sentence1, fpsString, 2020, red, green, blue, deviceContext);
}
 
 
bool TextClass::SetCpu(int cpu, ID3D11DeviceContext* deviceContext)
{
    // cpu 정수를 문자열 형식으로 변환합니다.
    char tempString[16= { 0, };
    _itoa_s(cpu, tempString, 10);
 
    // cpu 문자열을 설정합니다.
    char cpuString[16= { 0, };
    strcpy_s(cpuString, "Cpu: ");
    strcat_s(cpuString, tempString);
    strcat_s(cpuString, "%");
 
    // 문장 정점 버퍼를 새 문자열 정보로 업데이트합니다.
    return UpdateSentence(m_sentence2, cpuString, 20400.0f, 1.0f, 0.0f, deviceContext);
}
cs





출력 화면




마치면서


이제 화면에 fps와 cpu 사용량이 표시되는 것을 볼 수 있습니다. 더불어 프레임 속도와 상관없이 오브젝트들이 일정한 속도로 움직이고 회전할 수 있도록 해 주는 정밀한 타이머도 만들었습니다.



연습문제


1. 코드를 다시 컴파일해 보고 fps와 cpu값이 표시되는지 확인해 보십시오. esc키를 눌러 빠져나갑니다.


2. graphicsclass.h에서 수직동기화(vsync)를 사용하도록 하고 여러분의 비디오카드나 모니터가 몇 프레임으로 동작하는지 확인해 보십시오.



소스코드


소스코드 : Dx11Demo_15.zip