やっていくVulkan入門

3-2. メモリの割り当て

前回で生成した画像オブジェクト(イメージ)を使うためには、イメージにメモリを確保して割り当てる必要があります。ここで重要なことですが、このとき使うメモリはnewやmallocで確保できる通常のメモリとは違います。 これから扱うのはデバイスメモリという特殊なメモリです。実は、通常のメモリはGPUからアクセスすることができないのです。これではGPUが描画するどころではありません。そこでGPUからアクセスできる特殊なメモリを用意する必要があります。それがこれから使う「デバイスメモリ」です。

デバイスメモリはvk::DeviceallocateMemory メソッドで確保します。そして確保したデバイスメモリは、bindImageMemory メソッドで前節で作成したイメージに結びつけることができます。

new演算子やmallocなどで確保する通常のメモリとデバイスメモリの重要な違いとして、デバイスメモリには「種類」があります。 デバイスメモリを使う際はどれを使うか適切に選ばなければなりません。どんな種類のデバイスメモリがあるかは物理デバイス依存であり、vk::PhysicalDevicegetMemoryProperties メソッドで取得できます。

vk::PhysicalDeviceMemoryProperties memProps = physicalDevice.getMemoryProperties();

vk::PhysicalDeviceMemoryProperties 構造体にはGPUが何種類のメモリを持っているかを表すmemoryTypeCountメンバ変数、各種類のメモリに関する情報を表すmemoryTypesメンバ変数(配列です)が格納されています。

また、イメージ(vk::Image)に対してどんな種類のメモリがどれだけ必要かという情報はvk::DevicegetImageMemoryRequirements で取得できます。

vk::MemoryRequirements imgMemReq = device->getImageMemoryRequirements(image.get());

vk::MemoryRequirements 構造体には必要なメモリサイズを表すsizeメンバ変数、そして使用可能なメモリの種類をビットマスクで表すmemoryTypeBitsメンバ変数が格納されています。

例えばmemoryTypeBitsの中身が2進数で0b00101011だった場合を考えてみましょう。右から0番目、1番目、3番目、5番目のビットが1になっています。つまりこの場合、使えるメモリの種類はメモリタイプ0番、1番、3番、5番になるのでこの中から選ぶことになります。

以上を踏まえて、メモリの確保のコードを見てみましょう。

vk::PhysicalDeviceMemoryProperties memProps = physicalDevice.getMemoryProperties();

vk::MemoryRequirements imgMemReq = device->getImageMemoryRequirements(image.get());

vk::MemoryAllocateInfo imgMemAllocInfo;
imgMemAllocInfo.allocationSize = imgMemReq.size;

bool suitableMemoryTypeFound = false;
for (size_t i = 0; i < memProps.memoryTypeCount; i++) {
    if (imgMemReq.memoryTypeBits & (1 << i)) {
        imgMemAllocInfo.memoryTypeIndex = i;
        suitableMemoryTypeFound = true;
        break;
    }
}

if (!suitableMemoryTypeFound) {
    std::cerr << "使用可能なメモリタイプがありません。" << std::endl;
    return -1;
}

vk::UniqueDeviceMemory imgMem = device->allocateMemoryUnique(imgMemAllocInfo);

メモリを確保するときはvk::MemoryAllocateInfoに必要なメモリのサイズと種類を指定します。

memoryTypeBitsを確認して、ビットが立っていればその番号のメモリタイプを使用します。今回は使えるメモリタイプの中から単純に一番最初に見つかったものを使用していますが、今後の記事で、ビットが立っているメモリタイプの中から更に選ぶ処理も実装するかもしれません。

メモリの確保ができたらあとはイメージにバインド(結びつける)するだけです。

device->bindImageMemory(image.get(), imgMem.get(), 0);

第3引数の0は何かというと、「確保したメモリの先頭から何バイト目を使用するか」という項目です。たとえばメモリ1000バイトを要するイメージAとメモリ1500バイトを要するイメージBの2つがあったとき、2500バイトを確保して先頭から0バイト目以降をイメージAに、1000バイト目以降をイメージBにバインドするといったことができます。ここでは普通に0を指定しています。


この節ではメモリの確保をやりました。次節ではレンダーパスを作成します。

この節のコード
#include <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

const uint32_t screenWidth = 640;
const uint32_t screenHeight = 480;

int main() {
    vk::InstanceCreateInfo createInfo;

    vk::UniqueInstance instance;
    instance = vk::createInstanceUnique(createInfo);

    std::vector<vk::PhysicalDevice> physicalDevices = instance->enumeratePhysicalDevices();

    vk::PhysicalDevice physicalDevice;
    bool existsSuitablePhysicalDevice = false;
    uint32_t graphicsQueueFamilyIndex;

    for (size_t i = 0; i < physicalDevices.size(); i++) {
        std::vector<vk::QueueFamilyProperties> queueProps = physicalDevices[i].getQueueFamilyProperties();
        bool existsGraphicsQueue = false;

        for (size_t j = 0; j < queueProps.size(); j++) {
            if (queueProps[j].queueFlags & vk::QueueFlagBits::eGraphics) {
                existsGraphicsQueue = true;
                graphicsQueueFamilyIndex = j;
                break;
            }
        }

        if (existsGraphicsQueue) {
            physicalDevice = physicalDevices[i];
            existsSuitablePhysicalDevice = true;
            break;
        }
    }

    if (!existsSuitablePhysicalDevice) {
        std::cerr << "使用可能な物理デバイスがありません。" << std::endl;
        return -1;
    }

    vk::DeviceCreateInfo devCreateInfo;

    vk::DeviceQueueCreateInfo queueCreateInfo[1];
    queueCreateInfo[0].queueFamilyIndex = graphicsQueueFamilyIndex;
    queueCreateInfo[0].queueCount = 1;

    float queuePriorities[1] = { 1.0 };

    queueCreateInfo[0].pQueuePriorities = queuePriorities;

    devCreateInfo.pQueueCreateInfos = queueCreateInfo;
    devCreateInfo.queueCreateInfoCount = 1;
    vk::UniqueDevice device = physicalDevice.createDeviceUnique(devCreateInfo);

    vk::Queue graphicsQueue = device->getQueue(graphicsQueueFamilyIndex, 0);

    vk::CommandPoolCreateInfo cmdPoolCreateInfo;
    cmdPoolCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
    vk::UniqueCommandPool cmdPool = device->createCommandPoolUnique(cmdPoolCreateInfo);

    vk::CommandBufferAllocateInfo cmdBufAllocInfo;
    cmdBufAllocInfo.commandPool = cmdPool.get();
    cmdBufAllocInfo.commandBufferCount = 1;
    cmdBufAllocInfo.level = vk::CommandBufferLevel::ePrimary;
    std::vector<vk::UniqueCommandBuffer> cmdBufs =
        device->allocateCommandBuffersUnique(cmdBufAllocInfo);

    vk::ImageCreateInfo imgCreateInfo;
    imgCreateInfo.imageType = vk::ImageType::e2D;
    imgCreateInfo.extent = vk::Extent3D(screenWidth, screenHeight, 1);
    imgCreateInfo.mipLevels = 1;
    imgCreateInfo.arrayLayers = 1;
    imgCreateInfo.format = vk::Format::eR8G8B8A8Unorm;
    imgCreateInfo.tiling = vk::ImageTiling::eLinear;
    imgCreateInfo.initialLayout = vk::ImageLayout::eColorAttachmentOptimal;
    imgCreateInfo.usage = vk::ImageUsageFlagBits::eColorAttachment;
    imgCreateInfo.sharingMode = vk::SharingMode::eExclusive;
    imgCreateInfo.samples = vk::SampleCountFlagBits::e1;

    vk::UniqueImage image = device->createImageUnique(imgCreateInfo);

    vk::PhysicalDeviceMemoryProperties memProps = physicalDevice.getMemoryProperties();

    vk::MemoryRequirements imgMemReq = device->getImageMemoryRequirements(image.get());

    vk::MemoryAllocateInfo imgMemAllocInfo;
    imgMemAllocInfo.allocationSize = imgMemReq.size;

    bool suitableMemoryTypeFound = false;
    for (size_t i = 0; i < memProps.memoryTypeCount; i++) {
        if (imgMemReq.memoryTypeBits & (1 << i)) {
            imgMemAllocInfo.memoryTypeIndex = i;
            suitableMemoryTypeFound = true;
            break;
        }
    }

    if (!suitableMemoryTypeFound) {
        std::cerr << "使用可能なメモリタイプがありません。" << std::endl;
        return -1;
    }

    vk::UniqueDeviceMemory imgMem = device->allocateMemoryUnique(imgMemAllocInfo);

    device->bindImageMemory(image.get(), imgMem.get(), 0);

    vk::CommandBufferBeginInfo cmdBeginInfo;
    cmdBufs[0]->begin(cmdBeginInfo);

    // コマンドを記録

    cmdBufs[0]->end();

    vk::CommandBuffer submitCmdBuf[1] = { cmdBufs[0].get() };
    vk::SubmitInfo submitInfo;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = submitCmdBuf;

    graphicsQueue.submit({ submitInfo }, nullptr);

    return 0;
}
cmake_minimum_required(VERSION 3.22)

project(vulkan-test)

set(CMAKE_CXX_STANDARD 17)

add_executable(app main.cpp)

find_package(Vulkan REQUIRED)
target_include_directories(app PRIVATE ${Vulkan_INCLUDE_DIRS})
target_link_libraries(app PRIVATE ${Vulkan_LIBRARIES})