🌋

【Vulkan】BufferDeviceAddressを使ってデータアクセスを楽にしたい

2022/05/01に公開

はじめに

Vulkan では、通常のバインディングとは別に Buffer Device Address という機能でバッファにアクセスすることができます。これは、ポインタと似たような機能です。

特に、レイトレーシングパイプラインでは、頂点データやインデックスデータをバッファに格納しておき、 Buffer Device Address を使ってシェーダからアクセスすることが多いです。ユーザ定義の構造体にアクセスする際にも便利です。

この記事では、Buffer Device Address を使ってChitシェーダからヒットしたメッシュの頂点バッファとインデックスバッファにアクセスし、頂点位置を取得する方法を紹介します。

GLSL準備

GLSL側では以下の準備をします。

  • 必要な拡張機能を有効化する
  • 必要なアドレスを持たせる構造体 MeshAddress の配列を受け取る
  • buffer_reference を使って頂点配列を用意する
sample.rchit
#version 460
#extension GL_EXT_ray_tracing : enable
#extension GL_EXT_scalar_block_layout : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_buffer_reference2 : require

struct MeshAddress
{
    uint64_t vertices;
    uint64_t indices;
};

struct Vertex
{
    vec3 pos;
};

layout(binding = 0) buffer Addresses { MeshAddress addresses[]; };
layout(buffer_reference, scalar) buffer Vertices { Vertex v[]; };
layout(buffer_reference, scalar) buffer Indices { uvec3 i[]; };

Vulkan準備

Vulkan側では以下の準備をします。

  • 必要なFeatureを有効化する
  • vk::DeviceAddress を含む BufferAddress 構造体を作成する
  • 参照したいバッファのアドレスを入力する
  • BufferAddress 配列のバッファを作る
main.cpp
void createDevice()
{
    // Featuresを有効化する
    vk::PhysicalDeviceFeatures deviceFeatures;
    deviceFeatures.shaderInt64 = true;

    vk::PhysicalDeviceBufferDeviceAddressFeatures addressFeatures;
    addressFeatures.bufferDeviceAddress = true;

    vk::PhysicalDeviceScalarBlockLayoutFeatures scalarBlockFeatures;
    scalarBlockFeatures.scalarBlockLayout = true;

    // ...
}

struct BufferAddress
{
    vk::DeviceAddress vertices;
    vk::DeviceAddress indices;
};

void prepare(){
    // バッファにアドレスを持たせる
    std::vector<BufferAddress> addresses;
    for(auto&& mesh: meshes){
    BufferAddress address;
    address.vertices = mesh.vertexBuffer.deviceAddress;
    address.indices = mesh.indexBuffer.deviceAddress;
    addresses.push_back(address);
    }

    // バッファを作成
    addressBuffer = createBuffer(
        sizeof(BufferAddress) * addresses.size(), 
        vk::BufferUsageFlagBits::eStorageBuffer |
        vk::BufferUsageFlagBits::eShaderDeviceAddress);

    descSet.update("Addresses", addressBuffer);
}

@MatchaChoco010さんの助言により、vk::PhysicalDeviceScalarBlockLayoutFeaturesを追加しました。ありがとうございます。

GLSLで使う

sample.rchit
// ヒットしたメッシュの頂点バッファとインデックスバッファのアドレスを取得する
MeshAddress address = addresses[gl_InstanceID];

// バッファを参照する
Vertices vertices = Vertices(address.vertices);
Indices indices = Indices(address.indices);

// 頂点を取得する
uvec3 index = indices.i[gl_PrimitiveID];
Vertex v0 = vertices.v[index.x];
Vertex v1 = vertices.v[index.y];
Vertex v2 = vertices.v[index.z];

こんな感じでキレイに書けます。おわりです。

GitHubで編集を提案

Discussion