Vulkan 開発におけるメモリ管理の落とし穴:QVulkanWindow::hostVisibleMemoryIndex() 関数で回避する


QVulkanWindow::hostVisibleMemoryIndex() 関数は、Vulkan API の VkMemoryRequirements 構造体で定義されたホスト可視メモリタイプのインデックスを返します。これは、CPU から直接アクセスできるメモリ領域を識別するために使用されます。

構文

uint32_t QVulkanWindow::hostVisibleMemoryIndex() const;

戻り値

ホスト可視メモリタイプのインデックス。

詳細

Vulkan API では、メモリはさまざまなタイプに分類されます。これらのタイプは、メモリの使用方法と特性によって異なります。ホスト可視メモリは、CPU から直接アクセスできるメモリタイプです。これは、アプリケーションがデータを転送したり、レンダリングバッファにアクセスしたりするために使用されます。

QVulkanWindow::hostVisibleMemoryIndex() 関数は、Vulkan デバイスで利用可能なホスト可視メモリタイプのインデックスを返します。このインデックスは、VkMemoryRequirements 構造体の memoryTypeBits メンバーを使用して、メモリ割り当て時に使用できます。

uint32_t memoryIndex = window.hostVisibleMemoryIndex();

VkMemoryRequirements memoryRequirements;
vkGetBufferMemoryRequirements(device, buffer, &memoryRequirements);

uint32_t memoryTypeBits = memoryRequirements.memoryTypeBits;

VkMemoryAllocateInfo allocInfo;
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.pNext = nullptr;
allocInfo.allocationSize = bufferSize;
allocInfo.memoryTypeIndex = memoryTypeBits & (1 << memoryIndex);

VkDeviceMemory memory;
vkAllocateMemory(device, &allocInfo, nullptr, &memory);

この例では、QVulkanWindow::hostVisibleMemoryIndex() 関数を使用して、ホスト可視メモリタイプのインデックスを取得します。次に、このインデックスを使用して、VkMemoryRequirements 構造体の memoryTypeBits メンバーを調べます。これは、バッファに必要なメモリタイプのビットマスクを返します。最後に、このビットマスクを使用して、バッファに必要なメモリを割り当てます。

  • 便利のため、ホスト可視メモリとデバイスローカルメモリのタイプインデックスは、QVulkanWindow::hostVisibleMemoryIndex()QVulkanWindow::deviceLocalMemoryIndex() 関数によって公開されています。
  • この関数のデフォルトの実装は空です。
  • QVulkanWindow::hostVisibleMemoryIndex() 関数は、Vulkan デバイスがアイドル状態になるまで待機してから呼び出されます。


#include <QVulkanWindow>
#include <QtVulkan>

class MyWindow : public QVulkanWindow
{
public:
    MyWindow()
    {
        setSurfaceType(QVulkanWindow::VulkanSurface);
        setFormat(QVulkanFormat(QRgbFormat::Rgba8));
        setTitle("Vulkan Window");

        // Vulkan デバイスとキューを取得
        auto device = device();
        auto queue = queue();

        // バッファを作成
        VkBufferCreateInfo bufferCreateInfo;
        bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
        bufferCreateInfo.pNext = nullptr;
        bufferCreateInfo.flags = 0;
        bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC | VK_BUFFER_USAGE_VERTEX_BUFFER;
        bufferCreateInfo.size = 1024 * 1024 * sizeof(Vertex);

        VkBuffer buffer;
        vkCreateBuffer(device, &bufferCreateInfo, nullptr, &buffer);

        // バッファに必要なメモリサイズを取得
        VkMemoryRequirements memoryRequirements;
        vkGetBufferMemoryRequirements(device, buffer, &memoryRequirements);

        // ホスト可視メモリタイプのインデックスを取得
        uint32_t memoryIndex = hostVisibleMemoryIndex();

        // メモリ割り当て
        VkMemoryAllocateInfo allocInfo;
        allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        allocInfo.pNext = nullptr;
        allocInfo.allocationSize = memoryRequirements.size;
        allocInfo.memoryTypeIndex = memoryRequirements.memoryTypeBits & (1 << memoryIndex);

        VkDeviceMemory memory;
        vkAllocateMemory(device, &allocInfo, nullptr, &memory);

        // バッファをメモリにバインド
        vkBindBufferMemory(device, buffer, memory, 0);

        // ...

        // バッファとメモリを解放
        vkDestroyBuffer(device, buffer, nullptr);
        vkFreeMemory(device, memory, nullptr);
    }
};

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    MyWindow window;
    window.show();

    return app.exec();
}

このコードでは、まず QVulkanWindow オブジェクトを作成し、必要な設定を行います。次に、Vulkan デバイスとキューを取得し、バッファを作成します。

バッファを作成したら、バッファに必要なメモリサイズとホスト可視メモリタイプのインデックスを取得します。次に、これらの情報を使用してメモリを割り当て、バッファをメモリにバインドします。

最後に、バッファとメモリを解放します。



この関数は便利ですが、常に最適な方法とは限りません。代替方法として、以下の方法が考えられます。

vkGetPhysicalDeviceMemoryProperties() 関数を使用する

この関数は、物理デバイスのメモリ特性に関する情報を取得します。この情報には、ホスト可視メモリタイプのインデックスも含まれます。

VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(device, &memoryProperties);

uint32_t memoryIndex = 0;
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i)
{
    if (memoryProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
    {
        memoryIndex = i;
        break;
    }
}

この方法は、QVulkanWindow::hostVisibleMemoryIndex() 関数よりも汎用的ですが、若干コード量が増えます。

QVulkanWindow::deviceLocalMemoryIndex() 関数と組み合わせる

QVulkanWindow::deviceLocalMemoryIndex() 関数は、Vulkan デバイスで利用可能なデバイスローカルメモリタイプのインデックスを返します。デバイスローカルメモリは、CPU から直接アクセスできませんが、GPU から高速にアクセスできます。

多くの場合、ホスト可視メモリとデバイスローカルメモリを組み合わせることで、パフォーマンスを向上させることができます。

uint32_t hostVisibleMemoryIndex = hostVisibleMemoryIndex();
uint32_t deviceLocalMemoryIndex = deviceLocalMemoryIndex();

// バッファをホスト可視メモリとデバイスローカルメモリに分割する
// ...

この方法は、より複雑なメモリ管理タスクを実行する必要がある場合に役立ちます。

QVulkanWindow の派生クラスを作成する

QVulkanWindow の派生クラスを作成し、独自の hostVisibleMemoryIndex() 関数を実装することもできます。この方法は、より高度なメモリ管理ロジックを実装する必要がある場合に役立ちます。

最適な方法の選択

どの方法が最適かは、特定のニーズによって異なります。シンプルな場合は、QVulkanWindow::hostVisibleMemoryIndex() 関数を使用するのが最善です。より複雑なメモリ管理タスクを実行する必要がある場合は、上記の代替方法を検討してください。

  • QVulkanWindow::deviceLocalMemoryIndex() 関数は、Vulkan デバイスがアイドル状態になるまで待機しません。
  • QVulkanWindow::hostVisibleMemoryIndex() 関数は、Vulkan デバイスがアイドル状態になるまで待機しません。
  • vkGetPhysicalDeviceMemoryProperties() 関数は、Vulkan デバイスがアイドル状態になるまで待機してから呼び出されます。