서의 공간
[T16.1] Draw Triangle 본문
Exception 부분은 넘어간다. 또한 디버깅을 위해 여러 유틸 클래스가 추가되었지만 생략한다. 여기서 핵심은 Graphics.h의 DrawTestTriangle()함수이다. 삼각형이 어떻게 그려지는지 .cpp 코드 내에서 설명한다.
핵심 개념
1. Microsoft::WRL::ComPtr: 템플릿 매개 변수로 지정된 인터페이스를 나타내는 스마트 포인터 형식을 만든다. ComPtr은 기본 인터페이스 포인터에 대한 참조 횟수를 자동으로 유지하고 참조 횟수가 0이 되면 인터페이스를 해제한다. 즉 자동으로 해제해주기 때문에 인터페이스의 Release() 함수를 사용할 필요가 없다.
2. ID3DBlob: ID3DBlob 인터페이스는 D3DCommon.h 헤더 파일에 ID3D10Blob으로 정의된 인터페이스로서 정의된다. ID3DBlob은 버전 중립적이어서 모든 Direct3D 버전의 코드에서 사용 가능하다. Blob은 데이터 버퍼로 사용되어 메시 최적화 및 로드 작업 중에 정점, 인접성 및 머티리얼 정보를 저장할 수 있다. 또한 이 객체는 정점, 지오메트리 및 픽셀 셰이더를 컴파일하는 API에서 객체 코드 및 오류 메시지를 반환하는데도 사용한다. 여기서의 주 사용처가 바로 셰이더를 컴파일 한 코드 및 오류 메시지를 반환하는 데에 있다.
3. D3DReadFileToBlob: D3DCompiler.lib가 필요하다. <d3dcompiler.h> 헤더 파일에 정의된 함수이다. 이 헤더 파일은 HLSL에서 사용된다.
4. Shader(셰이더): 셰이더는 소프트웨어의 명령 집합이다. 그러니까 이해한대로 설명하자면 어떤 하나의 특정 도형을 그릴 때 GPU에 입력할 정점과 정점 정보들을 정의하고, 필요한 셰이더를 만든다. 그리고 Input layout에서 셰이더의 bytecode를 저장한 pBlob과 정점들의 정보들을 결정한다. 그렇다는 의미는 아마도 우리가 원하는 도형을 그리기 위해서는 그러한 정점 정보와 이 정점들을 이렇게 이렇게 계산해달라(shader)라고 설명서를 GPU에게 건넨다라고 보는게 맞을 듯하다. 그럼 GPU는 입력된 정점에 대해 병렬적으로 설명서(shader)대로 처리한다.
Graphics 클래스
Graphics.h
#pragma once
#include "ChiliWin.h"
#include "ChiliException.h"
#include <d3d11.h>
#include <wrl.h>
#include <vector>
#include "DxgiInfoManager.h"
class Graphics
{
public:
class Exception : public ChiliException
{
using ChiliException::ChiliException;
};
class HrException : public Exception
{
public:
HrException( int line,const char* file,HRESULT hr,std::vector<std::string> infoMsgs = {} ) noexcept;
const char* what() const noexcept override;
const char* GetType() const noexcept override;
HRESULT GetErrorCode() const noexcept;
std::string GetErrorString() const noexcept;
std::string GetErrorDescription() const noexcept;
std::string GetErrorInfo() const noexcept;
private:
HRESULT hr;
std::string info;
};
class InfoException : public Exception
{
public:
InfoException( int line,const char* file,std::vector<std::string> infoMsgs ) noexcept;
const char* what() const noexcept override;
const char* GetType() const noexcept override;
std::string GetErrorInfo() const noexcept;
private:
std::string info;
};
class DeviceRemovedException : public HrException
{
using HrException::HrException;
public:
const char* GetType() const noexcept override;
private:
std::string reason;
};
public:
Graphics( HWND hWnd );
Graphics( const Graphics& ) = delete;
Graphics& operator=( const Graphics& ) = delete;
~Graphics() = default;
void EndFrame();
void ClearBuffer( float red,float green,float blue ) noexcept;
void DrawTestTriangle();
private:
#ifndef NDEBUG
DxgiInfoManager infoManager;
#endif
// 모든 인터페이스 포인터 타입을 스마트 포인터 타입으로 바꾼다.
Microsoft::WRL::ComPtr<ID3D11Device> pDevice;
Microsoft::WRL::ComPtr<IDXGISwapChain> pSwap;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext;
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> pTarget;
};
Graphics.cpp
#include "Graphics.h"
#include "dxerr.h"
#include <sstream>
#include <d3dcompiler.h>
namespace wrl = Microsoft::WRL;
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib,"D3DCompiler.lib")
// graphics exception checking/throwing macros (some with dxgi infos)
#define GFX_EXCEPT_NOINFO(hr) Graphics::HrException( __LINE__,__FILE__,(hr) )
#define GFX_THROW_NOINFO(hrcall) if( FAILED( hr = (hrcall) ) ) throw Graphics::HrException( __LINE__,__FILE__,hr )
#ifndef NDEBUG
#define GFX_EXCEPT(hr) Graphics::HrException( __LINE__,__FILE__,(hr),infoManager.GetMessages() )
#define GFX_THROW_INFO(hrcall) infoManager.Set(); if( FAILED( hr = (hrcall) ) ) throw GFX_EXCEPT(hr)
#define GFX_DEVICE_REMOVED_EXCEPT(hr) Graphics::DeviceRemovedException( __LINE__,__FILE__,(hr),infoManager.GetMessages() )
#define GFX_THROW_INFO_ONLY(call) infoManager.Set(); (call); {auto v = infoManager.GetMessages(); if(!v.empty()) {throw Graphics::InfoException( __LINE__,__FILE__,v);}}
#else
#define GFX_EXCEPT(hr) Graphics::HrException( __LINE__,__FILE__,(hr) )
#define GFX_THROW_INFO(hrcall) GFX_THROW_NOINFO(hrcall)
#define GFX_DEVICE_REMOVED_EXCEPT(hr) Graphics::DeviceRemovedException( __LINE__,__FILE__,(hr) )
#define GFX_THROW_INFO_ONLY(call) (call)
#endif
Graphics::Graphics( HWND hWnd )
{
/*
DXGI_SWAP_CHAIN_DESC sd = {};
생성 부분 생략
*/
UINT swapCreateFlags = 0u;
#ifndef NDEBUG
swapCreateFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// for checking results of d3d functions
HRESULT hr;
// create device and front/back buffers, and swap chain and rendering context
// 디바이스와 프론트/백 버퍼를 만든다.
GFX_THROW_INFO( D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
swapCreateFlags,
nullptr,
0,
D3D11_SDK_VERSION,
&sd,
&pSwap,
&pDevice,
nullptr,
&pContext
) );
/*
SwapChain의 백버퍼를 얻어와서 백버퍼에 대한 뷰를 생성한다.
일종의 자원인 백버퍼를 파이프라인에 직접 바인딩하지 않는다.
대신 자원에 대한 또 하나의 뷰를 만들고, 그 뷰를 파이프라인에 바인딩한다.
뷰를 자원을 바라보는 또 하나의 시야라고 생각할 수 있다.
또는 그냥 자원에 대한 참조형 변수를 만든다라고 생각해버리자.
pBackBuffer는 자동으로 해제하기 때문에 따로 Release할 필요가 없다.
*/
// gain access to texture subresource in swap chain (back buffer)
wrl::ComPtr<ID3D11Resource> pBackBuffer;
GFX_THROW_INFO( pSwap->GetBuffer( 0,__uuidof(ID3D11Resource),&pBackBuffer ) );
GFX_THROW_INFO( pDevice->CreateRenderTargetView( pBackBuffer.Get(),nullptr,&pTarget ) );
}
/*
SwapChain이 백버퍼를 화면에 보여준다.
모든 그리기 연산을 백버퍼에 한다(Frame의 시작에서 끝으로 향하는 단계).
그리고 모든 그리기 연산이 완료되어 화면에 출력될 준비가 된다면
SwapChain은 백버퍼와 프론트버퍼를 스왑하여 화면에 결과를 출력한다(EndFrame).
*/
void Graphics::EndFrame()
{
HRESULT hr;
#ifndef NDEBUG
infoManager.Set();
#endif
if( FAILED( hr = pSwap->Present( 1u,0u ) ) )
{
if( hr == DXGI_ERROR_DEVICE_REMOVED )
{
throw GFX_DEVICE_REMOVED_EXCEPT( pDevice->GetDeviceRemovedReason() );
}
else
{
throw GFX_EXCEPT( hr );
}
}
}
void Graphics::ClearBuffer( float red,float green,float blue ) noexcept
{
const float color[] = { red,green,blue,1.0f };
pContext->ClearRenderTargetView( pTarget.Get(),color );
}
/**** 핵심 ****/
void Graphics::DrawTestTriangle()
{
namespace wrl = Microsoft::WRL;
HRESULT hr;
// 정점의 자료형
struct Vertex
{
float x;
float y;
};
// create vertex buffer (1 2d triangle at center of screen)
// 정점들의 local space 기준 위치 설정
const Vertex vertices[] =
{
{ 0.0f,0.5f },
{ 0.5f,-0.5f },
{ -0.5f,-0.5f },
};
/*
GPU가 정점들의 배열에 접근하려면, 그 정점들을 버퍼라고 부르는 GPU 자원에 넣어 두어야 한다.
정점들을 저장하는 버퍼를 정점 버퍼(Vertex buffer)라고 부른다. 응용 프로그램에서 정점 같은
자료 원소들의 배열을 GPU에 제공해야 할 때에는 항상! 버퍼를 사용한다.
다음은 정점 버퍼를 만들기 전에 Description을 작성한다.
*/
wrl::ComPtr<ID3D11Buffer> pVertexBuffer;
D3D11_BUFFER_DESC bd = {};
// 버퍼가 무엇으로써 어떻게 파이프라인에 바인드 될 것인지 설정
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
// 자료 원소들이(정점) GPU나 CPU로부터 읽기, 쓰기 권한을 설정
bd.Usage = D3D11_USAGE_DEFAULT;
// 버퍼에 대해 CPU 접근 플래그
bd.CPUAccessFlags = 0u;
// 여러 종류의 플래그
bd.MiscFlags = 0u;
// 버퍼의 bytes 크기. 모든 정점들의 bytes 크기와 같다.
bd.ByteWidth = sizeof( vertices );
/* 정점이 배열안에서 어떻게 구분되어질지, 즉 정점배열에서 각 정점의 크기가 얼마인지.
GPU가 버퍼를 읽을 때 주어진 byte 크기 단위로 건너뛰면서 자료를 읽게 된다.*/
bd.StructureByteStride = sizeof( Vertex );
/* Subresource 데이터를 초기화하기 위한 데이터를 지정한다.
GPU에서 사용할 Subresource 데이터는 메모리에 올려진 데이터로 초기화해야한다.
즉 정점 배열은 우리가 작성한 실제 값들이 들어있으며,
그 값들로 Subresource 데이터를 초기화한다.*/
D3D11_SUBRESOURCE_DATA sd = {};
sd.pSysMem = vertices;
// 모든 준비가 되었다면 GPU가 읽을 수 있는(또는 쓸 수도 있는) 정점 버퍼를 만든다.
GFX_THROW_INFO( pDevice->CreateBuffer( &bd,&sd,&pVertexBuffer ) );
// Bind vertex buffer to pipeline
const UINT stride = sizeof( Vertex );
const UINT offset = 0u;
// Vertex buffer를 만들고 pipeline(IA stage)에 바인드 한다.
pContext->IASetVertexBuffers( 0u,1u,&pVertexBuffer,&stride,&offset );
// create vertex shader
// 정점 셰이더를 만든다
wrl::ComPtr<ID3D11VertexShader> pVertexShader;
wrl::ComPtr<ID3DBlob> pBlob;
GFX_THROW_INFO( D3DReadFileToBlob( L"VertexShader.cso",&pBlob ) );
// VertexShader.cso 파일을 읽어 pBlob에 저장하였고, 이 데이터(컴파일된 셰이더)를 이용하여 VertexShader 객체를 만든다.
GFX_THROW_INFO( pDevice->CreateVertexShader( pBlob->GetBufferPointer(),pBlob->GetBufferSize(),nullptr,&pVertexShader ) );
// bind vertex shader
pContext->VSSetShader( pVertexShader.Get(),nullptr,0u );
// 정점 그리기 호출, Draw 함수의 첫 번째 파라미터는 vertices의 size일 뿐이다.
GFX_THROW_INFO_ONLY( pContext->Draw( (UINT)std::size( vertices ),0u ) );
}
// Graphics exception stuff
// Graphisc exception 관련 함수 구현 모두 생략
// 함수의 원형만 나열한다.
Graphics::HrException::HrException( int line,const char * file,HRESULT hr,std::vector<std::string> infoMsgs ) noexcept
const char* Graphics::HrException::what() const noexcept
const char* Graphics::HrException::GetType() const noexcept
HRESULT Graphics::HrException::GetErrorCode() const noexcept
std::string Graphics::HrException::GetErrorString() const noexcept
std::string Graphics::HrException::GetErrorDescription() const noexcept
std::string Graphics::HrException::GetErrorInfo() const noexcept
const char* Graphics::DeviceRemovedException::GetType() const noexcept
Graphics::InfoException::InfoException( int line,const char * file,std::vector<std::string> infoMsgs ) noexcept
const char* Graphics::InfoException::what() const noexcept
const char* Graphics::InfoException::GetType() const noexcept
std::string Graphics::InfoException::GetErrorInfo() const noexcept
VertexShader
float4 main( float2 pos : Position ) : SV_Position
{
return float4(pos.x,pos.y,0.0f,1.0f);
}
App 클래스
App.h
#pragma once
#include "Window.h"
#include "ChiliTimer.h"
class App
{
public:
App();
// master frame / message loop
int Go();
private:
void DoFrame();
private:
Window wnd;
ChiliTimer timer;
};
App.cpp
#include "App.h"
App::App()
:
wnd( 800,600,"The Donkey Fart Box" )
{}
/*
Go() 함수는 WinMain에서 호출되어 프로그램 종료코드 메시지가 입력될 때까지
무한 반복문을 실행한다.
*/
int App::Go()
{
while( true )
{
// process all messages pending, but to not block for new messages
if( const auto ecode = Window::ProcessMessages() )
{
// if return optional has value, means we're quitting so return exit code
return *ecode;
}
DoFrame();
}
}
// 매 프레임마다 실행된다.
void App::DoFrame()
{
const float c = sin( timer.Peek() ) / 2.0f + 0.5f;
// 매 프레임 백버퍼를 주어진 색상으로 지운다
wnd.Gfx().ClearBuffer( c,c,1.0f );
// 매 프레임 삼각형을 백버퍼에 그린다
wnd.Gfx().DrawTestTriangle();
/* 매 프레임의 끝에서 SwapChain에 의해 프론트 버퍼와 백버퍼를 스왑
하여 백버퍼를 화면에 출력한다.(스왑과 동시에 백버퍼는 프론트버퍼가 되고
프론트 버퍼였던 것은 백버퍼가 된다) */
wnd.Gfx().EndFrame();
}
'Graphics API > DirectX 11 - Chili' 카테고리의 다른 글
[T17] Draw Indexed Triangle (0) | 2020.11.29 |
---|---|
[T16.2] Draw Triangle (0) | 2020.11.28 |
[T16.0] DirectX 11 파이프라인 (0) | 2020.11.28 |
[T11] Graphics class (0) | 2020.11.27 |
[T6] Window class (0) | 2020.11.27 |