diff --git a/src/content/authors/shiokai.json b/src/content/authors/shiokai.json new file mode 100644 index 0000000..b19624e --- /dev/null +++ b/src/content/authors/shiokai.json @@ -0,0 +1,4 @@ +{ + "name": "Shiokai", + "github": "Gs-itisitcat" +} diff --git a/src/content/blog-metas/2024-12-31-slang.json b/src/content/blog-metas/2024-12-31-slang.json new file mode 100644 index 0000000..1dfdb5a --- /dev/null +++ b/src/content/blog-metas/2024-12-31-slang.json @@ -0,0 +1,3 @@ +{ + "postDate": "2024-12-31T13:21:57.286Z" +} diff --git a/src/content/blogs/2024-12-31-slang.md b/src/content/blogs/2024-12-31-slang.md new file mode 100644 index 0000000..b45cac8 --- /dev/null +++ b/src/content/blogs/2024-12-31-slang.md @@ -0,0 +1,671 @@ +--- +title: Slang Shaderをminimalに動かす +description: Slang Shaderをgfxを使用してminimalに動かす方法を解説します。 +category: tech +author: shiokai +tags: + - advent-calendar + - slang-shader +--- + +# Slang Shaderをminimalに動かす + +## はじめに + +本記事は、[OUCC Advent Calendar 2024](https://adventar.org/calendars/10655) の4日目に投稿されるはずだった記事です。[^1] + +Shaderを書く際には、通常その実行環境に応じて異なる言語で記述する必要があります。 +[Slang](https://github.com/shader-slang/slang) は、様々な環境向けのコードを単一のコードから生成できるシェーディング言語です。 +[また、Slang Graphics Layer (GFX)](https://shader-slang.com/slang/gfx-user-guide/index.html) を用いることで、各グラフィックスAPI向けのコードを書かずにslangのshaderを実行することが可能です。 +大変便利なものですが、現時点で試しに使ってみようとした際に以下のようなハードルが存在します。 + +- 日本語での情報が少ない +- 実行に関する公式のガイドでコードの一部が省略されている +- [example](https://github.com/shader-slang/slang/tree/master/examples) の[hello-world](https://github.com/shader-slang/slang/tree/master/examples/hello-world) が、実行にVulkanを露わに使用しており、GFXのみでの実行手法ではない。 +- GFXの使用方法の解説となるexampleが名前から分かりにくい[^2] +- exapmleのコードの一部はがユーティリティライブラリとして分離されている。 + +そこで、本記事では、Slang ShaderによるCompute Shaderを、GFXを使用して実行するコード例を提供します。 +なお、筆者はshading言語の実行環境やC++に関する知識は浅いため、それらに誤りがあるかもしれません。 + +## 環境 + +- OS: Windows 10 +- CPU: Intel Core i5-12400 +- GPU: NVIDIA GeForce RTX 3060 +- Compiler: gcc version 12.2.0 (x86_64-posix-seh-rev2, Built by MinGW-W64 project) +- Slang: v2024.17[^3] + +slangは[GitHubのリリースページ](https://github.com/shader-slang/slang/releases) からダウンロードできます。 +実行には少なくともbin内の`slang.dll`と`gfx.dll`が必要です。 + +また、slangのgfxは実行時にどのグラフィックスAPIを使用するか自動的に決定できます。 +当環境ではDrectX 12が使用されました。必要に応じて[DirectX Shader CompilerのRelease](https://github.com/microsoft/DirectXShaderCompiler/releases) からダウンロードしてください。 +実行には`dxcompiler.dll`が必要です。 +また正常に結果を受け取るために`dxil.dll`も必要です。[^4] + +Compile command: `g++ main.cpp -l slang -l gfx -I /path/to/slang/include -L path/to/slang/bin -o main.exe.exe` + +## 方針 + +[Getting Started with Slang](https://shader-slang.com/slang/user-guide/get-started.html) にも記載されている、hello-worldのslang shaderコードを実行することを目標とします。 + + +```hlsl +// hello-world.slang +StructuredBuffer buffer0; +StructuredBuffer buffer1; +RWStructuredBuffer result; + +[shader("compute")] +[numthreads(1,1,1)] +void computeMain(uint3 threadId : SV_DispatchThreadID) +{ + uint index = threadId.x; + result[index] = buffer0[index] + buffer1[index]; +} +``` + +gfxがC++向けのAPIであるため、実行コードはC++で記述します。 +可能な限り、slangによるライブラリと標準ライブラリのみで実装します。 + +基本的に[Getting Started with Slang Graphics Layer](https://shader-slang.com/slang/gfx-user-guide/01-getting-started.html) の内容に準拠して進めます。 +ただしオブジェクトの作成を、最初の使用の直前に行うように変更します。 +また一部は[`example/shader-object`](https://github.com/shader-slang/slang/blob/master/examples/shader-object/main.cpp)なども参考にします。 + +`SLANG_RETURN_ON_FAIL`その他のヘルパーは使用しません。 +また、`DebugCallback`の設定や`IBlob`へのdiagnosticsの取得は行いません。 +ただしCOMオブジェクトを扱う際には、`Slang::ComPtr`を使用します。 + +gfxのCOMオブジェクトを与えるAPIの一部は、引数に受け取った参照に設定するものと、返り値に`ComPtr`を返すものがあります。 +本記事では、返り値で与えられるものを使用します。 +その際`auto`の使用は避け、型を明示的に指定します。 + +関数分割はせず、すべてmainに1連のフローとして記述します。 + + +## 実装 + +### フロー + +1. deviceの作成 +2. pipeline stateの作成 + 1. deviceからsessionを取得 + 2. sessionからshaderファイルをload + 3. sessionから、loadしたshaderファイルとEntoryPointを指定してcomposite component typeを作成 + 4. deviceから、composite component typeを使用してshader programを作成 + 5. deviceから、shader programを使用してpipeline stateを作成 +3. deviceからbuffer resourceをとviewを作成 +4. commnad bufferの作成 + 1. deviceからtransient resource heapを作成 + 2. transient resource heapからcommand bufferを作成 + 3. command bufferからcommand encoderを作成 + 4. encoderから、pipeline stateを使用してshader objectを作成 + 5. shader objectにbuffer resourceを設定 + 6. encoderから、dispatch数を指定 +5. command bufferの実行 + 1. deviceからcommand queueを作成 + 2. command queueからcommand bufferを実行 +6. 実行待機 +7. 結果の取得 + +目的順に大きくまとめると + +- Shaderの1回の実行内容をcommand bufferとして記述する必要がある +- command bufferの作成には、graphcis layerと対話するdevice, 実行するshaderを指定するpipeline state, 入出力データのbufferが必要 +- pipline stateとbufferの作成にはdeviceが必要 + +となり、順にdevice, pipeline state, buffer, command buffer, を作成し実行、という流れが必要になります。 + + +### Header + +ヘッダーは以下のようになります。 + +```cpp +#include +#include +#include +#include + +#include + +#include +#include + +using Slang::ComPtr; +using std::string; +``` + +composite component typeの作成に`vector`を使用していますが、ポインタ配列を使用することも可能です。 + +`string`と`iostream`は、出力のために使用しています。 + +### Deviceの作成 + +はじめに、`IDevice`オブジェクトを作成します。 +ここはGetting Startedと同様です。 +slang graphics layerとの対話は、この`IDevice`オブジェクトを介して行います。 + +`gfxCreateDevice()`関数を使用して、`IDevice`オブジェクトを作成します。 + +```cpp +ComPtr device; +gfx::IDevice::Desc deviceDesc = {}; +gfx::gfxCreateDevice(&deviceDesc, device.writeRef()); +``` + +Vulkanなどを使用する場合、`gfx::IDevice::Desc`の`deviceType`を指定します。[^5] + +### Pipeline Stateの作成 + +次いで、pipeline stateを作成します。 +Getting Startedで省略のあった部分です。 +pipeline stateには、実行されるshaderを設定します。 + +`IPipelineState`の作成には、`IShaderProgram`が必要です。 + +`IShaderProgram`は、shaderファイルをロードした`IModule`と、その中の`IEntryPoint`からなる`IComponentType`を使用して作成します。 + +`IModule`の取得と`IComponentType`の作成には、`ISession`を使用します。 + +そういうわけで、pipeline stateに指定するshaderを作成するために、まずはsessionを作成します。 +`device->getSlangSession()`を使用して、`ISession`オブジェクトを取得します。 + +```cpp +ComPtr session = device->getSlangSession(); +``` + +次に、sessionからshaderファイルをloadします。 +`session->loadModule()`を使用して、`IModule`オブジェクトを取得します。 + +```cpp +const char shaderPath[] = "hello-world.slang"; +ComPtr slangModule(session->loadModule(shaderPath)); +``` + +そして、loadしたshaderファイルからEntryPointを取得します。 + +`slangModule->findEntryPointByName()`を使用して、`IEntryPoint`オブジェクトを取得します。 + +```cpp +const char entryPointName[] = "computeMain"; +ComPtr entryPoint; +slangModule->findEntryPointByName(entryPointName, entryPoint.writeRef()); +``` + +これらをpipeline stateに設定するshader objectにするため、composite component typeにまとめます。 + +`session->createCompositeComponentType()`を使用して、`IComponentType`オブジェクトを取得します。 + +```cpp +std::vector componentTypes; +componentTypes.push_back(slangModule); +componentTypes.push_back(entryPoint); + +ComPtr composedProgram; +session->createCompositeComponentType( + componentTypes.data(), + componentTypes.size(), + composedProgram.writeRef()); +``` + +最後に、pipeline stateを作成します。 + +`device->createProgram()`を使用して、`IShaderProgram`オブジェクトを取得し、 +`device->createComputePipelineState()`を使用して、`IPipelineState`オブジェクトを取得します。 + +```cpp +gfx::IShaderProgram::Desc programDesc = {}; +programDesc.slangGlobalScope = composedProgram.get(); +ComPtr shaderProgram = device->createProgram(programDesc); + +gfx::ComputePipelineStateDesc pipelineDesc = {}; +pipelineDesc.program = shaderProgram; +ComPtr pipelineState = device->createComputePipelineState(pipelineDesc); +``` + +pipeline state作成全体のコードをまとめます。 + +
pipeline state作成コード + +```cpp +ComPtr session = device->getSlangSession(); + +const char shaderPath[] = "hello-world.slang"; +ComPtr slangModule(session->loadModule(shaderPath)); + +const char entryPointName[] = "computeMain"; +ComPtr entryPoint; +slangModule->findEntryPointByName(entryPointName, entryPoint.writeRef()); + +std::vector componentTypes; +componentTypes.push_back(slangModule); +componentTypes.push_back(entryPoint); + +ComPtr composedProgram; +session->createCompositeComponentType( + componentTypes.data(), + componentTypes.size(), + composedProgram.writeRef()); + +gfx::IShaderProgram::Desc programDesc = {}; +programDesc.slangGlobalScope = composedProgram.get(); +ComPtr shaderProgram = device->createProgram(programDesc); + +gfx::ComputePipelineStateDesc pipelineDesc = {}; +pipelineDesc.program = shaderProgram; +ComPtr pipelineState = device->createComputePipelineState(pipelineDesc); +``` + +
+ +## Bufferの作成 + +次に、bufferを作成します。 +今回は、Getting Startedの例に従い、bufferを用いてShaderのデータを受け渡します。 + +Shaderの内容が、input0配列とinput1配列の同一インデックスの値の和を、result配列の同じインデックスに格納するものであるため、ここでは適当にサイズ4のfloat配列を用意します。 + +bufferは、まず`IBufferResource`を作成し、それを用いて`IBufferView`を作成します。 +この`IBufferView`を、command bufferの作成時にshader objectに設定します。 + +```cpp +const int numberCount1 = 4; +float initialData1[] = {0.0f, 1.0f, 2.0f, 3.0f}; +gfx::IBufferResource::Desc bufferDesc1 = {}; +bufferDesc1.sizeInBytes = numberCount1 * sizeof(float); +bufferDesc1.format = gfx::Format::Unknown; +bufferDesc1.elementSize = sizeof(float); +bufferDesc1.defaultState = gfx::ResourceState::UnorderedAccess; +bufferDesc1.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); +ComPtr inputBuffer1 = device->createBufferResource(bufferDesc1, (void *)initialData1); + +gfx::IResourceView::Desc viewDesc1 = {}; +viewDesc1.type = gfx::IResourceView::Type::ShaderResource; +viewDesc1.format = gfx::Format::Unknown; +ComPtr buffer1View = device->createBufferView(inputBuffer1, nullptr, viewDesc1); +``` + +`device->createBufferView()`の第2引数はcounter bufferを設定します。 +詳しくないので説明は省きますが、使用しない場合は`nullptr`を指定すれば動作します。 + +残りのbufferも同様に作成します。 + +ただし、result配列は出力用のため、ResourceViewのtypeを書き込みできる`ResourceState::UnorderedAccess`に指定します。 + +```cpp +gfx::IResourceView::Desc viewDescResult = {}; +viewDescResult.type = gfx::IResourceView::Type::UnorderedAccess; +viewDescResult.format = gfx::Format::Unknown; +ComPtr resultView = device->createBufferView(resultBuffer, nullptr, viewDescResult); +``` + +buffer作成全体のコードをまとめます。 + +
buffer作成コード + +```cpp +// Create input buffer 0 +const int numberCount0 = 4; +float initialData0[] = {0.0f, 1.0f, 2.0f, 3.0f}; +gfx::IBufferResource::Desc bufferDesc0 = {}; +bufferDesc0.sizeInBytes = numberCount0 * sizeof(float); +bufferDesc0.format = gfx::Format::Unknown; +bufferDesc0.elementSize = sizeof(float); +bufferDesc0.defaultState = gfx::ResourceState::UnorderedAccess; +bufferDesc0.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); +ComPtr inputBuffer0 = device->createBufferResource(bufferDesc0, (void *)initialData0); + +gfx::IResourceView::Desc viewDesc0 = {}; +viewDesc0.type = gfx::IResourceView::Type::ShaderResource; +viewDesc0.format = gfx::Format::Unknown; +ComPtr buffer0View = device->createBufferView(inputBuffer0, nullptr, viewDesc0); + +// Create input buffer 1 +const int numberCount1 = 4; +float initialData1[] = {0.0f, 1.0f, 2.0f, 3.0f}; +gfx::IBufferResource::Desc bufferDesc1 = {}; +bufferDesc1.sizeInBytes = numberCount1 * sizeof(float); +bufferDesc1.format = gfx::Format::Unknown; +bufferDesc1.elementSize = sizeof(float); +bufferDesc1.defaultState = gfx::ResourceState::UnorderedAccess; +bufferDesc1.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); +ComPtr inputBuffer1 = device->createBufferResource(bufferDesc1, (void *)initialData1); + +gfx::IResourceView::Desc viewDesc1 = {}; +viewDesc1.type = gfx::IResourceView::Type::ShaderResource; +viewDesc1.format = gfx::Format::Unknown; +ComPtr buffer1View = device->createBufferView(inputBuffer1, nullptr, viewDesc1); + +// Create result buffer +const int numberCountResult = 4; +float initialDataResult[4] = {}; +gfx::IBufferResource::Desc bufferDescResult = {}; +bufferDescResult.sizeInBytes = numberCountResult * sizeof(float); +bufferDescResult.format = gfx::Format::Unknown; +bufferDescResult.elementSize = sizeof(float); +bufferDescResult.defaultState = gfx::ResourceState::UnorderedAccess; +bufferDescResult.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); +ComPtr resultBuffer = device->createBufferResource(bufferDescResult, (void *)initialDataResult); + +gfx::IResourceView::Desc viewDescResult = {}; +viewDescResult.type = gfx::IResourceView::Type::UnorderedAccess; +viewDescResult.format = gfx::Format::Unknown; +ComPtr resultView = device->createBufferView(resultBuffer, nullptr, viewDescResult); +``` + +
+ +## Command Bufferの作成 + +次に、command bufferを作成します。 + +`ICommandBuffer`の作成には、`ITransientResourceHeap`が必要です。 +constatnt bufferのサイズは適当に4096としています。 + +`device->createTransientResourceHeap()`を使用して、`ITransientResourceHeap`オブジェクトを作成します。 + +```cpp +gfx::ITransientResourceHeap::Desc transientHeapDesc = {}; +transientHeapDesc.constantBufferSize = 4096; +ComPtr gTransientHeap = device->createTransientResourceHeap(transientHeapDesc); + +ComPtr commandBuffer = gTransientHeap->createCommandBuffer(); +``` + +作成した`ICommandBuffer`から、`IComputeCommandEncoder`を作成し、encoderにpipeline stateをバインドして、`IShaderObject`を作成します。[^6] + +```cpp +gfx::IComputeCommandEncoder *encoder = commandBuffer->encodeComputeCommands(); +gfx::IShaderObject *rootObject = encoder->bindPipeline(pipelineState); +``` + +そして、shader objectにbuffer resourceのviewを設定します。 + +```cpp +rootObject->setResource(gfx::ShaderOffset{0, 0, 0}, buffer0View); +rootObject->setResource(gfx::ShaderOffset{0, 1, 0}, buffer1View); +rootObject->setResource(gfx::ShaderOffset{0, 2, 0}, resultView); +``` + +最後にshaderの実行数を指定し、encoderを終了、command bufferを閉じます。 + +```cpp +encoder->dispatchCompute(4, 1, 1); + +encoder->endEncoding(); +commandBuffer->close(); +``` + +command buffer作成全体のコードをまとめます。 + +
command buffer作成コード + +```cpp +gfx::ITransientResourceHeap::Desc transientHeapDesc = {}; +transientHeapDesc.constantBufferSize = 4096; +ComPtr gTransientHeap = device->createTransientResourceHeap(transientHeapDesc); + +ComPtr commandBuffer = gTransientHeap->createCommandBuffer(); + +gfx::IComputeCommandEncoder *encoder = commandBuffer->encodeComputeCommands(); +gfx::IShaderObject *rootObject = encoder->bindPipeline(pipelineState); + +rootObject->setResource(gfx::ShaderOffset{0, 0, 0}, buffer0View); +rootObject->setResource(gfx::ShaderOffset{0, 1, 0}, buffer1View); +rootObject->setResource(gfx::ShaderOffset{0, 2, 0}, resultView); + +encoder->dispatchCompute(4, 1, 1); + +encoder->endEncoding(); +commandBuffer->close(); +``` + +
+ +## Command Bufferの実行と結果の取得 + +次に、command bufferを実行します。 +deviceから`ICommandQueue`を作成し、queueにcommand bufferを実行させます。 + +```cpp +gfx::ICommandQueue::Desc queueDesc = {gfx::ICommandQueue::QueueType::Graphics}; +ComPtr gQueue = device->createCommandQueue(queueDesc); +gQueue->executeCommandBuffer(commandBuffer); +``` + +実行したら、実行が終わるまで待機します。 + +```cpp +gQueue->waitOnHost(); +``` + +最後に、result bufferから結果を取得します。 +`device->readBufferResource()`を使用して、`ISlangBlob`にresultのresource bufferを読み込みます。 + +```cpp +ComPtr resultBlob; +device->readBufferResource(resultBuffer, 0, sizeof(float) * 4, resultBlob.writeRef()); +float *resultData = (float *)resultBlob->getBufferPointer(); +``` + +command bufferの実行と結果取得全体のコードをまとめます。 + +
command bufferの実行と結果取得コード + +```cpp +gfx::ICommandQueue::Desc queueDesc = {gfx::ICommandQueue::QueueType::Graphics}; +ComPtr gQueue = device->createCommandQueue(queueDesc); +gQueue->executeCommandBuffer(commandBuffer); + +gQueue->waitOnHost(); + +ComPtr resultBlob; +device->readBufferResource(resultBuffer, 0, sizeof(float) * 4, resultBlob.writeRef()); +float *resultData = (float *)resultBlob->getBufferPointer(); +string resultString = "Result: \n"; +for (int i = 0; i < 4; i++) +{ + resultString += std::to_string(resultData[i]) + ", "; +} +std::cout << resultString << std::endl; +``` + +
+ +## 終わりに + +以上で、Slang Shaderをminimalに動かす方法を説明しました。 + +内容としては、実行に必要なオブジェクトの作成に必要なオブジェクトの作成に必要なオブジェクトの...と、オブジェクトを作成して引き渡していくだけのものでしたが、普段実行はUnity等に任せっきりにしている身としては、最小限必要なオブジェクトを探し当てるのに苦労しました。 + +普段C++を書きなれない身としては、CUDAやD3D12のようなAPIより分かりやすく感じました。[^7] + +現在筆者はDirectXでの実行しかできていませんが、他のデバイスでの実行も本来可能なはずですので、今後もう少しいろいろ試してみたいところです。 + +最後に、本記事の内容が、私のような初心者がSlang Shaderを動かす際の参考になれば幸いです。 + +## 全体コード + +コード全体は以下のようになります。 + +
全体コード + +```cpp +#include +#include +#include +#include + +#include + +#include +#include + +using Slang::ComPtr; +using std::string; + +int main() +{ + std::cout << "Starting..." << std::endl; + + std::cout << "Creating device..." << std::endl; + ComPtr device; + gfx::IDevice::Desc deviceDesc = {}; + gfx::gfxCreateDevice(&deviceDesc, device.writeRef()); + + std::cout << "Creating pipeline..." << std::endl; + ComPtr session = device->getSlangSession(); + + const char shaderPath[] = "hello-world.slang"; + ComPtr slangModule(session->loadModule(shaderPath)); + + const char entryPointName[] = "computeMain"; + ComPtr entryPoint; + slangModule->findEntryPointByName(entryPointName, entryPoint.writeRef()); + + std::vector componentTypes; + componentTypes.push_back(slangModule); + componentTypes.push_back(entryPoint); + + ComPtr composedProgram; + session->createCompositeComponentType( + componentTypes.data(), + componentTypes.size(), + composedProgram.writeRef()); + + gfx::IShaderProgram::Desc programDesc = {}; + programDesc.slangGlobalScope = composedProgram.get(); + ComPtr shaderProgram = device->createProgram(programDesc); + + gfx::ComputePipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram; + ComPtr pipelineState = device->createComputePipelineState(pipelineDesc); + + std::cout << "Creating buffers..." << std::endl; + // Create input buffer 0 + const int numberCount0 = 4; + float initialData0[] = {0.0f, 1.0f, 2.0f, 3.0f}; + gfx::IBufferResource::Desc bufferDesc0 = {}; + bufferDesc0.sizeInBytes = numberCount0 * sizeof(float); + bufferDesc0.format = gfx::Format::Unknown; + bufferDesc0.elementSize = sizeof(float); + bufferDesc0.defaultState = gfx::ResourceState::UnorderedAccess; + bufferDesc0.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); + ComPtr inputBuffer0 = device->createBufferResource(bufferDesc0, (void *)initialData0); + + gfx::IResourceView::Desc viewDesc0 = {}; + viewDesc0.type = gfx::IResourceView::Type::ShaderResource; + viewDesc0.format = gfx::Format::Unknown; + ComPtr buffer0View = device->createBufferView(inputBuffer0, nullptr, viewDesc0); + + // Create input buffer 1 + const int numberCount1 = 4; + float initialData1[] = {0.0f, 1.0f, 2.0f, 3.0f}; + gfx::IBufferResource::Desc bufferDesc1 = {}; + bufferDesc1.sizeInBytes = numberCount1 * sizeof(float); + bufferDesc1.format = gfx::Format::Unknown; + bufferDesc1.elementSize = sizeof(float); + bufferDesc1.defaultState = gfx::ResourceState::UnorderedAccess; + bufferDesc1.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); + ComPtr inputBuffer1 = device->createBufferResource(bufferDesc1, (void *)initialData1); + + gfx::IResourceView::Desc viewDesc1 = {}; + viewDesc1.type = gfx::IResourceView::Type::ShaderResource; + viewDesc1.format = gfx::Format::Unknown; + ComPtr buffer1View = device->createBufferView(inputBuffer1, nullptr, viewDesc1); + + // Create result buffer + const int numberCountResult = 4; + float initialDataResult[4] = {}; + gfx::IBufferResource::Desc bufferDescResult = {}; + bufferDescResult.sizeInBytes = numberCountResult * sizeof(float); + bufferDescResult.format = gfx::Format::Unknown; + bufferDescResult.elementSize = sizeof(float); + bufferDescResult.defaultState = gfx::ResourceState::UnorderedAccess; + bufferDescResult.allowedStates = gfx::ResourceStateSet(gfx::ResourceState::UnorderedAccess, + gfx::ResourceState::ShaderResource); + ComPtr resultBuffer = device->createBufferResource(bufferDescResult, (void *)initialDataResult); + + gfx::IResourceView::Desc viewDescResult = {}; + viewDescResult.type = gfx::IResourceView::Type::UnorderedAccess; + viewDescResult.format = gfx::Format::Unknown; + ComPtr resultView = device->createBufferView(resultBuffer, nullptr, viewDescResult); + + std::cout << "Creating commands..." << std::endl; + gfx::ITransientResourceHeap::Desc transientHeapDesc = {}; + transientHeapDesc.constantBufferSize = 4096; + ComPtr gTransientHeap = device->createTransientResourceHeap(transientHeapDesc); + + ComPtr commandBuffer = gTransientHeap->createCommandBuffer(); + + gfx::IComputeCommandEncoder *encoder = commandBuffer->encodeComputeCommands(); + gfx::IShaderObject *rootObject = encoder->bindPipeline(pipelineState); + + rootObject->setResource(gfx::ShaderOffset{0, 0, 0}, buffer0View); + rootObject->setResource(gfx::ShaderOffset{0, 1, 0}, buffer1View); + rootObject->setResource(gfx::ShaderOffset{0, 2, 0}, resultView); + + std::cout << "Dispatching..." << std::endl; + encoder->dispatchCompute(4, 1, 1); + + encoder->endEncoding(); + commandBuffer->close(); + + std::cout << "Executing command buffer..." << std::endl; + gfx::ICommandQueue::Desc queueDesc = {gfx::ICommandQueue::QueueType::Graphics}; + ComPtr gQueue = device->createCommandQueue(queueDesc); + gQueue->executeCommandBuffer(commandBuffer); + + std::cout << "Waiting on host." << std::endl; + gQueue->waitOnHost(); + std::cout << "Done." << std::endl; + + std::cout << "Reading result buffer..." << std::endl; + ComPtr resultBlob; + device->readBufferResource(resultBuffer, 0, sizeof(float) * 4, resultBlob.writeRef()); + float *resultData = (float *)resultBlob->getBufferPointer(); + string resultString = "Result: \n"; + for (int i = 0; i < 4; i++) + { + resultString += std::to_string(resultData[i]) + ", "; + } + std::cout << resultString << std::endl; +} +``` + +
+ +## 参考文献 + +- [Slang Shader Language](https://shader-slang.com/) +- [shader-slang/slang](https://github.com/shader-slang/slang) +- [Getting Started with Slang](https://shader-slang.com/slang/user-guide/get-started.html) +- [Getting Started with Slang Graphics Layer](https://shader-slang.com/slang/gfx-user-guide/01-getting-started.html) +- [Slang Graphics Layer (GFX) User Guide](https://shader-slang.com/slang/gfx-user-guide/index.html) +- [Using the Reflection API](https://shader-slang.com/slang/user-guide/reflection.html) +- [Using the Compilation API](https://shader-slang.com/slang/user-guide/compiling.html#using-the-compilation-api) +- [ComputeShaderを触ってみる その1 ~スレッド編~](https://edom18.hateblo.jp/entry/2017/05/10/083421) +- [ComputeShaderを触ってみる その2 ~バッファ・テクスチャ編~](https://edom18.hateblo.jp/entry/2017/10/07/171147) +- [DirectXの話 第108回](https://sites.google.com/site/monshonosuana/directx%E3%81%AE%E8%A9%B1/directx%E3%81%AE%E8%A9%B1-%E7%AC%AC108%E5%9B%9E) + +[^1]: 大変遅れまして、申し訳ございません。 + +[^2]: はじめはそこまで丁寧な例が存在すると思っていなかったので、見つけるのに大変苦労しました。 + +[^3]: 以前のバージョンでは、Windowsかつgccでのコンパイル時にマクロのredefinition警告が出ていましたが、v2024.15.1で修正されたようです。 + +[^4]: 何故か実行自体は`dxil.dll`無しでも出来ました。このあたりあまり詳しくないです。 + +[^5]: 他のデバイスの使用は使用方法はまりよく分かっていません。Vulkanの場合は`gfx::DeviceType::Vulkan`の指定で動作しましたが、他のデバイスの場合はdevice typeの指定だけではsegmetation faultが発生しました。単に私の知識が欠如しているだけの気がします。 + +[^6]: なお、ここで得られるencoderとrootObjectは、command bufferのrelease時に自動的に解放されます。 + +[^7]: 何がそう感じさせたかは不明。抽象化のされ方が良かったのだろうか? diff --git a/src/content/tags/slang-shader.json b/src/content/tags/slang-shader.json new file mode 100644 index 0000000..edca28e --- /dev/null +++ b/src/content/tags/slang-shader.json @@ -0,0 +1,14 @@ +{ + "name": "Slang Shader", + "description": "Slangは、モダンで高度な機能を備えたShader言語、およびそれによって記述されたShaderのコンパイラです。独自のグラフィックス・レイヤーを持ち、単一のShaderコードを多様なターゲットで実行可能にします。", + "links": [ + { + "url": "https://shader-slang.com/", + "text": "The Slang Shading Language - 公式サイト" + }, + { + "url": "https://github.com/shader-slang/slang", + "text": "Slang - GitHub" + } + ] +}