자습

220714_2_DX Device Initialize 1

hyrule 2022. 8. 20. 23:01

> EngineInfo.h에 SAFE_RELEASE 매크로 추가

#define	SAFE_RELEASE(p)	if(p)	{ p->Release(); p = nullptr; }

> 그래픽카드 드라이버와 DirectX

드라이버는 그래픽카드(하드웨어)에 명령을 내리고 처리하기 위한 소프트웨어이다.

그런데, 제조사 별로 이 명령을 내리는 과정이 조금씩 다를 것이다. ex)엔비디아, 라데온

그렇다고 프로그램 개발자가 그래픽카드별로 다르게 코딩을 하게 되면 너무 불편해지므로 DirectX가 만들어진 것.

윈도우 프로그램에서 DirectX에 그래픽 관련 명령을 전달하면, DirectX는 그래픽카드 드라이버에 맞는 명령어를 전달해 주는 역할을 해준다.

일종의 완충 지대 역할을 하는 것이다.

> EngineInfo.h에서 필요한 헤더들 포함시키기

#include <d3dll.h>
#include <d3dcompiler.h>

> DX11과 이전 버전의 차이점

  • DX9까지는 C++ 코드를 이용해서 화면에 출력하는 기능이 었었으나 DX11부터는 해당 기능이 삭제됨.
  • DX11부터는 Shader를 만들어서 직접 렌더링해야 한다. → HLSL(High Level Shader Language)라는 특수 언어를 사용함.
  • 위에서 포함시킨 d3dcompiler.h가 해당 역할을 한다.

> DX11 라이브러리 링크시키기

  • DX11은 라이브러리 형태로 제공되고 있다.
  • 실제로 DX11이 작동할 Engine.cpp 안에서 해당 라이브러리를 링크시켜 준다.
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "dxguid.lib")

디바이스 초기화

  • 프로그램에서 그래픽카드에 접근하여 사용권한을 요청하는 과정이라고 보면 된다.
  • Engine 프로젝트에 해당 코드들을 정리할 수 있도록 새 필터를 생성해준다.

> class CDevice

  • Device.h 코드 전문 보기
    #pragma once
    
    #include "EngineInfo.h"
    
    class CDevice
    {
    private:
    	ID3D11Device* m_Device;
    	ID3D11DeviceContext* m_Context;
    	IDXGISwapChain* m_SwapChain;
    
    	ID3D11RenderTargetView* m_TargetView;
    	ID3D11DepthStencilView* m_DepthView;
    
    	HWND	m_hWnd;
    	Resolution	m_RS;
    
    public:
    	Resolution GetResolution()	const
    	{
    		return m_RS;
    	}
    
    public:
    	bool Init(HWND hWnd, unsigned int DeviceWidth, unsigned int DeviceHeight,
    		bool WindowMode);
    
    	DECLARE_SINGLE(CDevice)
    };

  • Device 클래스에서 하는 일
    • DirectX Device 초기화
    • 우리가 필요로 하는 기능들을 쓸 수 있게끔 Device Context라는 객체를 제공해줌

  • 싱글턴 패턴으로 생성한다.

  • Init() 메소드 선언
    public:
    	bool Init(HWND hWnd, unsigned int DeviceWidth, unsigned int DeviceHeight,
    		bool WindowMode);

    → 디바이스 초기화에는 윈도우 핸들, 창의 크기와 창모드인지 여부가 반드시 필요하다.

  • 여기까지 해 주었다면, CEngine의 Init() 메소드에서 위 클래스를 생성 및 초기화(싱글턴 패턴) 해주고, 소멸자에서 위 클래스를 제거해준다.
    • CDevice::Init()에서는 HWND를 필요로 하므로, CEngine::Init()의 창이 생성된 이후(Create() 메소드가 호출된 이후)에 초기화를 해 주어야 한다.
      bool CEngine::Init(HINSTANCE hInst, const TCHAR* Title,
          const TCHAR* ClassName, int IconID, int SmallIconID,
          unsigned int WindowWidth, unsigned int WindowHeight,
      	unsigned int DeviceWidth, unsigned int DeviceHeight, bool WindowMode)
      {
      	m_hInst = hInst;
      	m_WindowRS.Width = WindowWidth;
      	m_WindowRS.Height = WindowHeight;
      
      	Register(ClassName, IconID, SmallIconID);
      
      	Create(Title, ClassName);
      
          // Device 초기화
          if (!CDevice::GetInst()->Init(m_hWnd, DeviceWidth, DeviceHeight, WindowMode))
              return false;
      
      	return true;
      }

  • DX 관련 변수 선언
    private:
    	ID3D11Device* m_Device;
    	ID3D11DeviceContext* m_Context;
    • 잘 살펴보면 변수들 앞에 ‘I’ 가 붙어있는 것을 확인할 수 있다.
    • 정의를 F12키를 통해 타고 들어가 보면, ‘IUnknown’이라는 클래스를 상속받고 있는 것을 확인할 수 있다.

    ▶️
    IUnknown
    • Com객체(Component Object Model)를 지원하기 위한 클래스
    • DirectX를 사용 프로그래밍 언어에 구애받지 않고 동작할 수 있게 해주는 역할을 한다.
    • Com객체는 ‘GUID’라는 고유 식별문자 같은 것을 가지고 있다.(Win32Api 떄 TypeID를 사용했던 것과 비슷함)
    • 안쪽에 들어가서 살펴보면, Release() 메소드를 사용하는 것을 볼 수 있다.

      → SharedPtr을 통해 메모리를 관리하고 있다는 것을 알수 있다.

    ▶️
    Device와 Context
    • DX9까지는 m_Device 변수만으로 모든 컨트롤을 다 할수 있었으나, DX11부터는 Device와 Context로 분리되었다.
      • Device의 경우 버퍼 생성 용도로 많이 사용

        →텍스처 이미지, 3D 모델링 정보 등을 Vram에 저장(임시 저장공간)

      • Context의 경우 실제 렌더링에 관련한 명령을 내리는 용도
        • Immediate Context(우리가 지금 만들 컨텍스트)
        • Deferred Context(신기술. 멀티스레드 연산에 사용되는 컨텍스트)

    ▶️
    m_Context와 m_Device를 생성자에서 초기화, 소멸자에서 제거해 준다.
    CDevice::CDevice()	:
    	m_Device(nullptr),
    	m_Context(nullptr),
    {
    }
    
    CDevice::~CDevice()
    {
    	if (m_Context)
    		m_Context->ClearState();
    
    	SAFE_RELEASE(m_Context);
    	SAFE_RELEASE(m_Device);
    }
    • Context에서 먼저 모든 상태를 제거 한 뒤 하위 변수인 Context부터 제거한 뒤 Device를 제거한다.

버퍼 관련 변수 3종 초기화

class CDevice
{
private:
	IDXGISwapChain* m_SwapChain;
	ID3D11RenderTargetView* m_TargetView;
	ID3D11DepthStencilView* m_DepthView;

Back Buffer

▶️
주 버퍼 / 백 버퍼
  • 화면에 출력할 픽셀 정보를 임시로 저장하는 공간.

> Win32Api의 더블 버퍼링

  • Win32Api는 HDC를 두개 만들어서, 모든 렌더링을 백 버퍼에서만 한 뒤, SRC_COPY를 통해 주 버퍼로 ‘복사’하여 잔상을 없애는 방식을 사용했다.

    → 이 과정에서 복사하는 데 시간이 소모된다.


▶️
DX의 Page Flipping
  • DX에서도 주버퍼와 백버퍼를 사용하는 것은 동일하다.
  • 하지만, DX에서는 복사를 하는 대신, 주 버퍼와 백 버퍼를 지속적으로 교체시켜서 출력하는 방식을 사용한다.

    → 출력버퍼를 매번 바꿔준다는 뜻이다.

    → Win32Api의 방식보다 성능이 약간 더 낫다.

  • DX에서는 아예 이 기능을 지원해주고 있다.


▶️
SwapChain
  • Win32Api에서는 우리가 직접 이미지를 출력할 ‘백버퍼’를 지정하고, 거기에 그리도록 명령을 내렸었다.

    또한, 실제 출력은 주버퍼에서만 진행되었었으므로 모든 이미지 출력을 백버퍼에 맡기면 됐었다.

  • 하지만, 위에서 설명한 대로 Page Flipping의 경우 주버퍼와 백버퍼가 매번 바뀐다. SwapChain은 이 과정을 담당한다.


> Render Target Buffer

  • 스왑체인을 통해 주 버퍼와 백 버퍼가 계속 바뀌므로 렌더링한 픽셀 정보를 저장할 곳이 매번 바뀌게 되는데, 이 정보가 저장되는 변수가 ‘렌더타겟 뷰’이다.
  • 쉽게 생각하면 그냥 백버퍼라고 보면 된다.

> Depth/Stencil Buffer

▶️
Depth Buffer
  • 3D 공간에서 어떤 물체가 가까이 있는지에 대한 정보를 저장하는 버퍼
  • 3D는 2D와는 다르게 ‘깊이’에 대한 정보가 필요하다. 카메라 시점에서 멀리 있는 오브젝트는 가까이 있는 오브젝트에 의해 가려져야 한다.
  • 이 과정을 처리해 줄 버퍼이다.
  • 0~1 사이의 값

▶️
Stencil Buffer
  • 각각의 픽셀마다 내가 원하는 값을 저장한 후 특수효과를 만들기 위해 제작된 버퍼
  • 성능이 그다지 좋지 않아서 꼭 필요한 부분이 아니면 왠만하면 사용하지 않음

위 변수 3종을 생성자에서 초기화, 소멸자에서 SAFE_RELEASE 해준다.

> 윈도우 핸들과 현재 해상도를 변수로 만들어 저장

class CDevice
{
private:
	HWND	m_hWnd;
	Resolution	m_RS;

Init() 메소드 정의

코드 보기

bool CDevice::Init(HWND hWnd, unsigned int DeviceWidth, unsigned int DeviceHeight,
	bool WindowMode)
{
	m_hWnd = hWnd;
	m_RS.Width = DeviceWidth;
	m_RS.Height = DeviceHeight;

	unsigned int	Flag = 0;

#ifdef _DEBUG
	Flag = D3D11_CREATE_DEVICE_DEBUG;
#endif // _DEBUG


	return true;
}
▶️
Flag = D3D11_CREATE_DEVICE_DEBUG;
  • DX는 바이너리 코드로 된 라이브러리 형식이므로 내부 코드에 대한 디버깅을 할 수가 없다.
  • 대신 Flag 값을 인자로 전달하여 어느 부분에서 에러가 났는지는 전달받을 수 있다.


Uploaded by N2T