やっていくVulkan入門

3-3. レンダーパス

描画の対象となる場所ができたので、ここからは実際の描画処理になります。

描画をするにあたっては、まずレンダーパスというものが必要です。レンダーパスとは一言で言えば描画の処理順序を記述したオブジェクトで、Vulkanにおける描画処理の際には必ず必要になります。

大ざっぱな計画書みたいなもので、コマンド(=具体的な作業手順)とは別物です。

レンダーパスの情報を構成するものは3つあります。それぞれ「アタッチメント」「サブパス」「サブパス依存性」です。

アタッチメントはレンダーパスにおいて描画処理の対象となる画像データのことで、書き込まれたり読み込まれたりします。

サブパスは1つの描画処理のことで、単一または複数のサブパスが集まってレンダーパス全体を構成します。サブパスは任意の個数のアタッチメントを入力として受け取り、任意の個数のアタッチメントに描画結果を出力します。

サブパス依存性はサブパス間の依存関係、つまり「サブパス1番が終わってからでないとサブパス2番は実行できない」とかそういう関係を表します。

有向グラフとして考えてみると分かりやすいでしょう。以下は一例です。

複雑なレンダーパスで複雑な描画処理を表現することもできるのですが、今回は1つのサブパスと1つのアタッチメントだけで構成される単純なレンダーパスを作成します。

vk::AttachmentDescription attachments[1];
attachments[0].format = vk::Format::eR8G8B8A8Unorm;
attachments[0].samples = vk::SampleCountFlagBits::e1;
attachments[0].loadOp = vk::AttachmentLoadOp::eDontCare;
attachments[0].storeOp = vk::AttachmentStoreOp::eStore;
attachments[0].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
attachments[0].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
attachments[0].initialLayout = vk::ImageLayout::eUndefined;
attachments[0].finalLayout = vk::ImageLayout::eGeneral;

vk::AttachmentReference subpass0_attachmentRefs[1];
subpass0_attachmentRefs[0].attachment = 0;
subpass0_attachmentRefs[0].layout = vk::ImageLayout::eColorAttachmentOptimal;

vk::SubpassDescription subpasses[1];
subpasses[0].pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpasses[0].colorAttachmentCount = 1;
subpasses[0].pColorAttachments = subpass0_attachmentRefs;

vk::RenderPassCreateInfo renderpassCreateInfo;
renderpassCreateInfo.attachmentCount = 1;
renderpassCreateInfo.pAttachments = attachments;
renderpassCreateInfo.subpassCount = 1;
renderpassCreateInfo.pSubpasses = subpasses;
renderpassCreateInfo.dependencyCount = 0;
renderpassCreateInfo.pDependencies = nullptr;

vk::UniqueRenderPass renderpass = device->createRenderPassUnique(renderpassCreateInfo);

模式図としてはシンプルなのですが、それでもコードに直すと分量がえげつなくなります。実態はシンプルなので面食らわないようにしましょう。レンダーパスはvk::DevicecreateRenderPass メソッドで作成できます。

初期化用構造体のメンバには今の知識だとまだ説明の難しいものもありますが、このコードでサブパスとアタッチメントの接続関係が表現されていることをなんとなく分かってもらえれば十分です。なお、 vk::AttachmentReference 構造体の attachment メンバは「何番のアタッチメント」という形でレンダーパスの中のアタッチメントを指定します。ここでは0を指定しているので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::eUndefined;
    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::AttachmentDescription attachments[1];
    attachments[0].format = vk::Format::eR8G8B8A8Unorm;
    attachments[0].samples = vk::SampleCountFlagBits::e1;
    attachments[0].loadOp = vk::AttachmentLoadOp::eDontCare;
    attachments[0].storeOp = vk::AttachmentStoreOp::eStore;
    attachments[0].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    attachments[0].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    attachments[0].initialLayout = vk::ImageLayout::eUndefined;
    attachments[0].finalLayout = vk::ImageLayout::eGeneral;

    vk::AttachmentReference subpass0_attachmentRefs[1];
    subpass0_attachmentRefs[0].attachment = 0;
    subpass0_attachmentRefs[0].layout = vk::ImageLayout::eColorAttachmentOptimal;

    vk::SubpassDescription subpasses[1];
    subpasses[0].pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
    subpasses[0].colorAttachmentCount = 1;
    subpasses[0].pColorAttachments = subpass0_attachmentRefs;

    vk::RenderPassCreateInfo renderpassCreateInfo;
    renderpassCreateInfo.attachmentCount = 1;
    renderpassCreateInfo.pAttachments = attachments;
    renderpassCreateInfo.subpassCount = 1;
    renderpassCreateInfo.pSubpasses = subpasses;
    renderpassCreateInfo.dependencyCount = 0;
    renderpassCreateInfo.pDependencies = nullptr;

    vk::UniqueRenderPass renderpass = device->createRenderPassUnique(renderpassCreateInfo);
    
    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})