• Home
  • About
    • 게임 개발자 유정룡 photo

      게임 개발자 유정룡

      포트폴리오

    • Learn More
    • Email
    • Github
    • Bitbucket
  • Projects
    • All Projects
    • All Tags

DirectX11 공부 4일차(Wrapping Class - Shader)

04 Jun 2021

Reading time ~3 minutes

Wrapping Class - Shader

이젠 Shader 뤠핑 할 순서이다.

좀 많이 크다….

그런데 여기서 좀 특이한 방법들이 사용되었다.

D3D11_Shader.h

#pragma once

// 뒤에 U를 붙이면 unsigned가 됨

// 왜 비트연산을 사용하는가?
// 00000001
// 00000010
// 둘다
// 00000011

enum ShaderScope : uint
{
	ShaderScope_Unknown = 0U,
	ShaderScope_VS = 1U << 0,
	ShaderScope_PS = 1U << 1
};

enum class CompilationState : uint
{
	 Uknown,
	 Compiling,
	 Succeeded,
	 Failed
};

class D3D11_Shader final
{
public:
	D3D11_Shader(class Graphics* graphics);
	~D3D11_Shader();

	void* GetResource() const { return resource; }
	bool HasResource() const { return resource != nullptr; }
	ID3DBlob* GetShaderBlob() const { return shader_blob; }

	std::string GetName() const { return name; }
	void SetName(const std::string& name) { this->name = name; }

	std::string GetPath() const { return path; }

	ShaderScope GetShaderScope() const { return shader_scrope; }
	CompilationState GetCompilationState() const { return compilation_state; }
	
	const char* GetEntryPoint() const;
	const char* GetTargetProfile() const;
	const char* GetShaderModel() const;

	bool IsCompiled() const { return compilation_state == CompilationState::Succeeded; }

	void Create(ShaderScope scope, const std::string& shader);
	void Clear();

private:
	void* Compile(ShaderScope scope, const std::string& shader);

private:
	ID3D11Device* device = nullptr;
	ID3DBlob* shader_blob = nullptr;
	
	/// com interface 라서 템플릿말고 이것을 씀
	void* resource = nullptr;

	std::string name = ""; // TODO : 
	std::string path = ""; // TODO : 

	ShaderScope shader_scrope = ShaderScope_Unknown;
	CompilationState compilation_state = CompilationState::Uknown;
};

처음 해보는 방법으로는 enum 을 사용할때 비트마스크를 사용해서 판독한는 것과,

void*를 사용해서 유동적으로 데이터 타입을 바꾸는 것이다.

templete 이랑 비슷한거 같은데 대체해서 사용해도 될거같다.

아니, 그냥 templete 을 사용하는게 더 안전한것 같다.

이유는 밑에서 보자

D3D11_Shader.cpp

#include "stdafx.h"
#include "D3D11_Shader.h"

D3D11_Shader::D3D11_Shader(Graphics* graphics)
{
	device = graphics->GetDevice();
}

D3D11_Shader::~D3D11_Shader()
{
	Clear();
}

const char* D3D11_Shader::GetEntryPoint() const
{
	static const char* entry_point_empty = nullptr;
	static const char* entry_point_vs = "VS";
	static const char* entry_point_ps = "PS";

	switch (shader_scrope)
	{
	case ShaderScope_VS: 
		return entry_point_vs;
	case ShaderScope_PS: 
		return entry_point_ps;
	}
	return entry_point_empty;
}

const char* D3D11_Shader::GetTargetProfile() const
{
	static const char* target_profile_empty = nullptr;

#if defined(GRAPHICS_API_VERSION_D3D11)
	static const char* target_profile_vs = "vs_5_0";
	static const char* target_profile_ps = "ps_5_0";
#elif defined(GRAPHICS_API_VERSION_D3D12)
	static const char* target_profile_vs = "vs_6_0";
	static const char* target_profile_ps = "ps_6_0";
#endif

	switch (shader_scrope)
	{
	case ShaderScope_VS:
		return target_profile_vs;
	case ShaderScope_PS:
		return target_profile_ps;
	}

	return target_profile_empty;
}

const char* D3D11_Shader::GetShaderModel() const
{
#if defined(GRAPHICS_API_VERSION_D3D11)
	static const char* target_model = "5_0";
#elif defined(GRAPHICS_API_VERSION_D3D12)
	static const char* target_model = "6_0";
#endif

	return target_model;
}

void D3D11_Shader::Create(ShaderScope scope, const std::string& shader)
{
	shader_scrope = scope;

	compilation_state = CompilationState::Compiling;
	resource = Compile(scope, shader);
	compilation_state = HasResource() ? CompilationState::Succeeded : CompilationState::Failed;
}

void D3D11_Shader::Clear()
{
	switch (shader_scrope)
	{
	case ShaderScope_VS:
	{
		ID3D11VertexShader* shader = static_cast<ID3D11VertexShader*>(resource);
		SAFE_RELEASE(shader);
		break;
	}
	case ShaderScope_PS:
		ID3D11PixelShader* shader = static_cast<ID3D11PixelShader*>(resource);
		break;
	}

	SAFE_RELEASE(shader_blob);
	name = "";
	path = "";
	shader_scrope = ShaderScope_Unknown;
	compilation_state = CompilationState::Uknown;
}

void* D3D11_Shader::Compile(ShaderScope scope, const std::string& shader)
{
	ID3DBlob* error = nullptr;
	HRESULT result;

	result = D3DX11CompileFromFileA
	(
		shader.c_str(),
		nullptr,
		nullptr,
		GetEntryPoint(),
		GetTargetProfile(),
		0,
		0,
		nullptr,
		&shader_blob,
		&error,
		nullptr
	);

	if (error)
	{
		const char* str = static_cast<const char*>(error->GetBufferPointer());
		MessageBoxA(nullptr, str, "Shader Error", MB_OK);
	}
	assert(SUCCEEDED(result));

	void* shader_resource = nullptr;
	if (shader_blob)
	{
		switch (shader_scrope)
		{
		case ShaderScope_VS:
		{
			result = device->CreateVertexShader
			(
				shader_blob->GetBufferPointer(), 
				shader_blob->GetBufferSize(), 
				nullptr, 
				reinterpret_cast<ID3D11VertexShader**>(&shader_resource)
			);
			assert(SUCCEEDED(result));
			break;
		}
		case ShaderScope_PS:
		{
			result = device->CreatePixelShader
			(
				shader_blob->GetBufferPointer(),
				shader_blob->GetBufferSize(),
				nullptr,
				reinterpret_cast<ID3D11PixelShader**>(&shader_resource)
			);
			assert(SUCCEEDED(result));
			break;
		}
		}
	}

	return shader_resource;
}

으아아아앜 길다…

일단 위에 EntryPoint와 profile 그리고 shader_model 설정은 설명이 필요 없들듯 하고,

마지막에 Compile 함수만 보자 가장 중요하다.

쉐이더 파일을 읽고 쭉쭉 가면 shader_resource를 생성하는 것을 보면 void*형을 반환한다.

쉐이더 생성하는 곳을 보면 reinterpret_cast을 사용해서 void* 형을 변환한다.

reinterpret_cast

  • 형변환이 이뤄지게 되면 자료형의 bit수의 맞게 들어가게 된다.

  • 포인터가 다른 포인터 형식으로 변환될 수 있도록 한다.

  • 하지만, 변환할때 주소 값이 유효한지는 검사하지 않는다. 그래서 잘못된 주소라면 런타임 오류가 발생한다.

이런 이유 때문에 templete을 사용하는게 더 안전하다고 생각된다.

그리고 마지막 랜더링 할때

graphics->GetDeviceContext()->VSSetShader(static_cast<ID3D11VertexShader*>(vertex_shader->GetResource()), nullptr, 0);
		
graphics->GetDeviceContext()->PSSetShader(static_cast<ID3D11PixelShader*>(pixel_shader->GetResource()), nullptr, 0);

캐스팅을 따로 해줘야 하는 일이 발생한다.

다음에 이 클래스를 만들 때 templete을 사용해서 만드는게 더 좋을 것 같다.



DirectX Share Tweet +1