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을 사용해서 만드는게 더 좋을 것 같다.