




[19 Січ 2013] | Компіляція HLSL шейдерів в DirectX 11 |

Шейдера це програми для графічних карт, які дозволяють запрограмувати частини конвеєра рендерінгу. До введення шейдерів налаштування рендерінгу було обмежене змінами станів конвеєра рендерінгу. HLSL шейдер заміняє частину графічного конвеєра DirectX. Є наступні етапи графічного конвеєра, які можуть бути замінені : вершинний шейдер, піксельний шейдер, геометричний шейдер, шейдера тесселяції. Основними шейдерами є вершинний шейдер і піксельний шейдер. Інші типи шейдерів є необовязковими.

Розумні COM вказівники для DirectX обєктів
В DirectX використовуються COM обєкти. При необхідності видалити COM обєкт потрібно вручну викликати метод Release(). Про видалення обєкта можна забути і обєкт залишиться у памяті. Для автоматичного видалення простих С++ обєктів можна використати розумні вказівники, наприклад, з стандартної бібліотеки std. Але стандартні розумні вказівники не викликають метода Release. Виправити це можна використанням розумних COM вказівників. Використання таких вказівників є аналогічним до використання стандартних розумних вказівників. Тільки певні методи мають іншу назву, наприклад для отримання вказівника потрібно викликати vertexShader.GetInterfacePtr().
В Visual Studio додайте в project dependencies comsuppw.lib. Включіть файл #include "comdef.h". Також потрібно оголосити типи вказівників, які будуть використовуватися. Далі наведено приклад:
// Включення необхідних файлів
#include "d3d11.h"
#include "D3D11SDKLayers.h"
#include "d3dcompiler.h"
#include "comdef.h"
// Оголошуємо типи розумних вказівників для DirectX Com Objects
_COM_SMARTPTR_TYPEDEF(ID3D11Device, __uuidof(ID3D11Device));
_COM_SMARTPTR_TYPEDEF(ID3D11DeviceContext, __uuidof(ID3D11DeviceContext));
_COM_SMARTPTR_TYPEDEF(ID3D11VertexShader, __uuidof(ID3D11VertexShader));
_COM_SMARTPTR_TYPEDEF(ID3D11PixelShader, __uuidof(ID3D11PixelShader));
_COM_SMARTPTR_TYPEDEF(ID3D11InputLayout, __uuidof(ID3D11InputLayout));
_COM_SMARTPTR_TYPEDEF(ID3DBlob, __uuidof(ID3DBlob));
_COM_SMARTPTR_TYPEDEF(ID3D11ShaderReflection, IID_ID3D11ShaderReflection);

Компіляція DirectX шейдерів в Visual Studio
Задайте для шейдерів розширення .hlsl. Додайте шейдера в проект. Для кожного шейдера через правий клік -> Properties можна задати параметри компіляції. Наприклад, шейдерна модель, яка повинна використовуватися, чи зберігати з прекомпільованим шейдером debug інформацію, який це тип шейдера і тд. Після цього можна зробити Build для проекта, і Visual Studio скомпілює шейдера і запише їх прекомпільовані версії у одну з папок проекта. Прекомпільовані файли шейдерів по замовчуванню будуть мати .cso розширення. Тепер прекомпільовані шейдера можна завантажити в програму, створити з них DirectX шейдера і використовувати для рендерінгу.
Можна скомпілювати шейдера вручну замість компіляції шейдерів у Visual Studio.
fxc lighting.vertex /E VertexShaderEntry /Fo lighting_vertex.cso
fxc lighting.pixel /E PixelShaderEntry /Fo lighting_pixel.cso
Не забудьте додати d3d11.lib, d3dcompiler.lib, dxguid.lib в project dependencies.
PS. В наступних прикладах не всюди є перевірки на помилки, що повертаються DirectX функціями.

Завантаження вершинного та піксельного шейдерів:
// Оголошуємо необхідні змінні (наприклад, в класі ShaderProgram)
ID3D11VertexShaderPtr vertexShader(nullptr);
ID3D11PixelShaderPtr pixelShader(nullptr);
ID3D11InputLayoutPtr vertexLayout(nullptr);
std::vector<D3D11_INPUT_ELEMENT_DESC> inputLayoutArray;
// шляхи повинні вказувати на файли прекомпільованих шейдерів (.cso)
void buildShaderProgram(ID3D11Device * d3dDevice,
const std::string & vertexShaderPath,
const std::string & pixelShaderPath)
{
HRESULT hr = S_OK;
// зчитати з диску прекомпільований вершинний шейдер
ID3DBlobPtr vertexBlob;
hr = D3DReadFileToBlob(vertexShaderPath.c_str(), &vertexBlob);
if(FAILED(hr))
{
// обробка помилок при неможливості завантаження
return false;
}
// створити вершинний шейдер з прекомпільованого шейдера
hr = d3dDevice->CreateVertexShader(
vertexBlob->GetBufferPointer(),
vertexBlob->GetBufferSize(),
nullptr, &m_impl->vertexShader);
if(FAILED(hr))
{
// помилка при створенні вершинного шейдера
return false;
}
// зчитати з диску прекомпільований піксельний шейдер
ID3DBlobPtr pixelBlob;
hr = D3DReadFileToBlob(pixelShaderPath.c_str(), &pixelBlob);
if(FAILED(hr))
{
// обробка помилок при неможливості завантаження
return false;
}
// створити піксельний шейдер з прекомпільованого шейдера
hr = d3dDevice->CreatePixelShader(
pixelBlob->GetBufferPointer(),
pixelBlob->GetBufferSize(),
nullptr, &m_impl->pixelShader);
if(FAILED(hr))
{
// помилка при створенні піксельного шейдера
return false;
}
// дістати опис вхідних параметрів вершинного шейдера (атрибути в OpenGL)
hr = enumInputLayout(d3dDevice, vertexBlob.GetInterfacePtr());
if(FAILED(hr))
{
// неможливо створити input layout по вхідних параметрах
return false;
}
// дістати опис константних буферів, текстур і семплерів (uniforms в OpenGL)
enumConstantBuffers(d3dDevice, vertexBlob.GetInterfacePtr());
enumConstantBuffers(d3dDevice, pixelBlob.GetInterfacePtr());
return true;
}

Дістати дані про вхідні дані (атрибути) для вершинного шейдера та створити Input Layout:
HRESULT enumInputLayout(ID3D11Device * d3dDevice, ID3DBlob * VSBlob)
{
HRESULT hr = S_OK;
// Дістати опис для прекомпільованого шейдера
ID3D11ShaderReflectionPtr vertReflect;
D3DReflect(
VSBlob->GetBufferPointer(),
VSBlob->GetBufferSize(),
IID_ID3D11ShaderReflection,
(void**) &vertReflect
);
D3D11_SHADER_DESC descVertex;
vertReflect->GetDesc(&descVertex);
// зберегти опис вхідних даних (атрибутів вершинного шейдера)
inputLayoutArray.clear();
uint32 byteOffset = 0;
D3D11_SIGNATURE_PARAMETER_DESC input_desc;
for (uint i = 0; i
{
// дістати опис одного з вхідних параметрів
vertReflect->GetInputParameterDesc(i, &input_desc);
// заповнити структуру даних для подальшого створення input layout
D3D11_INPUT_ELEMENT_DESC ie;
ie.SemanticName = input_desc.SemanticName;
ie.SemanticIndex = input_desc.SemanticIndex;
ie.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
ie.InputSlot = i;
ie.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
ie.InstanceDataStepRate = 0;
ie.AlignedByteOffset = byteOffset;
// вирахувати правильний формат вхідного параметра і зсув
if (input_desc.Mask == 1)
{
if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
{
ie.Format = DXGI_FORMAT_R32_UINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
{
ie.Format = DXGI_FORMAT_R32_SINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
{
ie.Format = DXGI_FORMAT_R32_FLOAT;
}
byteOffset += 4;
}
else if (input_desc.Mask <= 3)
{
if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
{
ie.Format = DXGI_FORMAT_R32G32_UINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
{
ie.Format = DXGI_FORMAT_R32G32_SINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
{
ie.Format = DXGI_FORMAT_R32G32_FLOAT;
}
byteOffset += 8;
}
else if (input_desc.Mask <= 7)
{
if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
{
ie.Format = DXGI_FORMAT_R32G32B32_UINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
{
ie.Format = DXGI_FORMAT_R32G32B32_SINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
{
ie.Format = DXGI_FORMAT_R32G32B32_FLOAT;
}
byteOffset += 12;
}
else if (input_desc.Mask <= 15)
{
if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
{
ie.Format = DXGI_FORMAT_R32G32B32A32_UINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
{
ie.Format = DXGI_FORMAT_R32G32B32A32_SINT;
}
else if (input_desc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
{
ie.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
}
byteOffset += 16;
}
inputLayoutArray.push_back(ie);
// тут можна зберегти інші параметри input_desc, якщо потрібно
}
// створити input layout зі збережених даних
uint numElements = inputLayoutArray.size();
hr = d3dDevice->CreateInputLayout(
inputLayoutArray.data(),
numElements,
VSBlob->GetBufferPointer(),
VSBlob->GetBufferSize(),
&vertexLayout
);
if(FAILED(hr))
{
// неможливо створити input layout
return hr;
}
return S_OK;
}

Дістати дані про константні буфери (uniforms в OpenGL)
HRESULT initializeConstantBuffers(ID3D11Device * d3dDevice, ID3DBlob * blob, bool isVS)
{
// дістати опис для прекомпільованого шейдера
ID3D11ShaderReflectionPtr pReflector = NULL;
D3DReflect(
blob->GetBufferPointer(),
blob->GetBufferSize(),
IID_ID3D11ShaderReflection,
(void**) &pReflector
);
D3D11_SHADER_DESC desc;
pReflector->GetDesc( &desc );
// дістати опис кожного константного буфера в шейдері
for (uint i = 0; i < desc.ConstantBuffers; i++)
{
// дістати опис буфера
D3D11_SHADER_BUFFER_DESC shaderBuffer;
ID3D11ShaderReflectionConstantBuffer * pConstBuffer =
pReflector->GetConstantBufferByIndex(i);
pConstBuffer->GetDesc(&shaderBuffer);
// тут можна зберегти необхідні поля з shaderBuffer
// дістати опис кожної змінної в константному буфері
for (uint j = 0; j < shaderBuffer.Variables; j++)
{
// опис змінної
ID3D11ShaderReflectionVariable * pVariable =
pConstBuffer->GetVariableByIndex(j);
D3D11_SHADER_VARIABLE_DESC varDesc;
pVariable->GetDesc(&varDesc);
// тип змінної
D3D11_SHADER_TYPE_DESC varType;
ID3D11ShaderReflectionType * pType = pVariable->GetType();
pType->GetDesc(&varType);
// тут можна зберегти необхідні поля varType і varDesc
}
// дістати опис точки привязки для буфера
D3D11_SHADER_INPUT_BIND_DESC bindingDesc;
hr = pReflector->GetResourceBindingDescByName(shaderBuffer.Name, &bindingDesc);
// PS. Дані можна зберегти в наступній структурі
//struct ConstantBufferDescription
//{
// std::pair variables;
// D3D11_SHADER_INPUT_BIND_DESC bindingDescription;
//}
}
///////////////////////////////////////////////////
// Зберегти дані про текстури та семплери
for(uint i=0; i
{
D3D11_SHADER_INPUT_BIND_DESC inputBindDesc;
pReflector->GetResourceBindingDesc(i, &inputBindDesc);
// тут можна зберегти необхідні дані з inputBindDesc
}
return S_OK;
}

Встановити поточні шейдера для використання:
// Встановити вершинний шейдер
d3dContext->VSSetShader(vertexShader.GetInterfacePtr(), nullptr, 0);
// Встановити піксельний шейдер
d3dContext->PSSetShader(pixelShader.GetInterfacePtr(), nullptr, 0);


Sun and Black Cat- Ігор Дихта (
