서의 공간

[T21.1] Bindable System 본문

Graphics API/DirectX 11 - Chili

[T21.1] Bindable System

홍서의 2020. 11. 29. 12:20

이 장의 목표는 앞서 도형을 그리는 방법에서 설계를 새롭게 하여 파이프라인에 바인드 가능한 것을 모두 클래스화 한다. 그리고 다양한 구조의 도형을 그려본다. Bindable 클래스는 Bind 가능한 모든 클래스의 베이스 클래스로서, 구조는 다음과 같다.

[그림 21.1] Binable 클래스와 하위 클래스들

공통 개념

1. ComPtr::GetAddressOf와 ComPtr::operator&의 차이

ComPtr::GetAddressOf
Retrieves the address of the ptr_ data member, which contains a pointer to the interface represented by this ComPtr.
Return Value
The address of a variable.
ComPtr::operator&
Releases the interface associated with this ComPtr object and then retrieves the address of the ComPtr object.
Return Value
A weak reference to the current ComPtr.
Remarks
This method differs from ComPtr::GetAddressOf in that this method releases a reference to the interface pointer. Use ComPtr::GetAddressOf when you require the address of the interface pointer but don't want to release that interface.

Bindable 클래스

더보기

Bindable.h

#pragma once
#include "Graphics.h"

class Bindable
{
public:
	virtual void Bind( Graphics& gfx ) noexcept = 0;
	virtual ~Bindable() = default;
protected:
	static ID3D11DeviceContext* GetContext( Graphics& gfx ) noexcept;
	static ID3D11Device* GetDevice( Graphics& gfx ) noexcept;
	static DxgiInfoManager& GetInfoManager( Graphics& gfx ) noexcept(IS_DEBUG);
};

Bindable.cpp

#include "Bindable.h"

ID3D11DeviceContext* Bindable::GetContext( Graphics& gfx ) noexcept
{
	return gfx.pContext.Get();
}

ID3D11Device* Bindable::GetDevice( Graphics& gfx ) noexcept
{
	return gfx.pDevice.Get();
}

DxgiInfoManager& Bindable::GetInfoManager( Graphics& gfx ) noexcept(IS_DEBUG)
{
#ifndef NDEBUG
	return gfx.infoManager;
#else
	throw std::logic_error( "YouFuckedUp! (tried to access gfx.infoManager in Release config)" );
#endif
}

VertexBuffer 클래스

더보기

핵심 개념

VertexBuffer 생성자를 template 함수로 정의한 이유는 Vertex의 구조를 모르기 때문이다. 생성자의 인자로 vertices가 넘겨지는 것은 알고 있지만, 그 vertices의 원소인 vertex의 구조체의 속성들을 모르기 때문에 template인자가 추가되었다.

stride 변수는 하나의 vertex의 byte크기이고, 이것은 GPU로 하여금 VertexBuffer를 읽어들일 때, stride 단위로 읽어들이게 한다. 사전 뜻은 보폭이다.

 

VertexBuffer.h

#pragma once
#include "Bindable.h"
#include "GraphicsThrowMacros.h"

class VertexBuffer : public Bindable
{
public:
	template<class V>
	VertexBuffer( Graphics& gfx,const std::vector<V>& vertices )
		:
		stride( sizeof( V ) )
	{
		INFOMAN( gfx );

		D3D11_BUFFER_DESC bd = {};
		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		bd.Usage = D3D11_USAGE_DEFAULT;
		bd.CPUAccessFlags = 0u;
		bd.MiscFlags = 0u;
		bd.ByteWidth = UINT( sizeof( V ) * vertices.size() );
		bd.StructureByteStride = sizeof( V );
		D3D11_SUBRESOURCE_DATA sd = {};
		sd.pSysMem = vertices.data();
		GFX_THROW_INFO( GetDevice( gfx )->CreateBuffer( &bd,&sd,&pVertexBuffer ) );
	}
	void Bind( Graphics& gfx ) noexcept override;
protected:
	UINT stride;
	Microsoft::WRL::ComPtr<ID3D11Buffer> pVertexBuffer;
};

 VertexBuffer.cpp

#include "VertexBuffer.h"

void VertexBuffer::Bind( Graphics& gfx ) noexcept
{
	const UINT offset = 0u;
	GetContext( gfx )->IASetVertexBuffers( 0u,1u,pVertexBuffer.GetAddressOf(),&stride,&offset );
}

VertexShader 클래스

더보기

핵심 개념

PixelShader클래스와 차이점이 몇 있다. GetBytecode()함수와 pBytecodeBlob변수의 유무인데, 이 이유는 하나의 모델을 그리기 위해 pBytecodeBlob 변수는 2개가 필요없다. 단 하나의 ID3DBlob 인터페이스만 있으면 된다. 그것이 VertexShader에 정의되었고, 이 변수를 리턴하기 위한 함수 GetBytecode함수가 정의된 것이다.

그럼 PixelShader에서는 ID3DBlob 인터페이스가 없으므로 PixelShader를 어떻게 생성할 지 의문인데, 그것에 대한 설명은 PixelShader에서 한다.

 

VertexShader.h

#pragma once
#include "Bindable.h"

class VertexShader : public Bindable
{
public:
	VertexShader( Graphics& gfx,const std::wstring& path );
	void Bind( Graphics& gfx ) noexcept override;
	ID3DBlob* GetBytecode() const noexcept;
protected:
	Microsoft::WRL::ComPtr<ID3DBlob> pBytecodeBlob;
	Microsoft::WRL::ComPtr<ID3D11VertexShader> pVertexShader;
};

VertexShader.cpp

#include "VertexShader.h"
#include "GraphicsThrowMacros.h"


VertexShader::VertexShader( Graphics& gfx,const std::wstring& path )
{
	INFOMAN( gfx );

	GFX_THROW_INFO( D3DReadFileToBlob( path.c_str(),&pBytecodeBlob ) );
	GFX_THROW_INFO( GetDevice( gfx )->CreateVertexShader( 
		pBytecodeBlob->GetBufferPointer(),
		pBytecodeBlob->GetBufferSize(),
		nullptr,
		&pVertexShader 
	) );
}

void VertexShader::Bind( Graphics& gfx ) noexcept
{
	GetContext( gfx )->VSSetShader( pVertexShader.Get(),nullptr,0u );
}

ID3DBlob* VertexShader::GetBytecode() const noexcept
{
	return pBytecodeBlob.Get();
}

PixelShader 클래스

더보기

핵심 개념

클래스 변수에 ID3DBlob 인터페이스를 선언하지 않는다. 그 대신 임시로 생성자의 로컬 변수로서 선언한다. 그러면 아무 문제없이 PixelShader를 만들 수 있다. 생성자가 호출이 종료되는 시점에서 ID3DBlob 인터페이스는 release 될 것이다.

 

PixelShader.h

#pragma once
#include "Bindable.h"

class PixelShader : public Bindable
{
public:
	PixelShader( Graphics& gfx,const std::wstring& path );
	void Bind( Graphics& gfx ) noexcept override;
protected:
	Microsoft::WRL::ComPtr<ID3D11PixelShader> pPixelShader;
};

PixelShader.cpp

#include "PixelShader.h"
#include "GraphicsThrowMacros.h"

PixelShader::PixelShader( Graphics& gfx,const std::wstring& path )
{
	INFOMAN( gfx );

	Microsoft::WRL::ComPtr<ID3DBlob> pBlob;
	GFX_THROW_INFO( D3DReadFileToBlob( path.c_str(),&pBlob ) );
	GFX_THROW_INFO( GetDevice( gfx )->CreatePixelShader( pBlob->GetBufferPointer(),pBlob->GetBufferSize(),nullptr,&pPixelShader ) );
}

void PixelShader::Bind( Graphics& gfx ) noexcept
{
	GetContext( gfx )->PSSetShader( pPixelShader.Get(),nullptr,0u );
}

IndexBuffer 클래스

더보기

핵심 개념

IASetIndexBuffer()함수의 두 번째 인자 DXGI_FORMAT은 인덱스 버퍼 안에 있는 데이터의 포맷을 명시한다. 인덱스 버퍼의 데이터는 오직 16-bit(DXGI_FORMAT_R16_UINT)와 32-bit(DXGI_FORMAT_R32_UINT) 정수만 허락된다.

 

IndexBuffer.h

#pragma once
#include "Bindable.h"

class IndexBuffer : public Bindable
{
public:
	IndexBuffer( Graphics& gfx,const std::vector<unsigned short>& indices );
	void Bind( Graphics& gfx ) noexcept override;
	UINT GetCount() const noexcept;
protected:
	UINT count;
	Microsoft::WRL::ComPtr<ID3D11Buffer> pIndexBuffer;
};

IndexBuffer.cpp

#include "IndexBuffer.h"
#include "GraphicsThrowMacros.h"

IndexBuffer::IndexBuffer( Graphics& gfx,const std::vector<unsigned short>& indices )
	:
	count( (UINT)indices.size() )
{
	INFOMAN( gfx );

	D3D11_BUFFER_DESC ibd = {};
	ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	ibd.Usage = D3D11_USAGE_DEFAULT;
	ibd.CPUAccessFlags = 0u;
	ibd.MiscFlags = 0u;
	ibd.ByteWidth = UINT( count * sizeof( unsigned short ) );
	ibd.StructureByteStride = sizeof( unsigned short );
	D3D11_SUBRESOURCE_DATA isd = {};
	isd.pSysMem = indices.data();
	GFX_THROW_INFO( GetDevice( gfx )->CreateBuffer( &ibd,&isd,&pIndexBuffer ) );
}

void IndexBuffer::Bind( Graphics& gfx ) noexcept
{
	GetContext( gfx )->IASetIndexBuffer( pIndexBuffer.Get(),DXGI_FORMAT_R16_UINT,0u );
}

UINT IndexBuffer::GetCount() const noexcept
{
	return count;
}

ConstantBuffer 클래스

더보기

핵심 개념

ConstantBuffer는 VS단계 또는 PS단계에 바인드 될 수 있다. VS단계에 바인드 되는 경우 대표적으로 모든 정점을 동차 절단 공간(투영 공간) 기준 좌표로 변환하는 동차 절단 공간(투영 공간) 변환 매트릭스 상수를 넘기는 경우, PS단계에 바인드 되는 경우는 대표적으로 면의 색상을 바꾸는 경우가 있다. 따라서 두 종류의 ConstantBuffer를 구분할 필요가 있는데(VS에 바인드 될 ConstantBuffer 하나, PS에 바인드 될 ConstantBuffer 하나), 코드를 보면 ConstantBuffer를 상속받은 두 종류의 버퍼 클래스를 확인 할 수 있다(VertexConstantBuffer, PixelConstantBuffer).

ContantBuffer의 생성자가 두 개 버전이 있는데, 먼저 ConstantBuffer(Graphics& gfx, const C& consts)에서 template 인자 C는 일반적으로 모델에서 정의 할 Constant 구조체 타입이다. 구조체 타입이므로 D3D11_SUBRESOURCE_DATA 구조체의 멤버 pSysMem변수의 파라미터 이름의 주소를 넘기는 것을 확인 할 수 있있다. 구조체 타입이기 때문에 가능하다. 두 번째 생성자 버전 ConstantBuffer(Graphics& gfx) 에서 사용되는 템플릿 인자 C는 일반적으로 DirectX::XMMATRIX가 된다. 이것은 TransformCbuf 클래스에서 확인 할 수 있다.

상속한 각 VertexConstantBuffer, PixelConstantBuffer 클래스에서 using 키워드를 사용한다. 이유는 template 클래스인 경우 타입을 사전에 알 수 없기 때문에 해당 template 인자 C로서 pConstantBuffer변수와 Bindable::GetContext() 함수를 사용하겠다고 명시한 것이다. public 안에서 ConstantBuffer 생성자 역시 마찬가지이다.

 

ConstantBuffer.h

#pragma once
#include "Bindable.h"
#include "GraphicsThrowMacros.h"

template<typename C>
class ConstantBuffer : public Bindable
{
public:
	void Update( Graphics& gfx,const C& consts )
	{
		INFOMAN( gfx );

		D3D11_MAPPED_SUBRESOURCE msr;
		GFX_THROW_INFO( GetContext( gfx )->Map(
			pConstantBuffer.Get(),0u,
			D3D11_MAP_WRITE_DISCARD,0u,
			&msr
		) );
		memcpy( msr.pData,&consts,sizeof( consts ) );
		GetContext( gfx )->Unmap( pConstantBuffer.Get(),0u );
	}
	ConstantBuffer( Graphics& gfx,const C& consts )
	{
		INFOMAN( gfx );

		D3D11_BUFFER_DESC cbd;
		cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		cbd.Usage = D3D11_USAGE_DYNAMIC;
		cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		cbd.MiscFlags = 0u;
		cbd.ByteWidth = sizeof( consts );
		cbd.StructureByteStride = 0u;

		D3D11_SUBRESOURCE_DATA csd = {};
		csd.pSysMem = &consts;
		GFX_THROW_INFO( GetDevice( gfx )->CreateBuffer( &cbd,&csd,&pConstantBuffer ) );
	}
	ConstantBuffer( Graphics& gfx )
	{
		INFOMAN( gfx );

		D3D11_BUFFER_DESC cbd;
		cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		cbd.Usage = D3D11_USAGE_DYNAMIC;
		cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		cbd.MiscFlags = 0u;
		cbd.ByteWidth = sizeof( C );
		cbd.StructureByteStride = 0u;
		GFX_THROW_INFO( GetDevice( gfx )->CreateBuffer( &cbd,nullptr,&pConstantBuffer ) );
	}
protected:
	Microsoft::WRL::ComPtr<ID3D11Buffer> pConstantBuffer;
};

template<typename C>
class VertexConstantBuffer : public ConstantBuffer<C>
{
	using ConstantBuffer<C>::pConstantBuffer;
	using Bindable::GetContext;
public:
	using ConstantBuffer<C>::ConstantBuffer;
	void Bind( Graphics& gfx ) noexcept override
	{
		GetContext( gfx )->VSSetConstantBuffers( 0u,1u,pConstantBuffer.GetAddressOf() );
	}
};

template<typename C>
class PixelConstantBuffer : public ConstantBuffer<C>
{
	using ConstantBuffer<C>::pConstantBuffer;
	using Bindable::GetContext;
public:
	using ConstantBuffer<C>::ConstantBuffer;
	void Bind( Graphics& gfx ) noexcept override
	{
		GetContext( gfx )->PSSetConstantBuffers( 0u,1u,pConstantBuffer.GetAddressOf() );
	}
};

TransformCbuf 클래스

더보기

핵심 개념

이 클래스는 동차 절단 공간 변환 상수 버퍼이다. 따라서 VertexConstantBuffer<DirectX::XMMATRIX>이다. 즉 이 상수버퍼안에 들어있는 데이터는 매트릭스이다. TransformCbuf 생성자의 2번째 파라미터는 그릴 도형의 참조를 인자로 받는데, 이유는 그 도형의 월드 공간 좌표 정보를 가져와야 하기 때문이다. 카메라 공간 변환 매트릭스와 투영 변환 매트릭스는 Graphics 클래스에서 얻어온다. Bind() 함수에서는 도형의 모든 정점을 동차 절단 공간 변환을 한 후 만들어진 상수 버퍼에 쓴다(write). 그리고 VS에 바인드 한다.

 

TransformCbuf.h

#pragma once
#include "ConstantBuffers.h"
#include "Drawable.h"
#include <DirectXMath.h>

class TransformCbuf : public Bindable
{
public:
	TransformCbuf( Graphics& gfx,const Drawable& parent );
	void Bind( Graphics& gfx ) noexcept override;
private:
	static std::unique_ptr<VertexConstantBuffer<DirectX::XMMATRIX>> pVcbuf;
	const Drawable& parent;
};

TransformCbuf.cpp

#include "TransformCbufEx.h"

TransformCbufEx::TransformCbufEx(Graphics & gfx, const Drawable & parent)
	:
	parent(parent)
{
	if (!pVcbuf)
	{
		pVcbuf = std::make_unique<VertexConstantBuffer<DirectX::XMMATRIX>>(gfx);
	}
}

void TransformCbufEx::Bind(Graphics & gfx) noexcept
{
	pVcbuf->Update(gfx,
		DirectX::XMMatrixTranspose(
			parent.GetTransformXM() *
			gfx.GetCamera() *
			gfx.GetProjection()
		)
	);
	pVcbuf->Bind(gfx);
}

std::unique_ptr<VertexConstantBuffer<DirectX::XMMATRIX>> TransformCbufEx::pVcbuf;

InputLayout 클래스

더보기

InputLayout.h

#pragma once
#include "Bindable.h"

class InputLayout : public Bindable
{
public:
	InputLayout( Graphics& gfx,
		const std::vector<D3D11_INPUT_ELEMENT_DESC>& layout,
		ID3DBlob* pVertexShaderBytecode );
	void Bind( Graphics& gfx ) noexcept override;
protected:
	Microsoft::WRL::ComPtr<ID3D11InputLayout> pInputLayout;
};

InputLayout.cpp

#include "InputLayout.h"
#include "GraphicsThrowMacros.h"

InputLayout::InputLayout( Graphics& gfx,
	const std::vector<D3D11_INPUT_ELEMENT_DESC>& layout,
	ID3DBlob* pVertexShaderBytecode )
{
	INFOMAN( gfx );

	GFX_THROW_INFO( GetDevice( gfx )->CreateInputLayout(
		layout.data(),(UINT)layout.size(),
		pVertexShaderBytecode->GetBufferPointer(),
		pVertexShaderBytecode->GetBufferSize(),
		&pInputLayout
	) );
}

void InputLayout::Bind( Graphics& gfx ) noexcept
{
	GetContext( gfx )->IASetInputLayout( pInputLayout.Get() );
}

Topology 클래스

더보기

Topology.h

#pragma once
#include "Bindable.h"

class Topology : public Bindable
{
public:
	Topology( Graphics& gfx,D3D11_PRIMITIVE_TOPOLOGY type );
	void Bind( Graphics& gfx ) noexcept override;
protected:
	D3D11_PRIMITIVE_TOPOLOGY type;
};

Topology.cpp

#include "Topology.h"

Topology::Topology( Graphics& gfx,D3D11_PRIMITIVE_TOPOLOGY type )
	:
	type( type )
{}

void Topology::Bind( Graphics& gfx ) noexcept
{
	GetContext( gfx )->IASetPrimitiveTopology( type );
}

'Graphics API > DirectX 11 - Chili' 카테고리의 다른 글

[T21.3] Box, Hill, Sphere, Cylinder, Skull, Wave  (1) 2020.12.01
[T21.2] Drawble System  (0) 2020.11.30
[T20] Solid cube, Depth and stencil  (0) 2020.11.29
[T18] Constant buffer  (0) 2020.11.29
[T17] Draw Indexed Triangle  (0) 2020.11.29
Comments