Skip to content

Latest commit

 

History

History
307 lines (248 loc) · 12.9 KB

sparse_resources.adoc

File metadata and controls

307 lines (248 loc) · 12.9 KB

スパースリソース(Sparse Resources)

Vulkan のスパースリソースは、VkBufferVkImage オブジェクトを作成する方法で、1つまたは複数の VkDeviceMemory に非連続的にバインドすることができます。スパースリソースにはさまざまな側面や機能があり、仕様書ではそれらがうまく説明されています。実装ガイドラインに記載されているように、ほとんどの実装では、スパースリソースを使用してメモリの直線的な仮想アドレス範囲をアプリケーションに公開する一方で、バインド時には各スパースブロックを物理ページにマッピングします。

スパースメモリのバインド

通常のリソースが vkBindBufferMemory()vkBindImageMemory() を呼び出すのとは異なり、スパースメモリは キュー操作 vkQueueBindSparse() を介してバインドされます。この方法の主な利点は、アプリケーションがそのライフタイムを通じてメモリをスパースリソースに再バインドできることです。

この際、アプリケーション側でいくつか配慮が必要です。アプリケーションは、他のキューがバインディングの変更と同時にメモリの範囲をアクセスしないことを保証するために、同期プリミティブを使用しなければなりません。また、vkDeviceMemory オブジェクトを vkFreeMemory() で解放しても、メモリオブジェクトにバインドされたリソース(またはリソース領域)はアンバインドされません 。アプリケーションは、解放されたメモリにバインドされたリソースにアクセスしてはいけません。

スパースバッファ

以下の例は、スパースな VkBuffer がメモリ内でどのように見えるかを視覚的に示します。なお、必須ではありませんが、ほとんどの実装では VkBuffer に 64 KB のスパースなブロックサイズを使用します (実際のサイズは VkMemoryRequirements::alignment で返されます)。

256 KBの VkBuffer があって、アプリケーションが別々に更新したい3つの部分があるとします。

  • セクション A - 64 KB

  • セクション B - 128 KB

  • セクション C - 64 KB

以下は、アプリケーションが VkBuffer をどのように捉えるかを示しています。

sparse_resources_buffer.png

スパースイメージ

Mip Tail Region

スパースイメージを使ってミップレベルを個別に更新すると、mip tail regionのような結果になります。仕様書の図では、さまざまな例が記載されています。

スパースリソースの基本的な例

以下の例では、スパースイメージの基本的な作成方法と物理メモリへのバインド方法を説明します。

この基本的な例では、通常の VkImage オブジェクトを作成しますが、細かいメモリ割り当てを使用して、複数のメモリ範囲でリソースを保持します。

VkDevice                device;
VkQueue                 queue;
VkImage                 sparseImage;
VkAllocationCallbacks*  pAllocator = NULL;
VkMemoryRequirements    memoryRequirements = {};
VkDeviceSize            offset = 0;
VkSparseMemoryBind      binds[MAX_CHUNKS] = {}; // MAX_CHUNKS は Vulkan の一部ではない
uint32_t                bindCount = 0;

// ...

// イメージオブジェクトを割り当てる
const VkImageCreateInfo sparseImageInfo =
{
    VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,        // sType
    NULL,                                       // pNext
    VK_IMAGE_CREATE_SPARSE_BINDING_BIT | ...,   // flags
    ...
};
vkCreateImage(device, &sparseImageInfo, pAllocator, &sparseImage);

// メモリ要件を取得する
vkGetImageMemoryRequirements(
    device,
    sparseImage,
    &memoryRequirements);

// 複数の VkDeviceMemory プールから利用可能なメモリ範囲を見つけ、
// 細かくメモリをバインドする
// (これは説明用。パフォーマンスのためには最適化可能)
while (memoryRequirements.size && bindCount < MAX_CHUNKS)
{
    VkSparseMemoryBind* pBind = &binds[bindCount];
    pBind->resourceOffset = offset;

    AllocateOrGetMemoryRange(
        device,
        &memoryRequirements,
        &pBind->memory,
        &pBind->memoryOffset,
        &pBind->size);

    // メモリ範囲はアライメントの倍数のサイズでなければならない
    assert(IsMultiple(pBind->size, memoryRequirements.alignment));
    assert(IsMultiple(pBind->memoryOffset, memoryRequirements.alignment));

    memoryRequirements.size -= pBind->size;
    offset                  += pBind->size;
    bindCount++;
}

// イメージ全体にメモリ割り当てが完了していることを確認
if (memoryRequirements.size)
{
    // エラー条件 - チャンクが多すぎる
}

const VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo =
{
    sparseImage,                                // image
    bindCount,                                  // bindCount
    binds                                       // pBinds
};

const VkBindSparseInfo bindSparseInfo =
{
    VK_STRUCTURE_TYPE_BIND_SPARSE_INFO,         // sType
    NULL,                                       // pNext
    ...
    1,                                          // imageOpaqueBindCount
    &opaqueBindInfo,                            // pImageOpaqueBinds
    ...
};

// vkQueueBindSparse はキューオブジェクトごとに外部で同期している
AcquireQueueOwnership(queue);

// 実際にメモリをバインドする
vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE);

ReleaseQueueOwnership(queue);

高度なスパースリソース

より高度な例では、カラーアタッチメント/テクスチャイメージの配列を作成し、LOD ゼロと必要なメタデータのみを物理メモリにバインドします。

VkDevice                            device;
VkQueue                             queue;
VkImage                             sparseImage;
VkAllocationCallbacks*              pAllocator = NULL;
VkMemoryRequirements                memoryRequirements = {};
uint32_t                            sparseRequirementsCount = 0;
VkSparseImageMemoryRequirements*    pSparseReqs = NULL;
VkSparseMemoryBind                  binds[MY_IMAGE_ARRAY_SIZE] = {};
VkSparseImageMemoryBind             imageBinds[MY_IMAGE_ARRAY_SIZE] = {};
uint32_t                            bindCount = 0;

// イメージオブジェクトを割り当てる(レンダリング可能なものとサンプル可能なものの両方)
const VkImageCreateInfo sparseImageInfo =
{
    VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,        // sType
    NULL,                                       // pNext
    VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT | ..., // flags
    ...
    VK_FORMAT_R8G8B8A8_UNORM,                   // format
    ...
    MY_IMAGE_ARRAY_SIZE,                        // arrayLayers
    ...
    VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
    VK_IMAGE_USAGE_SAMPLED_BIT,                 // usage
    ...
};
vkCreateImage(device, &sparseImageInfo, pAllocator, &sparseImage);

// メモリ要件を取得する
vkGetImageMemoryRequirements(
    device,
    sparseImage,
    &memoryRequirements);

// スパースイメージのアスペクトプロパティを取得
vkGetImageSparseMemoryRequirements(
    device,
    sparseImage,
    &sparseRequirementsCount,
    NULL);

pSparseReqs = (VkSparseImageMemoryRequirements*)
    malloc(sparseRequirementsCount * sizeof(VkSparseImageMemoryRequirements));

vkGetImageSparseMemoryRequirements(
    device,
    sparseImage,
    &sparseRequirementsCount,
    pSparseReqs);

// LOD レベル0と必要なメタデータをメモリにバインドする
for (uint32_t i = 0; i < sparseRequirementsCount; ++i)
{
    if (pSparseReqs[i].formatProperties.aspectMask &
        VK_IMAGE_ASPECT_METADATA_BIT)
    {
        // メタデータは他のアスペクトと組み合わせてはならない
        assert(pSparseReqs[i].formatProperties.aspectMask ==
               VK_IMAGE_ASPECT_METADATA_BIT);

        if (pSparseReqs[i].formatProperties.flags &
            VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT)
        {
            VkSparseMemoryBind* pBind = &binds[bindCount];
            pBind->memorySize = pSparseReqs[i].imageMipTailSize;
            bindCount++;

            // ... メモリ範囲の割り当て

            pBind->resourceOffset = pSparseReqs[i].imageMipTailOffset;
            pBind->memoryOffset = /* 割り当てられた memoryOffset */;
            pBind->memory = /* 割り当てられた memory */;
            pBind->flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT;

        }
        else
        {
            // 配列レイヤごとに mip tail region が必要です。
            for (uint32_t a = 0; a < sparseImageInfo.arrayLayers; ++a)
            {
                VkSparseMemoryBind* pBind = &binds[bindCount];
                pBind->memorySize = pSparseReqs[i].imageMipTailSize;
                bindCount++;

                // ... メモリ範囲の割り当て

                pBind->resourceOffset = pSparseReqs[i].imageMipTailOffset +
                                        (a * pSparseReqs[i].imageMipTailStride);

                pBind->memoryOffset = /* 割り当てられた memoryOffset */;
                pBind->memory = /* 割り当てられた memory */
                pBind->flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT;
            }
        }
    }
    else
    {
        // リソースデータ
        VkExtent3D lod0BlockSize =
        {
            AlignedDivide(
                sparseImageInfo.extent.width,
                pSparseReqs[i].formatProperties.imageGranularity.width);
            AlignedDivide(
                sparseImageInfo.extent.height,
                pSparseReqs[i].formatProperties.imageGranularity.height);
            AlignedDivide(
                sparseImageInfo.extent.depth,
                pSparseReqs[i].formatProperties.imageGranularity.depth);
        }
        size_t totalBlocks =
            lod0BlockSize.width *
            lod0BlockSize.height *
            lod0BlockSize.depth;

        // 各ブロックはアライメント要求と同じサイズ
        // レベル0の総メモリサイズを計算する
        VkDeviceSize lod0MemSize = totalBlocks * memoryRequirements.alignment;

        // 各配列レイヤにメモリを割り当てる
        for (uint32_t a = 0; a < sparseImageInfo.arrayLayers; ++a)
        {
            // ... メモリ範囲の割り当て

            VkSparseImageMemoryBind* pBind = &imageBinds[a];
            pBind->subresource.aspectMask = pSparseReqs[i].formatProperties.aspectMask;
            pBind->subresource.mipLevel = 0;
            pBind->subresource.arrayLayer = a;

            pBind->offset = (VkOffset3D){0, 0, 0};
            pBind->extent = sparseImageInfo.extent;
            pBind->memoryOffset = /* 割り当てられた memoryOffset */;
            pBind->memory = /* 割り当てられた memory */;
            pBind->flags = 0;
        }
    }

    free(pSparseReqs);
}

const VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo =
{
    sparseImage,                                // image
    bindCount,                                  // bindCount
    binds                                       // pBinds
};

const VkSparseImageMemoryBindInfo imageBindInfo =
{
    sparseImage,                                // image
    sparseImageInfo.arrayLayers,                // bindCount
    imageBinds                                  // pBinds
};

const VkBindSparseInfo bindSparseInfo =
{
    VK_STRUCTURE_TYPE_BIND_SPARSE_INFO,         // sType
    NULL,                                       // pNext
    ...
    1,                                          // imageOpaqueBindCount
    &opaqueBindInfo,                            // pImageOpaqueBinds
    1,                                          // imageBindCount
    &imageBindInfo,                             // pImageBinds
    ...
};

// vkQueueBindSparse はキューオブジェクトごとに外部で同期している
AcquireQueueOwnership(queue);

// 実際にメモリをバインドする
vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE);

ReleaseQueueOwnership(queue);