BLOG

記事一覧 タグ一覧

Slang Shaderをminimalに動かす

投稿日:

Slang Shaderをminimalに動かす

はじめに

本記事は、OUCC Advent Calendar 2024 の4日目に投稿されるはずだった記事です。1

Shaderを書く際には、通常その実行環境に応じて異なる言語で記述する必要があります。 Slang は、様々な環境向けのコードを単一のコードから生成できるシェーディング言語です。 また、Slang Graphics Layer (GFX) を用いることで、各グラフィックスAPI向けのコードを書かずにslangのshaderを実行することが可能です。 大変便利なものですが、現時点で試しに使ってみようとした際に以下のようなハードルが存在します。

  • 日本語での情報が少ない
  • 実行に関する公式のガイドでコードの一部が省略されている
  • examplehello-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.dllgfx.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連のフローとして記述します。

実装

フロー

  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, を作成し実行、という流れが必要になります。

ヘッダーは以下のようになります。

#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を使用していますが、ポインタ配列を使用することも可能です。

stringiostreamは、出力のために使用しています。

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::DescdeviceTypeを指定します。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;
}

参考文献

Footnotes

  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. 何がそう感じさせたかは不明。抽象化のされ方が良かったのだろうか?