Slang Shaderをminimalに動かす
はじめに
本記事は、OUCC Advent Calendar 2024 の4日目に投稿されるはずだった記事です。1
Shaderを書く際には、通常その実行環境に応じて異なる言語で記述する必要があります。 Slang は、様々な環境向けのコードを単一のコードから生成できるシェーディング言語です。 また、Slang Graphics Layer (GFX) を用いることで、各グラフィックスAPI向けのコードを書かずにslangのshaderを実行することが可能です。 大変便利なものですが、現時点で試しに使ってみようとした際に以下のようなハードルが存在します。
- 日本語での情報が少ない
- 実行に関する公式のガイドでコードの一部が省略されている
- example の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.173
slangはGitHubのリリースページ からダウンロードできます。
実行には少なくともbin内のslang.dll
とgfx.dll
が必要です。
また、slangのgfxは実行時にどのグラフィックスAPIを使用するか自動的に決定できます。
当環境ではDrectX 12が使用されました。必要に応じてDirectX Shader CompilerのRelease からダウンロードしてください。
実行には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 にも記載されている、hello-worldのslang shaderコードを実行することを目標とします。
// hello-world.slang
StructuredBuffer<float> buffer0;
StructuredBuffer<float> buffer1;
RWStructuredBuffer<float> 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 の内容に準拠して進めます。
ただしオブジェクトの作成を、最初の使用の直前に行うように変更します。
また一部はexample/shader-object
なども参考にします。
SLANG_RETURN_ON_FAIL
その他のヘルパーは使用しません。
また、DebugCallback
の設定やIBlob
へのdiagnosticsの取得は行いません。
ただしCOMオブジェクトを扱う際には、Slang::ComPtr
を使用します。
gfxのCOMオブジェクトを与えるAPIの一部は、引数に受け取った参照に設定するものと、返り値にComPtr
を返すものがあります。
本記事では、返り値で与えられるものを使用します。
その際auto
の使用は避け、型を明示的に指定します。
関数分割はせず、すべてmainに1連のフローとして記述します。
実装
フロー
- deviceの作成
- pipeline stateの作成
- deviceからsessionを取得
- sessionからshaderファイルをload
- sessionから、loadしたshaderファイルとEntoryPointを指定してcomposite component typeを作成
- deviceから、composite component typeを使用してshader programを作成
- deviceから、shader programを使用してpipeline stateを作成
- deviceからbuffer resourceをとviewを作成
- commnad bufferの作成
- deviceからtransient resource heapを作成
- transient resource heapからcommand bufferを作成
- command bufferからcommand encoderを作成
- encoderから、pipeline stateを使用してshader objectを作成
- shader objectにbuffer resourceを設定
- encoderから、dispatch数を指定
- command bufferの実行
- deviceからcommand queueを作成
- command queueからcommand bufferを実行
- 実行待機
- 結果の取得
目的順に大きくまとめると
- Shaderの1回の実行内容をcommand bufferとして記述する必要がある
- command bufferの作成には、graphcis layerと対話するdevice, 実行するshaderを指定するpipeline state, 入出力データのbufferが必要
- pipline stateとbufferの作成にはdeviceが必要
となり、順にdevice, pipeline state, buffer, command buffer, を作成し実行、という流れが必要になります。
Header
ヘッダーは以下のようになります。
#include <slang.h>
#include <slang-gfx.h>
#include <slang-com-ptr.h>
#include <slang-com-helper.h>
#include <vector>
#include <string>
#include <iostream>
using Slang::ComPtr;
using std::string;
composite component typeの作成にvector
を使用していますが、ポインタ配列を使用することも可能です。
string
とiostream
は、出力のために使用しています。
Deviceの作成
はじめに、IDevice
オブジェクトを作成します。
ここはGetting Startedと同様です。
slang graphics layerとの対話は、このIDevice
オブジェクトを介して行います。
gfxCreateDevice()
関数を使用して、IDevice
オブジェクトを作成します。
ComPtr<gfx::IDevice> 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
オブジェクトを取得します。
ComPtr<slang::ISession> session = device->getSlangSession();
次に、sessionからshaderファイルをloadします。
session->loadModule()
を使用して、IModule
オブジェクトを取得します。
const char shaderPath[] = "hello-world.slang";
ComPtr<slang::IModule> slangModule(session->loadModule(shaderPath));
そして、loadしたshaderファイルからEntryPointを取得します。
slangModule->findEntryPointByName()
を使用して、IEntryPoint
オブジェクトを取得します。
const char entryPointName[] = "computeMain";
ComPtr<slang::IEntryPoint> entryPoint;
slangModule->findEntryPointByName(entryPointName, entryPoint.writeRef());
これらをpipeline stateに設定するshader objectにするため、composite component typeにまとめます。
session->createCompositeComponentType()
を使用して、IComponentType
オブジェクトを取得します。
std::vector<slang::IComponentType *> componentTypes;
componentTypes.push_back(slangModule);
componentTypes.push_back(entryPoint);
ComPtr<slang::IComponentType> composedProgram;
session->createCompositeComponentType(
componentTypes.data(),
componentTypes.size(),
composedProgram.writeRef());
最後に、pipeline stateを作成します。
device->createProgram()
を使用して、IShaderProgram
オブジェクトを取得し、
device->createComputePipelineState()
を使用して、IPipelineState
オブジェクトを取得します。
gfx::IShaderProgram::Desc programDesc = {};
programDesc.slangGlobalScope = composedProgram.get();
ComPtr<gfx::IShaderProgram> shaderProgram = device->createProgram(programDesc);
gfx::ComputePipelineStateDesc pipelineDesc = {};
pipelineDesc.program = shaderProgram;
ComPtr<gfx::IPipelineState> pipelineState = device->createComputePipelineState(pipelineDesc);
pipeline state作成全体のコードをまとめます。
pipeline state作成コード
ComPtr<slang::ISession> session = device->getSlangSession();
const char shaderPath[] = "hello-world.slang";
ComPtr<slang::IModule> slangModule(session->loadModule(shaderPath));
const char entryPointName[] = "computeMain";
ComPtr<slang::IEntryPoint> entryPoint;
slangModule->findEntryPointByName(entryPointName, entryPoint.writeRef());
std::vector<slang::IComponentType *> componentTypes;
componentTypes.push_back(slangModule);
componentTypes.push_back(entryPoint);
ComPtr<slang::IComponentType> composedProgram;
session->createCompositeComponentType(
componentTypes.data(),
componentTypes.size(),
composedProgram.writeRef());
gfx::IShaderProgram::Desc programDesc = {};
programDesc.slangGlobalScope = composedProgram.get();
ComPtr<gfx::IShaderProgram> shaderProgram = device->createProgram(programDesc);
gfx::ComputePipelineStateDesc pipelineDesc = {};
pipelineDesc.program = shaderProgram;
ComPtr<gfx::IPipelineState> pipelineState = device->createComputePipelineState(pipelineDesc);
Bufferの作成
次に、bufferを作成します。 今回は、Getting Startedの例に従い、bufferを用いてShaderのデータを受け渡します。
Shaderの内容が、input0配列とinput1配列の同一インデックスの値の和を、result配列の同じインデックスに格納するものであるため、ここでは適当にサイズ4のfloat配列を用意します。
bufferは、まずIBufferResource
を作成し、それを用いてIBufferView
を作成します。
このIBufferView
を、command bufferの作成時にshader objectに設定します。
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<gfx::IBufferResource> inputBuffer1 = device->createBufferResource(bufferDesc1, (void *)initialData1);
gfx::IResourceView::Desc viewDesc1 = {};
viewDesc1.type = gfx::IResourceView::Type::ShaderResource;
viewDesc1.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> buffer1View = device->createBufferView(inputBuffer1, nullptr, viewDesc1);
device->createBufferView()
の第2引数はcounter bufferを設定します。
詳しくないので説明は省きますが、使用しない場合はnullptr
を指定すれば動作します。
残りのbufferも同様に作成します。
ただし、result配列は出力用のため、ResourceViewのtypeを書き込みできるResourceState::UnorderedAccess
に指定します。
gfx::IResourceView::Desc viewDescResult = {};
viewDescResult.type = gfx::IResourceView::Type::UnorderedAccess;
viewDescResult.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> resultView = device->createBufferView(resultBuffer, nullptr, viewDescResult);
buffer作成全体のコードをまとめます。
buffer作成コード
// 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<gfx::IBufferResource> inputBuffer0 = device->createBufferResource(bufferDesc0, (void *)initialData0);
gfx::IResourceView::Desc viewDesc0 = {};
viewDesc0.type = gfx::IResourceView::Type::ShaderResource;
viewDesc0.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> 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<gfx::IBufferResource> inputBuffer1 = device->createBufferResource(bufferDesc1, (void *)initialData1);
gfx::IResourceView::Desc viewDesc1 = {};
viewDesc1.type = gfx::IResourceView::Type::ShaderResource;
viewDesc1.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> 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<gfx::IBufferResource> resultBuffer = device->createBufferResource(bufferDescResult, (void *)initialDataResult);
gfx::IResourceView::Desc viewDescResult = {};
viewDescResult.type = gfx::IResourceView::Type::UnorderedAccess;
viewDescResult.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> resultView = device->createBufferView(resultBuffer, nullptr, viewDescResult);
Command Bufferの作成
次に、command bufferを作成します。
ICommandBuffer
の作成には、ITransientResourceHeap
が必要です。
constatnt bufferのサイズは適当に4096としています。
device->createTransientResourceHeap()
を使用して、ITransientResourceHeap
オブジェクトを作成します。
gfx::ITransientResourceHeap::Desc transientHeapDesc = {};
transientHeapDesc.constantBufferSize = 4096;
ComPtr<gfx::ITransientResourceHeap> gTransientHeap = device->createTransientResourceHeap(transientHeapDesc);
ComPtr<gfx::ICommandBuffer> commandBuffer = gTransientHeap->createCommandBuffer();
作成したICommandBuffer
から、IComputeCommandEncoder
を作成し、encoderにpipeline stateをバインドして、IShaderObject
を作成します。6
gfx::IComputeCommandEncoder *encoder = commandBuffer->encodeComputeCommands();
gfx::IShaderObject *rootObject = encoder->bindPipeline(pipelineState);
そして、shader objectにbuffer resourceのviewを設定します。
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を閉じます。
encoder->dispatchCompute(4, 1, 1);
encoder->endEncoding();
commandBuffer->close();
command buffer作成全体のコードをまとめます。
command buffer作成コード
gfx::ITransientResourceHeap::Desc transientHeapDesc = {};
transientHeapDesc.constantBufferSize = 4096;
ComPtr<gfx::ITransientResourceHeap> gTransientHeap = device->createTransientResourceHeap(transientHeapDesc);
ComPtr<gfx::ICommandBuffer> 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を実行させます。
gfx::ICommandQueue::Desc queueDesc = {gfx::ICommandQueue::QueueType::Graphics};
ComPtr<gfx::ICommandQueue> gQueue = device->createCommandQueue(queueDesc);
gQueue->executeCommandBuffer(commandBuffer);
実行したら、実行が終わるまで待機します。
gQueue->waitOnHost();
最後に、result bufferから結果を取得します。
device->readBufferResource()
を使用して、ISlangBlob
にresultのresource bufferを読み込みます。
ComPtr<ISlangBlob> resultBlob;
device->readBufferResource(resultBuffer, 0, sizeof(float) * 4, resultBlob.writeRef());
float *resultData = (float *)resultBlob->getBufferPointer();
command bufferの実行と結果取得全体のコードをまとめます。
command bufferの実行と結果取得コード
gfx::ICommandQueue::Desc queueDesc = {gfx::ICommandQueue::QueueType::Graphics};
ComPtr<gfx::ICommandQueue> gQueue = device->createCommandQueue(queueDesc);
gQueue->executeCommandBuffer(commandBuffer);
gQueue->waitOnHost();
ComPtr<ISlangBlob> 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を動かす際の参考になれば幸いです。
全体コード
コード全体は以下のようになります。
全体コード
#include <slang.h>
#include <slang-gfx.h>
#include <slang-com-ptr.h>
#include <slang-com-helper.h>
#include <vector>
#include <string>
#include <iostream>
using Slang::ComPtr;
using std::string;
int main()
{
std::cout << "Starting..." << std::endl;
std::cout << "Creating device..." << std::endl;
ComPtr<gfx::IDevice> device;
gfx::IDevice::Desc deviceDesc = {};
gfx::gfxCreateDevice(&deviceDesc, device.writeRef());
std::cout << "Creating pipeline..." << std::endl;
ComPtr<slang::ISession> session = device->getSlangSession();
const char shaderPath[] = "hello-world.slang";
ComPtr<slang::IModule> slangModule(session->loadModule(shaderPath));
const char entryPointName[] = "computeMain";
ComPtr<slang::IEntryPoint> entryPoint;
slangModule->findEntryPointByName(entryPointName, entryPoint.writeRef());
std::vector<slang::IComponentType *> componentTypes;
componentTypes.push_back(slangModule);
componentTypes.push_back(entryPoint);
ComPtr<slang::IComponentType> composedProgram;
session->createCompositeComponentType(
componentTypes.data(),
componentTypes.size(),
composedProgram.writeRef());
gfx::IShaderProgram::Desc programDesc = {};
programDesc.slangGlobalScope = composedProgram.get();
ComPtr<gfx::IShaderProgram> shaderProgram = device->createProgram(programDesc);
gfx::ComputePipelineStateDesc pipelineDesc = {};
pipelineDesc.program = shaderProgram;
ComPtr<gfx::IPipelineState> 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<gfx::IBufferResource> inputBuffer0 = device->createBufferResource(bufferDesc0, (void *)initialData0);
gfx::IResourceView::Desc viewDesc0 = {};
viewDesc0.type = gfx::IResourceView::Type::ShaderResource;
viewDesc0.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> 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<gfx::IBufferResource> inputBuffer1 = device->createBufferResource(bufferDesc1, (void *)initialData1);
gfx::IResourceView::Desc viewDesc1 = {};
viewDesc1.type = gfx::IResourceView::Type::ShaderResource;
viewDesc1.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> 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<gfx::IBufferResource> resultBuffer = device->createBufferResource(bufferDescResult, (void *)initialDataResult);
gfx::IResourceView::Desc viewDescResult = {};
viewDescResult.type = gfx::IResourceView::Type::UnorderedAccess;
viewDescResult.format = gfx::Format::Unknown;
ComPtr<gfx::IResourceView> resultView = device->createBufferView(resultBuffer, nullptr, viewDescResult);
std::cout << "Creating commands..." << std::endl;
gfx::ITransientResourceHeap::Desc transientHeapDesc = {};
transientHeapDesc.constantBufferSize = 4096;
ComPtr<gfx::ITransientResourceHeap> gTransientHeap = device->createTransientResourceHeap(transientHeapDesc);
ComPtr<gfx::ICommandBuffer> 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<gfx::ICommandQueue> 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<ISlangBlob> 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
- shader-slang/slang
- Getting Started with Slang
- Getting Started with Slang Graphics Layer
- Slang Graphics Layer (GFX) User Guide
- Using the Reflection API
- Using the Compilation API
- ComputeShaderを触ってみる その1 ~スレッド編~
- ComputeShaderを触ってみる その2 ~バッファ・テクスチャ編~
- DirectXの話 第108回
Footnotes
-
大変遅れまして、申し訳ございません。 ↩
-
はじめはそこまで丁寧な例が存在すると思っていなかったので、見つけるのに大変苦労しました。 ↩
-
以前のバージョンでは、Windowsかつgccでのコンパイル時にマクロのredefinition警告が出ていましたが、v2024.15.1で修正されたようです。 ↩
-
何故か実行自体は
dxil.dll
無しでも出来ました。このあたりあまり詳しくないです。 ↩ -
他のデバイスの使用は使用方法はまりよく分かっていません。Vulkanの場合は
gfx::DeviceType::Vulkan
の指定で動作しましたが、他のデバイスの場合はdevice typeの指定だけではsegmetation faultが発生しました。単に私の知識が欠如しているだけの気がします。 ↩ -
なお、ここで得られるencoderとrootObjectは、command bufferのrelease時に自動的に解放されます。 ↩
-
何がそう感じさせたかは不明。抽象化のされ方が良かったのだろうか? ↩