본문 바로가기
DirectX

[DirectX 11] 스터디 1일 프레임 워크 짜기

by Minok_nok 2019. 12. 18.

스터디를 합니다.

그냥 다른 블로그 보면서 정리합니다.

 

Direct x 11 스터디 1주 차

 

Direct x 11 개요

 

참고 블로그:https://copynull.tistory.com/238?category=649932

 

Direct X

Direct X는 MicroSoft사에서 만든 2D 및 3D 관련 그래픽을 렌더링하기 위한 도구입니다.

 

DirectX는 대표적으로 IDE에 SDK를 포함시켜 사용됩니다.

 

Direct X에는 여러가지 API의 묶음인 컴포넌트로 구성이 되어있어, 사용 처에 따라 맞는 컴포넌트의 API를 사용하면 된다.

 

Direct X11 설치 및 적용

 

같은 Microsoft사의 Visual studio 2017기준으로 작성된다.

 

Direct X를 일일이 설치 했어야 했던 이전 Visual Studio와 달리 2015버전 부터 DirectX SDK가 같이 설치가 된다.

 

그러므로 프로젝트 생성과 함께 적용만 시켜주면 된다.

 

먼저 Windows 데스크톱 마법사로 빈 프로젝트를 하나 만들어줍니다.

빈프로젝트 체크 및 SDK 검사 체크를 해제 해 준다.

그런 다음 콘솔 응용 프로그램으로 설정 한 뒤 만들어준다.

 

빈 프로젝트가 만들어지면 상단에 x84(32bit)를 x64(64bit)로 바꾸어준다.




Direct x 11 프레임 워크 베이스 만들기

 

이 스터디에서 최종 목적은 엔진을 하나 만들어서 게임을 만드는겁니다.

 

저희가 주로 쓰는 Unity도 엔진이기 때문에 엔진이 돌아가는 프레임마다 돌아가는 프레임워크가 준비되어 있습니다.

 

저희도 프레임마다 돌아가며 특정 이벤트를 진행하는 프레임워크를 만들어 봅시다.

 

 

저희가 작성 할 프레임 워크의 다이어그램입니다.

 

WinMain은 프레임 워크의 진입점입니다.

 

Window콘솔의 설정들은 SystemClass에서 진행되며, Run 함수를 통해 프레임 진행을 처리합니다.

 

InputClass는 입력처리를 받으며, GraphicsClass는 그래픽 렌더를 위한 클래스입니다.

 

아래쪽에는 참고 블로그에서 나온 코드를 뜯어봅시다.



미리 컴파일 된 헤더 “Stdafx.h”

 

stdafx.h 는 자주 쓰는 헤더 여러 개를 한꺼번에 미리 컴파일을 해놓아서 재사용성을 높이는 헤더입니다.

 

저희는 Stdafx 헤더파일에서 사용되는 헤더들을 알아보는 시간을 가져봅시다.

 

사용 할 Stdafx.h 코드입니다.

 

하나하나씩 알아봅시다.

 

●  #pragma once: 한 헤더파일이 중복되어 컴파일 되지 않도록 선언을 한다. 다른 예로는 #ifndef를 사용한 예외처리가 있다.


●  “targetver.h”: 여러가지 window버전에 맞는 윈도우 플랫폼을 사용합니다.

 

이전 버전의 윈도우 플랫폼을 사용하기 위해서는 targetver.h안에 있는

#include <SDKDDKVer.h> 대신, #define _WIN32_WINNT (버전) 을 사용하여 다른 버전의 윈도우 플랫폼을 사용할 수 있도록 설정 합니다.

 

(버전)에 들어갈 코드는 https://docs.microsoft.com/ko-kr/windows/win32/winprog/using-the-windows-headers?redirectedfrom=MSDN 여기서 볼 수 있다.


●  #define WIN32_LEAN_AND_MEAN: 외부 MFC 오버헤더를 포함하지 않는다.

 

#include <windows.h>를 할 때 Windown API를 C++로 둘러싼 Library인 MFC까지 불러오는데, 게임 프로그래밍을 할 땐 MFC를 쓰지 않는다.

 

그렇기 때문에 WIN32_LEAN_AND_MEAN을 정의하여 windows헤더를 불러 올 때 MFC를 불러오지 않아 조금 더 날씬 한 프로그램을 작성 할 수 있다.


windows.h: 윈도우 개발자에게 필요한 모든 매크로, 함수, 데이터타입, API를 정의하고 있는 헤더이다.

윈도우 베이스인 Directx를 사용하기 위해서는 필수 https://ko.wikipedia.org/wiki/Windows.h


stdlib.h: c 언어의 표준라이브러리, 표함되어 있는건 문자열 변환, 난수생성, 동적 메모리 등 유용한 기능들을 포함하고 있다.

 

포함 하고 있는 것은 https://ko.wikipedia.org/wiki/Stdlib.h 이곳에서 확인 할 수 있다.


malloc.h: 동적할당관련 함수를 가지고 있는 헤더

stdlib 헤더가 있다면 사용하지 않아도 될거 같음 (아리송함)

표준 헤더도 아님


memory.h: 메모리 관련 함수들이 저장되어 있음

주로 문자열 관련으로 사용 된다. 

 

예시 https://m.blog.naver.com/PostView.nhn?blogId=sharonichoya&logNo=220508334439&proxyReferer=https%3A%2F%2Fwww.google.com%2F


tchar.h:

 

windows.h에는 typedef 키워드를 통해 windows 스타일의 새로운 이름을 정의한다.

 

char -> CHAR

 

wchar_t -> WCHAR (유니코드를 저장하는 와이드 문자, 문자열)

관련 글 https://dojang.io/mod/page/view.php?id=770

 

CHAR* -> LPSTR CHAR 문자열

 

CONST CHAR* -> LPCSTR ( “abcd”와 같은 문자열은 기본적으로 const char로 정의 됨)

 

WCHAR* -> LPWSTR

 

CONST WCHAR* -> LPCWSTR



이를 확장하는 헤더가 tchar헤더이다.

 

WCAHR와 CHAR의 차이점은 2바이트를 쓰는 와이드 문자열인지 아닌지로 구분이 되는데,

많이 사용하다 보면 상당히 귀찮다.

 

계속 구분을 한다는것은 MBCS(영어는 1바이트 한글은 2바이트)와 WBCS(전부 2바이트)를 동시에 지원 한다는 말이 된다.

 

그렇기 때문에 tchar헤더는 MBCS와 WBCS를 같은 코드를 사용하고, 플랫폼에 따라 다른 문자 타입을 사용할 수 있도록 만든다.

 

WCHAR -> TCHAR

CHAR -> TCHAR

 

LPWSTR -> LPTSTR

LPSTR -> LPTSTR

 

LPCWSTR -> LPCTSTR

LPCSTR -> LPCTSTR

 

대표적으로 앞이나 사이에 T가 들어간다.

 

사용방법은 UNICODE라는 매크로가 정의 되어 있으면 MBCS기반, 정의 되어있지 않으면 WBCS 기반의 문자열을 사용한다.



tchar에선 _T라는 매크로로 문자열을 생성하는데,  MBCS기반이면 _T(“chars”) => “chars”

WBCS기반이면 _T(“char”) => L”chars”로 변경이 된다.

 

자세한 설명 및 예시는 여기서 확인 할 수 있다.

https://m.blog.naver.com/PostView.nhn?blogId=popssong&logNo=70107145866&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 


DxDifine.h 우리가 곧 Directx 관련하여 추가 할 헤더이다.




프레임 워크의 진입점인 wWinMain

 

상당히 간결합니다.

상용 헤더와 나중에 나올 시스템 클래스를 사용하기 위해 SystemClass.h를 넣어주었습니다.

 

제일 중요한건 진입점 함수입니다.

 

WinAPI에서의 가장 기본적인 진입점으로 형태가 고정되어 있습니다.

콘솔로 만들었을 때는 main이지만 윈도우에서는 wWinMain이라고 보면 됩니다.

하지만 다른 데에선 WinMain으로 선언하는 경우도 있고, _tWinMain으로 선언하는 경우도 있습니다.

 

차이점은 유니코드 지원이 필요하면 wWinMain이고, 아스키 코드 지원이 필요하면 WinMain을 사용합니다.

_tWinMain을 사용하는 경우도 있는데 이는 tchar헤더와 같은 원리로 유니코드 지원이 필요하면 wWinMain으로 바꾸어주고, 아스키 코드를 지원하면 WinMain으로 바꾸어줍니다.

https://devday.tistory.com/entry/tWinMain-WinMain-wWinMain-%EA%B0%84%EC%9D%98-%EC%B0%A8%EC%9D%B4

 

int형을 반환하는 함수이며, APIENTRY 지정자를 사용하는데 되게 생소합니다.

APIENTRY지정자는 윈도우즈 표준 호출 규약인 __stdcall을 사용하는 콜백함수입니다..

 

WinMain으로 진입점 이름을 선언하고, 이를 콜백함수로 지정을 하면 자동으로 진입점으로 등록이 됩니다.

 

APIENTRY가 아닌 CALLBACK, WINAPI같은 지정자도 똑같은 기능을 하기 때문에 바꾸어 주어도 됩니다.

 

콜백함수의 예제:http://soen.kr/lecture/win32api/lec4/lec4-3-4.htm

 

APIENTRY와 CALLBACK의 차이:https://vallista.tistory.com/entry/APIENTRY%EC%99%80-CALLBACK%EC%9D%98-%EC%B0%A8%EC%9D%B4

더보기

 

__stdcall은 함수 호출 규약.

__stdcall이라는 생소한 단어가 나온 만큼 한번 알아보자.

 

__stdcall은 함수 호출 규약의 한 종류이다.

 

여기서 함수 호출 규약은 함수가 계속 호출이 되면, 호출 당하는 쪽과 호출하는 쪽의 순서를 정해주어야 한다.

이런 규약을 함수 호출 규약이라고 한다.

 

  1. __cdecl

x86 즉 32bit 구조에서 주로 사용하는 호출 규약이다.

C/C++ 컴파일러에서 기본적으로 사용하며, 오른쪽 인자부터 스택에 전달하며, 호출자가 스택을 정리하는 특징이 있다.

 

  1. __stdcall

MS Win32API 표준 규약으로, 함수 호출 시 오른쪽 인자부터 스택에 전달하고 호출당한 함수가 사용한 스택을 정리한다.

 

  1. __fastcall

매개 변수의 일부를 스택이 아닌 ECX, EDX와 같은 레지스터로 전달한다.

다른 규약보다 빠른 편이며, __stdcall과 함께 호출 당한 함수가 사용한 스택을 정리한다.

https://mer-bleu.tistory.com/55



아직 끝나지 않았다. 이제 매개변수를 들여다 볼 차례이다.

(_In_ HINSTANCE hInstance,........

이 부분에서 HINSTANCE는 나중에 알아보고 _In_ 먼저 알아보자.

 

_In_ SAL중 하나인데, SAL이란 매개변수가 어떤 타입으로 사용이 될지를 미리 지정해 두는것이다.

예를 들면, 매개변수 앞에 _In_이 붙으면 이 데이터는 호출된 함수에 전달되고, 읽기만 된다.

 

이와 같이 코드의 가독성을 넓히는 게 SAL이다.

https://docs.microsoft.com/ko-kr/visualstudio/code-quality/understanding-sal?view=vs-2019

 

_In_ 부분은 확인 해 보았고, HINSTANCE가 무엇인지 알아보자.

일단 HINSTANCE는 인스턴스 핸들의 정보를 가지고 있는 구조체이다.

 

hInstance엔 프로그램 자체를 대표하는 정수값을 포함한다.

 

인스턴스 값은 같은 프로그램끼리 동일하며, 핸들값은 개별적인 윈도우마다 다르게 부여가 됩니다.

 

예들 들면, 크롬을 두 개를 켜놓는다고 하면 크롬이라는 인스턴스가 2개가 생성이 된다는것과 같은 말이다.

 

WinMain에서 (HINSTANCE hInstance, HINSTANCE hPrevInstance,..... 가 있는데,

hInstance는 현재 프로그램의 인스턴스 핸들이다.

hPrevInstance는 바로 이전에 실행 된 현재 프로그램의 인스턴스 핸들인데, 없을 경우 NULL이다. 이제 거의 사용을 하지 않는다고 한다.

 

그 다음 (....,LPWSTR lpCmdLine,...)부분이다.

LPWSTR은 wide형 문자열로 받는 인수이며, 명령 행으로 입력 된 프로그램의 인수이다.

도스의 main함수에 있는 argv인수에 해당한다.

 

main의 argv인수 예제:http://mwultong.blogspot.com/2006/12/c-argc-argv-main-function-parameter.html

 

마지막 매개변수는 int nCmdShow인데, 이는 프로그램이 처음 실행 되었을 때 윈도우 창의 형태를 받아온다.

 

드디어 wWinMain의 기본적인 틀을 알아보았다.

 

다시 이 코드를 따라 내려가면, SystemClass를 하나 생성해주고, 만약 만들어지지 않았다면 프로그램을 끝내준다.

 

그 후 SystemCall의 Initialize를 실행 시켜서 성공을 한다면 Run함수를 실행 시켜 준다.

Run안쪽 부분은 프레임이 돌아가게 될 것이다.

 

만약 SystemClass의 작업이 모두 끝났다면 Shutdown을 해주고, 메모리 해제를 시켜준다.

 

그리고 wWinMain의 역할을 끝. 간단하다.

 

하지만 이제 중요한건 SystemClass기 때문에 보러 가자.




프레임을 관장하는 SystemClass

 

일단 헤더입니다.

어떤게 들어갈지 기대가 되군요

먼저 InpuClass와 GraphicsClass를 사용하기 위해 선언만 해 줍니다.

 

그 뒤 생성자 소멸자, 초기화 및 실행, 종료 함수가 있습니다.

그런데 그 아래에 다른 콜백 함수가 있습니다.

 

MessageHandler함수인데요, 어떤 역할을 하는건지 알아봅시다.

 

메시지를 관리하는 MessageHandler를 관리하는 WndProc

MessageHandler함수의 내부 구조입니다.

대략적인 모습으로는 키보드 입력에 대한 처리 함수이며, umsg변수에 따라 할 일을 결정합니다.

 

그 후 InpuClass에 키에 대한 더 자세한 값을 보내줍니다.

 

MessageHandler가 받은 메시지의 타입이 키보드 업, 다운이 아니라면 DefWindowProc로 디폴트 처리를 해 줍니다.

 

그런데 이게 WinMain같이 자동으로 콜백으로 지정이 되어있는 함수는 아닙니다.

이게 어떤 말이냐면 다시 헤더로 와서 천천히 내리다 보면 static 콜백 함수인 WndProc도 있습니다.

 

WndProc의 내부 구조입니다.

MessageHandler와 상당히 흡사합니다.

 

WndProc는 윈도우에서 메시지 정보가 들어올때 마다 자동으로 받아와서 처리를 하는 구조인데, 여기서 키보드 입력, 윈도우 생성주기에 관한 메시지 등 여러가지 타입의 이벤트를 관리하다 보면 함수의 줄이 길어지기 때문에 분할을 해 놓았습니다.

 

WndProc는 SystemClass의 Run에서 나올 DispatchMessage를 호출하면 실행이 됩니다.

 

종료 이벤트에서 나오는 PostQuitMessage는 프로그램이 종료된다는 WM_QUIT메시지를 보내는 역할을 한다.

http://soen.kr/lecture/win32api/reference/Function/PostQuitMessage.htm

 

http://soen.kr/lecture/win32api/lec2/lec2-2-4.htm

여기서 WndProc에 대한 이야기가 나옵니다.

 

여기서 ApplicationHandle의 정체는 SystemClass의 InitializeWindows함수로 이동하면 정체를 알게 됩니다.

 

초기화를 하는 곳

 

윈도우 초기화를 시켜줄 때 자신을 대입 시켜줍니다.

 

ApplicationHandle이 어디에 대입이 되는지 알아봤으니 이곳은 어느곳인지 알아봅시다.

 

이곳은 말 그대로 윈도우를 초기화를 해주는곳입니다.

윈도우의 이름, 색상, 해상도까지 웬만한 윈도우의 틀을 지정을 해 줍니다.

 

윈도우 클래스를 하나 생성하여, 그곳에 윈도우의 정보를 커스터 마이징 하고 저희한테 보여줍니다.

 

그 후 만들어진 윈도우 클래스를 등록해주어야 합니다.



윈도우 클래스

http://soen.kr/lecture/win32api/lec2/lec2-2-2.htm



아래쪽이 만들어진 윈도우 클래스를  보여주는 함수들입니다.

 

CreateWindowEx함수를 사용하여 만들어 놓은 정보를 메모리에 올려놓은 후 윈도우 핸들값 즉 새로 생길 유일한 창의 값을 전해줍니다.

 

이 핸들값으로 윈도우를 보여주는 함수가 ShowWindow이며 (핸들값, 속성)으로 매개변수가 이루어져 있다.

 

SetForegroundWindow함수는 프로그램창이 최상위로 오도록 만든다.

SetFocus함수는 포커스(선택)가 된 프로그램이 이 프로그램이 되도록 지정을 해 줍니다

 

InitializeWindow함수는 SystemClass의 Initialize함수에 있습니다.

Initialize함수엔 윈도우 생성뿐만 아니라, Inputclass와 GraphicsClass까지 생성하여 초기화를 합니다.

 

프레임 진행 Run함수

WinMain의 일부분입니다.

초기화를 해 준 뒤 Run함수를 실행시킵니다.

이 부분이 이제 프로그램의 진행을 도맡아주는 부분입니다.

 

 

천천히 코드를 봅시다.

 

MSG구조체를 만들고 ZeroMemory를 사용해 0으로 초기화를 해줍니다.

 

ZeroMemory에 관한 재밌는 글:https://rockdrumy.tistory.com/493

 

그 후 while문을 돌며 여러가지 일을 처리합니다.



while문 안에 PeekMessage가 돋보입니다.

PeekMessage는 간단하게 윈도우가 받은 메시지가 있다면 true를 반환하고 없다면 false를 반환합니다.

 

만약 메시지가 있다면 TranslateMessage와 DispatchMessage를 실행을 시키는데,

TranslateMessage는 메시지에 대한 정보를 더욱 자세한 방향으로 바꾸어 주고,

DispatchMessage는 변환한 메시지를 윈도우 한테 넘겨준다. 이 메시지는 WndProc로 넘어가게 되어, 이 메시지를 더욱 심화된 과정으로 수행합니다.

 

메시지 중에 종료메시지가 있다면 종료를 시켜줍니다.

 

만약 메시지가 없다면 Frame함수를 처리합니다.

 

프레임 함수는 InputClass와 GraphicsClass와 연관이 되어 있기 때문에 나중에 봅시다.




종료

마지막으로 볼 것은 ShutDown함수입니다.

Graphics클래스도 해제 작업을 시켜주고 InputClass도 해제 작업을 시켜주며,

ShutdownWindows함수로 완전히 종료시킵니다.

 

ShutdownWindows함수입니다. 이곳에서도 지금까지 만들었던 윈도우와 인스턴스를 해제시키는 작업을 합니다.

 

이 함수까지 끝이나면 아예 프로그램이 종료 되었다고 볼 수 있습니다.

 

입력을 관리하는 InputClass

InputClass의 헤더입니다.

초기화와 키 입력에 관한 함수가 있습니다.

구현도 초기 버전이라 상당히 간단합니다.

 

256개의 key코드가 눌렸는지 안눌렸는지 확인하는 함수들입니다.

VK_(KEY)로 매칭을 할 수 있습니다.

 

SystemClass의 Frame함수에서 ESC버튼을 눌렀을 때 종료를 한다고 처리를 해 줍니다.

 

아직까지는 일 안하는 GraphicsClass

아직 일 안합니다. 나중에 봅시다.

 

마무리

자 이렇게 많은 코드를 하나하나 뜯어보았습니다.

이 코드를 실행시키면 

이 화면이 나옵니다.

ㅋㅋㅋ

 

아직 프레임 워크 베이스지만 winAPI와 친해졌다는걸로 의의를 두는게 좋습니다.

 

앞으로는 Directx도 섞어가며 코드가 들어가기 때문에 몸풀기라고 생각합시다.

 

댓글