четверг, 3 мая 2012 г.

Simply Compute Shader Example

Одной из главных особенностей DirectX 11 является наличие Compute Shader. У меня появилась идея написать простой пример на эту тему. Прежде всего позвольте мне сказать, что вычислительные шейдеры являются чем-то потрясающим! Они открывают так много возможностей, что сразу теряешься в идеях, где бы их опробовать в действии. В этом примере будет показано, как создать Compute Shader и затем использовать его для запуска потоков, которые просто выведут идентификатор потока в текстуру.
 Device device = new Device(DriverType.Hardware, DeviceCreationFlags.Debug, FeatureLevel.Level_11_0);
ComputeShader compute = Helper.LoadComputeShader(device, "SimpleCompute.hlsl", "main"); Texture2D uavTexture;
UnorderedAccessView computeResult = Helper.CreateUnorderedAccessView(device, 1024, 1024, Format.R8G8B8A8_UNorm, out uavTexture);
device.ImmediateContext.ComputeShader.Set(compute);
device.ImmediateContext.ComputeShader.SetUnorderedAccessView(computeResult, 0);device.ImmediateContext.Dispatch(32, 32, 1);
Texture2D.ToFile(device.ImmediateContext, uavTexture, ImageFileFormat.Png, "uav.png");

Верьте или нет, но это весь код обработчика.

Вот что именно нужно сделать в коде:
1) Создать графическое устройство DX11, с тем чтобы использовать Compute Shaders 5.0
2) Загрузить/скомпилить код HLSL в объект ComputeShader.
3) Создать объект UnorderedAccesdView  1024 x 1024, который будет использоваться для хранения данных.
4) Передать ComputeShader и UnorderedAccesdView на графическое устройство.
5) Запустить ComputeShader путем вызова метода Dispatch (32 x 32 x 1).
6) Сохранить текстуру на жесткий диск.

Код HLSL еще проще:

RWTexture2D<float4> Output;

[numthreads(32, 32, 1)]
void main( uint3 threadID : SV_DispatchThreadID )
{
    Output[threadID.xy] = float4(threadID.xy / 1024.0f, 0, 1);
}

Как вы можете видеть объект RWTexture2D используется для хранения данных. Шейдер -настроен для запуска 32 x 32 x 1 потоков в группе потоков. Это означает, что процессор запустит 32 x 32 x 1 группу  потоков, то есть  (32x32) x (32x32) x 1 потоки запустятся по отдельности. Это равняется одному потоку на пиксел в выходных данных UAV. Так в UAV, цвет устанавливается равным идентификатору потока - результат работы этого кода приводится на следующем изображении :



Довольно просто, да? Но не очень интересно. Мы могли бы легко сделать что-то подобное с pixel Shader (хотя нам пришлось бы растрировать полноэкранный квад для этого).

Мы должны попытаться сделать то, что показывает силу вычислительного шейдера. Что-то, что нельзя было сделать в шейдерах раньше. Как насчет рисования некоторых примитивов, таких как линии или круги? Для рисования линий, давайте использовать алгоритм ЦДА. Он переводится на HLSL очень легко.

void Plot(int x, int y)
{
   Output[uint2(x, y)] = float4(0, 0, 1, 1);
}

void DrawLine(float2 start, float2 end)
{
    float dydx = (end.y - start.y) / (end.x - start.x);
    float y = start.y;
    for (int x = start.x; x <= end.x; x++)    
   {
        Plot(x, round(y));        y = y + dydx;   
   }
}

Для рисования кругов давайте использовать Midpoint Circle алгоритм. Для краткости я не буду обьяснять его сейчас. Для этого в моей главной функции CS, я просто добавлю этот код:

if (threadID.x == 1023 && threadID.y == 1023)
{
    DrawLine(float2(0, 0), float2(1024, 1024));
    DrawLine(float2(0, 1023), float2(1023, 0));
    DrawCircle(512, 512, 250);
    DrawCircle(0, 512, 250);
}

Результат этого кода приводится на следующем рисунке:



Я подозреваю, что вам, возможно покажется довольно странным, написание шейдера, который рисует примитивы. Но этот пример определенно помогает проиллюстрировать мощь Compute Shader.

Effects in DirectX 11

Я никогда не поверю, что Microsoft сделали доброе дело, так усложнив DirectX11. Effects framework - API которое поддерживало загрузку и использование файлов эффектов сгруппированных в HLSL код, рендерринг через passes и techniques – больше не являются неотъемлемой частью D3DX. Вместо этого они предоставили исходный код этой библиотеки, и это означает, что вы можете скомпилировать его самостоятельно!

Итак сделаем это: Зайдите в DX SDK подпапку "Samples\C++\Effects11", откройте "Effects11_*.sln".

Чтобы использовать эффекты API в своем проекте вы должны включить этот заголовок: "YOUR_DX_SDK_PATH\Samples\C++\Effects11\Inc\D3dx11effect.h" and link with this lib: "YOUR_DX_SDK_PATH\Samples\C++\Effects11\Debug\D3DX11EffectsD.lib (Debug) or "YOUR_DX_SDK_PATH\Samples\C++\Effects11\Release\D3DX11Effects.lib" (Release), as well as with "d3dcompiler.lib" (в обеих конфигурациях).

Вот пример того, как загружается эффект из файла. Вы должны сначала скомпилировать исходный код из файла или из памяти в бинарный эффект (ID3D10Blob), а затем создать реальный объект эффекта из него.

// Compile effect from HLSL file into binary Blob in memory
ID3D10Blob *effectBlob = 0, *errorsBlob = 0;
HRESULT hr = D3DX11CompileFromFile("Effect1.fx", 0, 0, 0, "fx_5_0", 0, 0, 0, &effectBlob, &errorsBlob, 0);
assert(SUCCEEDED(hr) && effectBlob);
if (errorsBlob) errorsBlob->Release();
// Create D3DX11 effect from compiled binary memory block
ID3DX11Effect *g_Effect;
hr = D3DX11CreateEffectFromMemory(effectBlob->GetBufferPointer(), effectBlob->GetBufferSize(), 0, g_Dev, &g_Effect);
assert(SUCCEEDED(hr));
effectBlob->Release();

Одного эффекта не достаточно. Вам нужно получить объект, который представляет "pass" чтобы использовать его. Если вы получите технику с эффектом (по индексу или по имени), то от нее получите и "pass".

ID3DX11EffectTechnique *g_EffectTechnique; // No need to be Release()-d.
g_EffectTechnique = g_Effect->GetTechniqueByIndex(0);
assert(g_EffectTechnique && g_EffectTechnique->IsValid());
ID3DX11EffectPass *g_EffectPass; // No need to be Release()-d.
g_EffectPass = g_EffectTechnique->GetPassByIndex(0);
assert(g_EffectPass && g_EffectPass->IsValid());

Теперь у вас есть этот объект, и вы можете применить настройки этого пасса к deviceContext во время рендеринга:

g_EffectPass->Apply(0, g_Ctx);
g_Ctx->Draw(3, 0);

Но все-таки одна проблема остается. В DirectX 11 вам нужно передать указатель на байт-код  вместе со скомпилированным шейдером при создании input layout - шаг, который вы, вероятно, не можете пропустить. К счастью, есть способ получить доступ к этому указателю. Он хранится внутри загруженного эффекта. Вам просто нужно пройти через два дескриптора, как здесь:

D3DX11_PASS_SHADER_DESC effectVsDesc;
g_EffectPass->GetVertexShaderDesc(&effectVsDesc);
D3DX11_EFFECT_SHADER_DESC effectVsDesc2;
effectVsDesc.pShaderVariable->GetShaderDesc(effectVsDesc.ShaderIndex, &effectVsDesc2);
const void *vsCodePtr = effectVsDesc2.pBytecode;
unsigned vsCodeLen = effectVsDesc2.BytecodeLength;
ID3D11InputLayout *g_InputLayout;
D3D11_INPUT_ELEMENT_DESC inputDesc[] = { /* ... */ };
hr = g_Dev->CreateInputLayout( inputDesc, _countof(inputDesc), vsCodePtr, vsCodeLen, &g_InputLayout);

К счастью, дела обстоят так, что effect framework не добавляет больше функциональности чем HLSL шейдеры поддерживающиеся в D3D11, поэтому вы можете его не использовать. Определение этих методов и passess не столь важно, в конце концов ...

Physically Based Rendering