서울게임아카데미 교육과정 6개월 C++ ~ DirectX2D

56일차. DirectX11_2D시작, DirectX11_2D[Graphics]

WDmil 2023. 7. 13. 22:11
728x90

서론

DirectX11을 구성하기 위해 Framework 와 UnitTest로 두개의 프로젝트로 나누어 관리한다.

 

Framework는 각 라이브러리를 가져와서 연동시키는 역활을 하고,

 

UnitTest는 연동된 라이브러리를 활용하는 직접적인 알고리즘 부분을 맡게 된다.

 

[ 위와같은 방법으로 사용하지 않고 자유롭게 구축하여도 무방하다. ]


DirectX 라이브러리 연동하기

이와같이 두개의 프로젝트를 구성하여, Framework 의 프로젝트를 UnitTest에 연동시키기 위해,  VS에서는 다음과 같은 설정을 진행한다.

 

Framework에서 마우스 오른쪽 클릭 -> 속성

위 항목에서 포함 디렉터리, 라이브러리 디렉터리 를 수정한다.

포함 디렉터리 -> 메크로 -> SolutionDir을 찾아 빈칸에 붙여넣기.

상속된 값, 에 해당하는 기록들도 같이 복사 붙여넣기 해준다.

 

위 경로는, DirectX경로의 Include 를 프로젝트 폴더에 복사 붙여넣기 한 경로이다.

만약, 절대경로로써 로컬디스크 C 에 있는 DirextX를 사용하고 싶다면, 위 경로를 찾아가서 설정해도 좋다.


디버그 폴더 관리하기

그리고, 프로젝트를 컴파일 했을 때 나오는 디버그 폴더를 관리하기 위해, 출력 디렉터리와 중간 디렉터리를 다음과 같이 수정해준다.

위 항목을 Framework와 UnitTest 둘다 사용한다.


DIrextX 의 Graphics에 대한 설명을 작성한다.

 

그래픽 작업을 다루는 클레스이며, 싱글톤 베이스 로 작업한다.

싱글톤 베이스는 겹쳐지는 것을 피하기 위해 한개만 사용할때 사용한다.

// 그래픽들의 값을 전부 초기화 해주는것.
// 인자값을 전부 초기화 해야한다.
class D3DEnumAdapterInfo;
class D3DEnumOutputInfo;

class Graphics : public SingletonBase<Graphics>
{
// 싱글톤 베이스 = 한개가 존재해야 하는것은 한개만 넣는것.
public:
	friend class SingletonBase<Graphics>;
	// private에 있는 것을 사용하기 위해 firend 선언해준다.
	ID3D11Device* GetDevice() { return device;}
	ID3D11DeviceContext* GetDC() { return deviceContext; }
	
	void Resize(const UINT& width, const UINT& height);
	// 창 사이즈 바꾸기
	void SetViewport(const UINT& width, const UINT& height);
	// Viewport = 우리가 보고있는 화면.

	void Begin();
	void End();
	// 생성자와 소멸자의 내용을 줄여서 만든다.
	void GUI();
	// 그래픽 유저 인터페이스.
	// 다른 부가정보를 띄워주기 위해 사용
private:
	void CreateSwapchain();
	// 우리가 화면을 그려줄떄 뒤쪽에 버퍼를 생성해준다.
	// 그리고 화면을 두개 띄워서 교차하면서 표시해준다.
	void CreateRenderTargetView();
	// 
	void DeleteSurface();

private:
	Graphics();
	~Graphics();

private:
	void EnumerateAdapters();
	bool EnumerateAdapterOutput(D3DEnumAdapterInfo* adapterInfo);


private:
	ID3D11Device* device = nullptr;
	ID3D11DeviceContext* deviceContext = nullptr;
	IDXGISwapChain* swapChain = nullptr;
	ID3D11RenderTargetView* rtv = nullptr;
	D3D11_VIEWPORT viewport;
	D3DXCOLOR clearColor = 0xff555566;

	UINT numerator = 0;
	UINT denominator = 1;

	UINT gpuMemorySize;
	wstring gpuName;

	vector<D3DEnumAdapterInfo*> adapterInfos;
	int selectedAdapterIndex = 0;

	bool bVsync = true;
};

class D3DEnumAdapterInfo
// 그래픽카드 
{
public:
	~D3DEnumAdapterInfo();

	UINT adapterOrdinal = 0;
	IDXGIAdapter1* adapter = nullptr;
	DXGI_ADAPTER_DESC1 adapterDesc = { 0 };

	D3DEnumOutputInfo* outputInfo = nullptr;
};

class D3DEnumOutputInfo
// 모니터
{
public:
	~D3DEnumOutputInfo();

	IDXGIOutput* output = nullptr;
	DXGI_OUTPUT_DESC outputDesc = { 0 };

	UINT numerator = 0;
	UINT denominator = 1;
};
#include "Framework.h"
#include "Graphics.h"

// 스왑체인을 새로 조정하고, 렌더 타겟 뷰와 뷰포트를 업데이트
void Graphics::Resize(const UINT& width, const UINT& height)
{
	// 기존에 생성된 RTV 제거
	DeleteSurface();

	// 버퍼 크기 변경
	{
		HRESULT hr = swapChain->ResizeBuffers
		(
			0,						// 백 버퍼의 수 (0은 기존과 동일하게)
			width,					// 백 버퍼의 너비
			height,					// 백 버퍼의 높이
			DXGI_FORMAT_UNKNOWN,	// 백 버퍼의 포맷 (기존과 동일하게 유지)
			0						// 백 버퍼의 속성 (기존과 동일하게 유지)
		);
		assert(SUCCEEDED(hr));		// 리사이즈 실패한 경우 프로그램 종료
	}
	CreateRenderTargetView();		// 새로운 백 버퍼에 대한 RTV 생성
	SetViewport(width, height);		// 뷰포트를 새로운 크기에 맞게 설정
}

// 뷰포트 설정
void Graphics::SetViewport(const UINT& width, const UINT& height)
{
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;
	viewport.Width = (float)width;
	viewport.Height = (float)height;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
}

// 렌더링을 시작하기 전 필요한 초기화 작업을 수행
void Graphics::Begin()
{
	// RTV 설정
	deviceContext->OMSetRenderTargets(1, &rtv, nullptr);
	// 뷰포트 설정
	deviceContext->RSSetViewports(1, &viewport);
	// RTV를 지우고 clearColor로 색상을 채움
	deviceContext->ClearRenderTargetView(rtv, clearColor);
}

// 렌더링을 끝내고 화면에 결과를 출력
void Graphics::End()
{
	// 백 버퍼를 출력하고, 화면 갱신에 대해 대기할지 여부를 bVsync로 전달
	HRESULT hr = swapChain->Present(bVsync == true ? 1 : 0, 0);
	assert(SUCCEEDED(hr));
}

// Gui 구현
void Graphics::GUI()
{
	static bool bOpen = true;
	ImGui::SetNextWindowPos({ 0, 15 });
	ImGui::SetNextWindowSize(ImVec2(200, 30)); // 위젯 창 크기 설정
	ImGui::Begin
	(
		"Vstnc",
		&bOpen,
		ImGuiWindowFlags_NoBackground |
		ImGuiWindowFlags_NoTitleBar |
		ImGuiWindowFlags_NoResize |
		ImGuiWindowFlags_NoMove |
		ImGuiWindowFlags_NoScrollbar
	);
	{
		ImGui::Checkbox("##Vsync", &bVsync);
	}
	ImGui::End();
}

// 스왑 체인을 생성
void Graphics::CreateSwapchain()
{
	// 이전에 할당된 메모리 해제
	{
		SAFE_RELEASE(device);
		SAFE_RELEASE(deviceContext);
		SAFE_RELEASE(swapChain);
	}

	// 구조체 초기화
	DXGI_SWAP_CHAIN_DESC desc;
	ZeroMemory(&desc, sizeof(DXGI_SWAP_CHAIN_DESC));

	// 너비와 높이 설정
	desc.BufferDesc.Width = 0;
	desc.BufferDesc.Height = 0;

	// 수직 동기화를 사용할 경우 프레임 레이트 설정
	{
		if (bVsync)
		{
			desc.BufferDesc.RefreshRate.Numerator = adapterInfos[0]->outputInfo->numerator;
			desc.BufferDesc.RefreshRate.Denominator = adapterInfos[0]->outputInfo->denominator;
		}
		else
		{
			desc.BufferDesc.RefreshRate.Numerator = 0;
			desc.BufferDesc.RefreshRate.Denominator = 1;
		}
	}

	// 버퍼의 쓰임새에 대해서 정의
	{
		// 버퍼의 색상 형식 설정
		desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

		// 스케일링과 스캔라인 순서
		desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

		// 스왑 체인의 버퍼 개수, 용도, MSAA 품질 설정
		desc.BufferCount = 1;
		desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		desc.SampleDesc.Count = 1;
		desc.SampleDesc.Quality = 0;

		// 출력 창 핸들, 창 모드, 스왑 체인 효과 설정
		desc.OutputWindow = handle;
		desc.Windowed = true;
		desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	}

	// 사용 가능한 D3D 기능 수준을 나타내는 D3D_FEATURE_LEVEL 배열 생성
	vector<D3D_FEATURE_LEVEL> featureLevel
	{
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
	};

	// 가장 VRAM이 큰 어댑터를 선택하여 selectedAdapterIndex에 할당
	UINT maxVRam = 0;
	for (UINT i = 0; i < adapterInfos.size(); i++)
	{
		if (adapterInfos[i]->adapterDesc.DedicatedVideoMemory > maxVRam)
		{
			selectedAdapterIndex = i;
			maxVRam = adapterInfos[i]->adapterDesc.DedicatedVideoMemory;
		}
	}

	// 디바이스와 스왑 체인 생성
	HRESULT hr = D3D11CreateDeviceAndSwapChain
	(
		adapterInfos[selectedAdapterIndex]->adapter,	// 최대 VRAM 용량을 가진 그래픽 장치 선택
		D3D_DRIVER_TYPE_UNKNOWN,						// 드라이버 타입 (자동)
		nullptr,										// 소프트웨어 렌더러 사용 안함
		0,												// 렌더링 시 필요한 플래그 설정
		featureLevel.data(),							// 사용할 Direct3D 기능 레벨
		featureLevel.size(),							// 사용할 Direct3D 기능 레벨의 개수
		D3D11_SDK_VERSION,								// DirectX SDK 버전
		&desc,											// 스왑 체인을 생성할 때 사용할 구조체
		&swapChain,										// 생성된 스왑 체인 객체 반환
		&device,										// 생성된 디바이스 객체 반환
		nullptr,										// 사용하지 않음
		&deviceContext									// 생성된 디바이스 컨텍스트 객체를 반환
	);
	assert(SUCCEEDED(hr));

	// 사이즈 재설정
	Resize(WinMaxWidth, WinMaxHeight);

	// cmd 출력
	{
		for (int i = 0; i < adapterInfos.size(); i++)
		{
			gpuName = adapterInfos[i]->adapterDesc.Description;
			wcout << "GPU Name : " << adapterInfos[i]->adapterDesc.Description << endl;
			cout << "VRAM : " << adapterInfos[i]->adapterDesc.DedicatedVideoMemory << endl;
			cout << endl;
		}

		wcout << "Selected GPU Name : "
			<< adapterInfos[selectedAdapterIndex]->
			adapterDesc.Description << endl;
	}

}

// 스왑체인에서 백 버퍼를 가져와 RTV 생성
void Graphics::CreateRenderTargetView()
{
	// 백버퍼 가져오기
	ID3D11Texture2D* backbuffer = nullptr;
	HRESULT hr = swapChain->GetBuffer
	(
		0,
		__uuidof(ID3D11Texture2D),
		(void**)&backbuffer
	);
	assert(SUCCEEDED(hr));

	// 백 버퍼로 RTV 생성
	hr = device->CreateRenderTargetView
	(
		backbuffer,
		nullptr,
		&rtv
	);
	assert(SUCCEEDED(hr));

	// 백 버퍼 해제
	SAFE_RELEASE(backbuffer);
}

// RTV 해제
void Graphics::DeleteSurface()
{
	SAFE_RELEASE(rtv);
}

// 어댑터를 검색하고 스왑체인 생성
Graphics::Graphics()
{
	EnumerateAdapters();
	CreateSwapchain();
}

// 생성한 자원을 해제
Graphics::~Graphics()
{
	SAFE_RELEASE(rtv);
	SAFE_RELEASE(swapChain);
	SAFE_RELEASE(deviceContext);
	SAFE_RELEASE(device);
}

// 그래픽 어댑터를 찾아 정보를 저장
void Graphics::EnumerateAdapters()
{
	// DXGI 객체를 생성하는 함수를 구현한 인터페이스
	IDXGIFactory1* factory;
	// 팩토리 생성
	if (FAILED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory)))
		// 실패시 종료
		return;

	UINT index = 0;
	while (true)
	{
		IDXGIAdapter1* adapter = nullptr;
		// 어댑터를 나열
		HRESULT hr = factory->EnumAdapters1(index, &adapter);

		// 더 이상 어댑터가 없으면 종료
		if (hr == DXGI_ERROR_NOT_FOUND)
			break;
		// 나열에 실패하면 프로그램 종료
		assert(SUCCEEDED(hr));

		D3DEnumAdapterInfo* adapterInfo = new D3DEnumAdapterInfo();
		ZeroMemory(adapterInfo, sizeof(D3DEnumAdapterInfo));
		// 어댑터의 인덱스 저장
		adapterInfo->adapterOrdinal = index;
		// 어댑터의 정보 저장
		adapter->GetDesc1(&adapterInfo->adapterDesc);
		// 어댑터 정보 저장
		adapterInfo->adapter = adapter;

		// 어댑터의 출력 장치 나열
		EnumerateAdapterOutput(adapterInfo);
		// 어댑터 정보 저장
		adapterInfos.push_back(adapterInfo);

		// 다음 어댑터로 이동
		index++;
	}
	// 팩토리 해제
	SAFE_RELEASE(factory);
}

// 어댑터에서 지원하는 출력 모드를 가져오는 역할, 출력 빈도를 저장
bool Graphics::EnumerateAdapterOutput(D3DEnumAdapterInfo* adapterInfo)
{
	IDXGIOutput* output = nullptr;
	// 어댑터의 출력 장치 나열
	HRESULT hr = adapterInfo->adapter->EnumOutputs(0, &output);

	// 출력 장치가 없으면 함수 종료
	if (DXGI_ERROR_NOT_FOUND == hr)
		return false;
	// 나열에 실패하면 프로그램 종료
	assert(SUCCEEDED(hr));

	D3DEnumOutputInfo* outputInfo = new D3DEnumOutputInfo();
	ZeroMemory(outputInfo, sizeof(D3DEnumOutputInfo));


	output->GetDesc(&outputInfo->outputDesc);	// 출력 장치의 정보 저장
	outputInfo->output = output;				// 출력 장치 객체 저장

	UINT numModes = 0;
	DXGI_MODE_DESC* displayModes = nullptr;
	DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM;

	// 출력 장치의 디스플레이 모드 개수 확인
	hr = output->GetDisplayModeList(format, DXGI_ENUM_MODES_INTERLACED, &numModes, nullptr);
	assert(SUCCEEDED(hr));


	displayModes = new DXGI_MODE_DESC[numModes];
	// 출력 장치의 디스플레이 모드 정보 저장
	hr = output->GetDisplayModeList(format, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModes);
	assert(SUCCEEDED(hr));

	for (UINT i = 0; i < numModes; i++)
	{
		bool bCheck = true;
		bCheck &= displayModes[i].Width == WinMaxWidth;		// 디스플레이 모드의 너비가 최대 너비와 같은지 확인
		bCheck &= displayModes[i].Height == WinMaxHeight;	// 디스플레이 모드의 높이가 최대 높이와 같은지 확인

		if (bCheck == true)
		{
			// 디스플레이 모드의 리프레시 비율 저장
			outputInfo->numerator = displayModes[i].RefreshRate.Numerator;
			outputInfo->denominator = displayModes[i].RefreshRate.Denominator;
		}
	}

	// 출력 장치 정보 저장
	adapterInfo->outputInfo = outputInfo;
	SAFE_DELETE_ARRAY(displayModes);
	return true;
}

D3DEnumAdapterInfo::~D3DEnumAdapterInfo()
{
	SAFE_RELEASE(adapter);
	SAFE_DELETE(outputInfo);
}

D3DEnumOutputInfo::~D3DEnumOutputInfo()
{
	SAFE_RELEASE(output);
}

함수

Graphics::GetDevice()

  • 현재 DIrectX장치를 반환하는 함수이다.

Graphics::GetDC()

Graphics::Resize( ... )

  • 현재 출력되는 창의 크기를 변경하는 함수이다. 전달된 너비와 높이에 따라 렌더타겟을 재생성하고 크기를 조정한다.

Graphics::SetViewport( ... )

  • viewport의 크기를 설정해주는 함수로, 화면에 그려질 영역을 지정해주는 역활을 한다.

Graphics::Begin()

  • 렌더링의 루프를 시작하는 함수로, 시작전 값을 초기값으로 초기화해주는 작업을 한다.

Graphics::End()

  • 렌더링 루프의 종료를 나타내는 함수로, 렌더링 이후 정리작업을 수행한다.

Graphics::GUI()

  • 그래픽 사용자 인터페이스를 처리하는 함수이다. 다른부가정보를 표시한다.

Graphics::CreateSwapchain()

  • Swap Chain을 생성하는 함수로, 화면에 그릴후, 그리기전의 버퍼를 생성하고 두개의 화면을 교차하며 표시한다.

Graphics::CreateRenderTargetView()

  • Render TargetView를 생성하는 함수. SwapChain의 뷰포트 를 지정해준다.

Graphics::DeleteSurface()

  • Surface를 삭제하는 함수. 생성된 SwapChain과 Render Target View와 관련된 자원을 정리한다.

Graphics::EnumerateAdapters()

  • 사용가능한 그래픽 어댑터를 열거하는 함수이다. 시스템 설치 그래픽카드를 확인하고 어뎁터 정보를 수집한다.

Graphics::EnumerateAdapterOutput( ... )

  • 그래픽 어댑터의 출력(모니터)를 열거하는 함수로 모니터의 정보를 수집한다.

나머지는 소멸자이다.


Grapic Pipeline에 대해 설명한다.

 

728x90