KoreanFoodie's Study

DirectX 11 2D 게임 프로그래밍 - 5. Window 창에 DirectX 연결(연동)하기 본문

Game Dev/DirectX

DirectX 11 2D 게임 프로그래밍 - 5. Window 창에 DirectX 연결(연동)하기

GoldGiver 2021. 10. 24. 10:19


DirectX 11 2D 게임 프로그래밍 - 5. Window 창에 DirectX 연결(연동)하기

이번 시간에는 이전에 띄웠던 Window 창에 DirectX요소를 넣어 연동해본다.

 

알아두어야 할 개념 : 

1. Design Pattern 조사

2. 정적 변수 복습 -> static member는 class 내부에 하나 밖에 없으며, 클래스에서 공유하는 자원이다.

3. 16진수로 표현되는 색상 정보 조사해보기 -> D3DXCOLOR 변수는 실제로 float[4]로 표현 가능

4. const int* vs int* const 차이? -> 왼쪽은 포인터가 상수인 것. 따라서 가리키는 것을 바꿀 수 있고, 참조하는 변수의 값을 바꿀 수는 있지만, *ptr = new_value식으로는 바꿀 수 없다. 오른쪽은 상수인 녀석을 가리키는 포인터. 따라서 포인터가 어떤 녀석을 참조하는지를 바꿀 수는 없지만 참조하는 녀석의 값을 바꿀 수는 있다(*ptr = new_value처럼 역참조하여 바꿀 수 있다는 것임).

항상 헷갈리는 부분인데, const가 붙은 녀석의 오른쪽에 무엇이 오는지를 보면 외우기 쉽다.

const int* ptr은, const (int* ptr)로 생각하자. 상수를 가리키는 포인터인 것이다. 따라서, ptr = &new_value는 가능하지만, *ptr = new_value는 불가능하다. 상수를 가리키는 포인터이므로.

int* const ptr는 int* const (ptr)로 생각하자. 포인터를 상수로 만든 것이다. 따라서, ptr = &new_value는 불가능하다. 포인터가 상수인 것이니. 대신, 포인터가 어떤 값을 가리키는지는 바꿀 수 있다. 따라서 *ptr = new_value는 가능하다.

 

Graphics.cpp

Graphics.cpp에 Begin( )과 End( ) 메소드를 추가하여 백버퍼에 그릴 부분을 그리고, 내보내게 만든다. Begin( )은 영역에 그리는 역할을, End( )는 버퍼를 flush하는 역할을 한다.

#include "stdafx.h"
#include "Graphics.h"

Graphics::Graphics()
{
}

Graphics::~Graphics()
{
	// 먼저 할당된 포인터를 제일 나중에 해제해 주자! (dangling pointer 상황 방지)
	SAFE_RELEASE(render_target_view);
	SAFE_RELEASE(device);
	SAFE_RELEASE(device_context);
	SAFE_RELEASE(swap_chain);

}

void Graphics::Initialize()
{
	DXGI_SWAP_CHAIN_DESC desc;
	ZeroMemory(&desc, sizeof(DXGI_SWAP_CHAIN_DESC));
	desc.BufferDesc.Width = 0; // 백버퍼 크기를 잡아줌
	desc.BufferDesc.Height = 0;
	desc.BufferDesc.RefreshRate.Numerator = 60;
	desc.BufferDesc.RefreshRate.Denominator = 1;
	// R8.. 하나의 채널에 몇비트 할당할 것인지. RED : 8비트! (0~255)
	// UNORM : 정규화된 ... (0~255의 값을 0~1로 오도록 정규화한 것)
	desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; // 한 라인을 Scanline... Unpecified : 미확인
	desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 화면이 확대될 때 어떤 효과를 줄 것인지?
	desc.BufferCount = 1;

	// Aliasing : 계단 현상. Anti-Aliasing을 하는 방법에는 보통 SSA와 MSAA가 있다.
	// SSAA : 이미지 하나를 k배로 늘리고 줄인다 + 보정 -> 비용이 커서 잘 안쓰임
	// MSAA : 특정 부분(예 : 외각선)만 처리해서 상대적으로 저렴함.
	// MSAA 에서 샘플링을 어떻게 할 것인가, 변수를 결정.
	desc.SampleDesc.Count = 1;
	
	// 위에 있는 버퍼를 어떻게 사용할 것인가?
	desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

	desc.SampleDesc.Quality = 0;

	desc.OutputWindow = Settings::Get().GetWindowHandle();
	desc.Windowed = TRUE;
	// 더블 버퍼링할 때 잉여 버퍼를 어떻게 할지..? DISCARD : 폐기!
	desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

	std::vector<D3D_FEATURE_LEVEL> feature_levels
	{
		//D3D_FEATURE_LEVEL_11_1, // Undefined... Maybe deprecated?
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
	};

	// Device, Device Context, SwapChain까지 한꺼번에 만들 수 있다.
	// return type이 HRESULT임. HRESULT는 함수의 결과가 성공했는지 실패했는지의 정보를 담고 있다.
	auto hr = D3D11CreateDeviceAndSwapChain		
	(
		nullptr,
		D3D_DRIVER_TYPE_HARDWARE, // 하드웨어 가속을 사용할 수 있게 해준다.
		nullptr,
		0, // 어떤 API를 사용할 것인지
		feature_levels.data(), // &feature_levels[0] 와 같다. 첫 시작 지점의 포인터를 반환
		feature_levels.size(),
		D3D11_SDK_VERSION,
		&desc,
		&swap_chain,
		&device,
		nullptr,
		&device_context
	);

	// 함수의 결과가 성공했는지 실패했는지의 정보를 받아 assert
	assert(SUCCEEDED(hr));

}

void Graphics::CreateBackBuffer(const uint& width, const uint& height)
{
	auto hr = swap_chain->ResizeBuffers
	(
		0, // ResizeBuffers 함수에는 초기값다 다른, 변형할 값만 넣어주면 된다.
		width,
		height,
		DXGI_FORMAT_UNKNOWN, // 포맷을 모르겠다... (위에 정의된 기본 값 사용)
		0
	);

	assert(SUCCEEDED(hr));

	// ID3D11SwapChain을 통해서 BackBuffer 정보를 얻어낼 수 있다
	ID3D11Texture2D* back_buffer = nullptr;
	hr = swap_chain->GetBuffer
	(
		0,
		// UUID : Universally Unique ID		GUID : Globally Unique ID
		// ID3D11Texture2D의 정의로 가보면, MIDL_INTERFACE("6f15aaf2-d208-4e89-9ab4-489535d34f9c") 가 UID에 해당한다!
		// UUID, GUID 는 중복될 경우가 없기에 고유 ID라고도 부른다 (MS에서 GUID 사용, 전역적으로는 UUID 사용)
		__uuidof(ID3D11Texture2D), //IID_ID3D11Texture2D, // IID : Interface Identifier
		reinterpret_cast<void**>(&back_buffer)
	);

	assert(SUCCEEDED(hr));

	hr = device->CreateRenderTargetView
	(
		back_buffer,
		nullptr,
		&render_target_view
	);

	assert(SUCCEEDED(hr));

	// 시작점
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;
	viewport.Width =  static_cast<float>(width);
	viewport.Height = static_cast<float>(height);
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;

	
	// delete말고, Release()함수를 이용해서 메모리 해제를 해 주어야 한다!
	SAFE_RELEASE(back_buffer);
}

// 그리기 시작 : 버퍼 세팅과 초기화 작업
void Graphics::Begin()
{
	// OM : Output Merger Stage
	// Output Merger Stage에 세팅될 수 있는 RenderTarget의 개수는 총 8장이다.
	device_context->OMSetRenderTargets(1, &render_target_view, nullptr);
	device_context->RSSetViewports(1, &viewport);
	device_context->ClearRenderTargetView(render_target_view, clear_color);

}

// 그리기 끝
void Graphics::End()
{
	// 후면 버퍼를 전면 버퍼로 출력해 준다.
	auto hr = swap_chain->Present(1, 0);
	assert(SUCCEEDED(hr));


}


// ID3D11Resource
// 모든 ID3D11은 이 Resource 녀석을 상속 받는다. (Buffer와 Texture로 나뉨)

// ID3D11Buffer : 구조체를 만들어 넘기는 자원(Buffer)		ID3D11Texture1D, ID3D11Texture2D, ID3D11Texture3D : 이미지화된 자원(Texture)

// Resource View : Texture 자원의 사용 용도를 명확히 해주는 개념
// - ID3D11RenderTargetView(화면 그리기), ID3D11ShaderResourceView(이미 만들어진 이미지 넘기기), ID3D11DepthStencilView(깊이 관련...), ID3D11UnorderedAccessView(읽고 쓰기가 다 된다)

 

Execute.h

그 후, 실행을 위한 Execute.h, Execute.cpp 파일을 만들어준다.

#pragma once
class Execute final
{
public:
	Execute();
	~Execute();

	void Update();
	void Render();

private:
	class Graphics* graphics = nullptr;


};

 

Execute.cpp

#include "stdafx.h"
#include "Execute.h"

#include "Graphics.h"

Execute::Execute()
{
	// 그냥 쓰면 불완전한 형식은 사용할 수 없다고 나옴. 따라서 해당 자료형의 헤더파일을 include 해주어야 한다.
	graphics = new Graphics();
	graphics->Initialize();
	graphics->CreateBackBuffer(static_cast<uint>(Settings::Get().GetWidth()), static_cast<uint>(Settings::Get().GetHeight()));
}

Execute::~Execute()
{
	SAFE_DELETE(graphics);
}

void Execute::Update()
{
}

void Execute::Render()
{
	graphics->Begin();

	// Rendering Part
	{


	}

	graphics->End();

}

 

Settings.h

Settings.h 에서 싱글턴 객체인 Settings를 활용해 창의 크기나 높이 정보 등을 저장하고 가져올 수 있도록 만든다.

#pragma once
#include "stdafx.h"

// Singleton Pattern : 하나의 인스턴스만 생성
class Settings final
{
public:
	static Settings& Get()
	{
		static Settings instance;
		return instance;
	}

	auto GetWindowHandle() const -> HWND { return handle; }
	void SetWindowHandle(HWND handle) { this->handle = handle; }

	auto GetWidth() const -> const float& { return width; }
	void SetWidth(const float& width) { this->width = width; }

	auto GetHeight() const -> const float& { return height; }
	void SetHeight(const float& height) { this->height = height; }


private:
	// default 키워드는 = {} 로 생각하면 된다.
	Settings() = default;
	~Settings() = default;

private:
	HWND handle = nullptr;
	float width = 0.0f;
	float height = 0.0f;

};

 

Program.cpp

그 후, WinMain함수가 들어가 있는 Program.cpp에서 Execute 객체를 통해 update와 render를 호출해주면 된다. Settings를 통해 창에 대한 설정 값들을 받아온다.

#include "stdafx.h"
#include "Window.h"
#include "Execute.h"

int APIENTRY WinMain
(
	HINSTANCE hInstance,
	HINSTANCE prevInstance,
	LPSTR lpszCmdParam,
	int nCmdShow
)
{
	Window::Create(hInstance, 500, 500);
	Window::Show();

	Settings::Get().SetWindowHandle(Window::global_handle);
	Settings::Get().SetWidth(static_cast<float>(Window::GetWidth()));
	Settings::Get().SetHeight(static_cast<float>(Window::GetHeight()));
	
	Execute* execute = new Execute();

	while (Window::Update())
	{
		execute->Update();
		execute->Render();
	}

	SAFE_DELETE(execute);
	Window::Destroy();
	return 0;
}

 

결과

연동이 잘 되었다면 회색 바탕의 화면이 나오는 것을 확인할 수 있다. D3DXCOLOR에서 0Xff555566로 설정했기에, 0xff5566에 해당하는 hex color(대충 회색임)이 출력된다.


더 자세한 내용이 궁금하시다면 직접 들어보시는 걸 추천드립니다!

Comments