やっていくVulkan入門

5-4. 頂点入力デスクリプション

この節では頂点入力デスクリプションというものについて解説します。


前回で頂点データをバッファに書き込みました。GPUがアクセスできる形にはなりました。しかしこのままではバッファがどういう形式で頂点のデータを持っているのか、Vulkanのシステム側には分かりません。Vertex構造体というこちらが勝手に定義してみただけの構造体のことは知りようがないのです。このままではシェーダに読み込ませられません。

そこで頂点入力デスクリプションというものを用意します。適切な説明(デスクリプション)を与えることで「こういう形式でこういうデータが入っている」という情報を与えることができます。データだけでなく説明を与えて初めてシェーダからデータを読み込めるのです。

デスクリプションはパイプライン作成時の情報に含めることになります。さっそく準備していきましょう。要するに初期化用構造体の一種でしかありません。


アトリビュートとバインディング

頂点入力デスクリプションには2種類あります。アトリビュートデスクリプション(vk::VertexInputAttributeDescription)バインディングデスクリプション(vk::VertexInputBindingDescription)です。これはひとえに、シェーダに与えるデータを分ける単位が「バインディング」と「アトリビュート」の2段階あるためです。少しこの辺について説明しましょう。

バインディングが一番上の単位です。基本的に1つのバインディングに対して1つのバッファ(の範囲)が結び付けられます。あるバインディングに含まれるデータは一定の大きさごとに切り分けられ、各頂点のデータとして解釈されます。

アトリビュートはより細かい単位になります。1つの頂点のデータは、1つまたは複数のアトリビュートとして分けられます。一つの頂点データは「座標」とか「色」みたいな複数のデータを内包していますが、この1つ1つのデータが1つのアトリビュートに相当することになります。

(注意点として、1つのアトリビュートは「多次元ベクトル」や「行列」だったりできるので、アトリビュートは「x座標」とか「y座標」みたいな単位よりはもう少し大きい単位になります。)


以上を踏まえて説明をします。

バインディングデスクリプションでは、「1つのバインディングのデータをどうやって各頂点のデータに分割するか」という情報を設定します。具体的には、1つの頂点データのバイト数とかを設定します。

そしてアトリビュートデスクリプションでは、「1つの頂点データのどの位置からどういう形式でアトリビュートのデータを取り出すか」という情報を設定します

アトリビュートデスクリプションはアトリビュートの数だけ作成します。1つの頂点データが3種類のデータを含んでいるならば、3つのアトリビュートデスクリプションを作らなければなりません。今回のデータだと含んでいるデータは「座標」だけなので1つだけになりますが、複数のアトリビュートを含む場合はその数だけ作成します。


頂点入力バインディングデスクリプション

バインディングの読み方の説明情報です。まずは vk::VertexInputBindingDescription を用意しましょう。

vk::VertexInputBindingDescription vertexBindingDescription[1];
vertexBindingDescription[0].binding = 0;
vertexBindingDescription[0].stride = sizeof(Vertex);
vertexBindingDescription[0].inputRate = vk::VertexInputRate::eVertex;

(要素数1の配列にしていますが、単に後々ちょっと説明しやすくするためというだけです。あまり気にしないでください)

bindingは説明の対象となるバインディングの番号です。各頂点入力バインディングには0から始まる番号が振ってあります。ここでは0番を使うことにします。

strideは1つのデータを取り出す際の刻み幅です。つまり上の図でいう所の「1つの頂点データ」の大きさですね。ここではVertex構造体のバイト数を指定しています。前節までで用意したデータはVertex構造体が並んでいるわけなので、各データを取り出すには当然Vertex構造体のバイト数ずつずらして読み取ることになります。

inputRateにはvk::VertexInputRate::eVertexを指定します。1頂点ごとにデータを読み込む、というだけの意味です。インスタンス化というものを行う場合に別の値を指定します。

バインディングデスクリプションはこれだけです。次に移りましょう。


頂点入力アトリビュートデスクリプション

アトリビュートの読み込み方の説明情報です。 今度は vk::VertexInputAttributeDescription を用意します。今回用意した頂点データが含む情報は「座標」だけなので、用意するアトリビュートデスクリプションは1つだけです。

vk::VertexInputAttributeDescription vertexInputDescription[1];
vertexInputDescription[0].binding = 0;
vertexInputDescription[0].location = 0;
vertexInputDescription[0].format = vk::Format::eR32G32Sfloat;
vertexInputDescription[0].offset = 0;

bindingはデータの読み込み元のバインディングの番号を指定します。上で0番のバインディングを使うことにしたので、ここでも0にしています。

locationはシェーダにデータを渡す際のデータ位置です。これは詳しく説明します。

ちょっと5-1. 頂点データの用意に書いたシェーダのコードを見てみましょう。以下のコードがあると思います。

layout(location = 0) in vec2 inPos;

location = 0という部分がありますね。シェーダがアトリビュートのデータを受け取る際はこのようにデータ位置を指定します。このlayout(location = xx)の指定とアトリビュートデスクリプションのlocationの位置は対応付けて書かなければなりません。 locationの対応付けによって、シェーダで読み込む変数とVulkanが用意したアトリビュートの対応が付くのです。

formatはデータ形式です。今回渡すのは32bitのfloat型が2つある2次元ベクトルなので、それを表すeR32G32Sfloatを指定します。ここで使われているvk::Formatは本来ピクセルの色データのフォーマットを表すものなのでRとかGとか入っていますが、それらはここでは無視してください。 ここでは 「32bit, 32bit, それぞれSigned(符号付)floatである」という意味を表すためだけにこれが指定されています。

ちなみにfloat型の3次元ベクトルを渡す際にはeR32G32B32Sfloatとか指定します。ここでもRGBの文字に深い意味はありません。色のデータを渡すにしろ座標データを渡すにしろこういう名前の値を指定します。違和感があるかもしれませんが、こういうものなので仕方ないです。

offsetは頂点データのどの位置からデータを取り出すかを示す値です。今回は1つしかアトリビュートが無いので0を指定していますが、複数のアトリビュートがある場合にはとても重要なものです。


2種類の頂点入力デスクリプションを作成したら、それをパイプラインに設定します。

頂点入力デスクリプションはvk::PipelineVertexInputStateCreateInfo構造体に設定します。パイプライン作成処理を以下のように書き換えましょう。

vk::PipelineVertexInputStateCreateInfo vertexInputInfo;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = vertexBindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 1;
vertexInputInfo.pVertexAttributeDescriptions = vertexInputDescription;

// (中略)

pipelineCreateInfo.pVertexInputState = &vertexInputInfo;

これでバッファを渡す準備は出来ました。


この節では頂点入力デスクリプションを設定しました。次節ではいよいよバッファを用いてポリゴン描画を行います。

この節のコード
#include <iostream>
#include <fstream>
#include <filesystem>
#include <vulkan/vulkan.hpp>
#include <GLFW/glfw3.h>

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

struct Vertex {
    float x, y;
};

std::vector<Vertex> vertices = {
    Vertex{ 0.0f, -0.5f },
    Vertex{ 0.5f,  0.5f },
    Vertex{-0.5f,  0.5f },
};

int main() {
    if (!glfwInit())
        return -1;

    uint32_t requiredExtensionsCount;
    const char** requiredExtensions = glfwGetRequiredInstanceExtensions(&requiredExtensionsCount);

    vk::InstanceCreateInfo createInfo;
    createInfo.enabledExtensionCount = requiredExtensionsCount;
    createInfo.ppEnabledExtensionNames = requiredExtensions;

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

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    GLFWwindow* window;
    window = glfwCreateWindow(screenWidth, screenHeight, "GLFW Test Window", NULL, NULL);
    if (!window) {
        const char* err;
        glfwGetError(&err);
        std::cout << err << std::endl;
        glfwTerminate();
        return -1;
    }

    VkSurfaceKHR c_surface;
    auto result = glfwCreateWindowSurface(instance.get(), window, nullptr, &c_surface);
    if (result != VK_SUCCESS) {
        const char* err;
        glfwGetError(&err);
        std::cout << err << std::endl;
        glfwTerminate();
        return -1;
    }
    vk::UniqueSurfaceKHR surface{ c_surface, instance.get() };

    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 &&
                physicalDevices[i].getSurfaceSupportKHR(j, surface.get())) {
                existsGraphicsQueue = true;
                graphicsQueueFamilyIndex = j;
                break;
            }
        }

        std::vector<vk::ExtensionProperties> extProps = physicalDevices[i].enumerateDeviceExtensionProperties();
        bool supportsSwapchainExtension = false;

        for (size_t j = 0; j < extProps.size(); j++) {
            if (std::string_view(extProps[j].extensionName.data()) == VK_KHR_SWAPCHAIN_EXTENSION_NAME) {
                supportsSwapchainExtension = true;
                break;
            }
        }

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

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

    vk::DeviceCreateInfo devCreateInfo;

    auto devRequiredExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };

    devCreateInfo.enabledExtensionCount = devRequiredExtensions.size();
    devCreateInfo.ppEnabledExtensionNames = devRequiredExtensions.begin();

    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::BufferCreateInfo vertBufferCreateInfo;
    vertBufferCreateInfo.size = sizeof(Vertex) * vertices.size();
    vertBufferCreateInfo.usage = vk::BufferUsageFlagBits::eVertexBuffer;
    vertBufferCreateInfo.sharingMode = vk::SharingMode::eExclusive;

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

    vk::UniqueBuffer vertexBuf = device->createBufferUnique(vertBufferCreateInfo);

    vk::MemoryRequirements vertexBufMemReq = device->getBufferMemoryRequirements(vertexBuf.get());
        
    vk::MemoryAllocateInfo vertexBufMemAllocInfo;
    vertexBufMemAllocInfo.allocationSize = vertexBufMemReq.size;

    bool suitableMemoryTypeFound = false;
    for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
        if (vertexBufMemReq.memoryTypeBits & (1 << i) &&
            (memProps.memoryTypes[i].propertyFlags & vk::MemoryPropertyFlagBits::eHostVisible)) {
            vertexBufMemAllocInfo.memoryTypeIndex = i;
            suitableMemoryTypeFound = true;
            break;
        }
    }
    if (!suitableMemoryTypeFound) {
        std::cerr << "適切なメモリタイプが存在しません。" << std::endl;
        return -1;
    }

    vk::UniqueDeviceMemory vertexBufMemory = device->allocateMemoryUnique(vertexBufMemAllocInfo);

    device->bindBufferMemory(vertexBuf.get(), vertexBufMemory.get(), 0);

    void* vertexBufMem = device->mapMemory(vertexBufMemory.get(), 0, sizeof(Vertex) * vertices.size());

    std::memcpy(vertexBufMem, vertices.data(), sizeof(Vertex) * vertices.size());

    vk::MappedMemoryRange flushMemoryRange;
    flushMemoryRange.memory = vertexBufMemory.get();
    flushMemoryRange.offset = 0;
    flushMemoryRange.size = sizeof(Vertex) * vertices.size();

    device->flushMappedMemoryRanges({ flushMemoryRange });

    device->unmapMemory(vertexBufMemory.get());

    std::vector<vk::SurfaceFormatKHR> surfaceFormats = physicalDevice.getSurfaceFormatsKHR(surface.get());
    std::vector<vk::PresentModeKHR> surfacePresentModes = physicalDevice.getSurfacePresentModesKHR(surface.get());

    vk::SurfaceFormatKHR swapchainFormat = surfaceFormats[0];
    vk::PresentModeKHR swapchainPresentMode = surfacePresentModes[0];

    vk::AttachmentDescription attachments[1];
    attachments[0].format = swapchainFormat.format;
    attachments[0].samples = vk::SampleCountFlagBits::e1;
    attachments[0].loadOp = vk::AttachmentLoadOp::eClear;
    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::ePresentSrcKHR;

    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::Viewport viewports[1];
    viewports[0].x = 0.0;
    viewports[0].y = 0.0;
    viewports[0].minDepth = 0.0;
    viewports[0].maxDepth = 1.0;
    viewports[0].width = screenWidth;
    viewports[0].height = screenHeight;

    vk::Rect2D scissors[1];
    scissors[0].offset = vk::Offset2D{ 0, 0 };
    scissors[0].extent = vk::Extent2D{ screenWidth, screenHeight };

    vk::PipelineViewportStateCreateInfo viewportState;
    viewportState.viewportCount = 1;
    viewportState.pViewports = viewports;
    viewportState.scissorCount = 1;
    viewportState.pScissors = scissors;

    vk::VertexInputBindingDescription vertexBindingDescription[1];
    vertexBindingDescription[0].binding = 0;
    vertexBindingDescription[0].stride = sizeof(Vertex);
    vertexBindingDescription[0].inputRate = vk::VertexInputRate::eVertex;

    vk::VertexInputAttributeDescription vertexInputDescription[1];
    vertexInputDescription[0].binding = 0;
    vertexInputDescription[0].location = 0;
    vertexInputDescription[0].format = vk::Format::eR32G32Sfloat;
    vertexInputDescription[0].offset = 0;

    vk::PipelineVertexInputStateCreateInfo vertexInputInfo;
    vertexInputInfo.vertexBindingDescriptionCount = 1;
    vertexInputInfo.pVertexBindingDescriptions = vertexBindingDescription;
    vertexInputInfo.vertexAttributeDescriptionCount = 1;
    vertexInputInfo.pVertexAttributeDescriptions = vertexInputDescription;

    vk::PipelineInputAssemblyStateCreateInfo inputAssembly;
    inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;
    inputAssembly.primitiveRestartEnable = false;

    vk::PipelineRasterizationStateCreateInfo rasterizer;
    rasterizer.depthClampEnable = false;
    rasterizer.rasterizerDiscardEnable = false;
    rasterizer.polygonMode = vk::PolygonMode::eFill;
    rasterizer.lineWidth = 1.0f;
    rasterizer.cullMode = vk::CullModeFlagBits::eBack;
    rasterizer.frontFace = vk::FrontFace::eClockwise;
    rasterizer.depthBiasEnable = false;

    vk::PipelineMultisampleStateCreateInfo multisample;
    multisample.sampleShadingEnable = false;
    multisample.rasterizationSamples = vk::SampleCountFlagBits::e1;

    vk::PipelineColorBlendAttachmentState blendattachment[1];
    blendattachment[0].colorWriteMask =
        vk::ColorComponentFlagBits::eA |
        vk::ColorComponentFlagBits::eR |
        vk::ColorComponentFlagBits::eG |
        vk::ColorComponentFlagBits::eB;
    blendattachment[0].blendEnable = false;

    vk::PipelineColorBlendStateCreateInfo blend;
    blend.logicOpEnable = false;
    blend.attachmentCount = 1;
    blend.pAttachments = blendattachment;

    vk::PipelineLayoutCreateInfo layoutCreateInfo;
    layoutCreateInfo.setLayoutCount = 0;
    layoutCreateInfo.pSetLayouts = nullptr;

    vk::UniquePipelineLayout pipelineLayout = device->createPipelineLayoutUnique(layoutCreateInfo);

    size_t vertSpvFileSz = std::filesystem::file_size("shader.vert.spv");

    std::ifstream vertSpvFile("shader.vert.spv", std::ios_base::binary);

    std::vector<char> vertSpvFileData(vertSpvFileSz);
    vertSpvFile.read(vertSpvFileData.data(), vertSpvFileSz);

    vk::ShaderModuleCreateInfo vertShaderCreateInfo;
    vertShaderCreateInfo.codeSize = vertSpvFileSz;
    vertShaderCreateInfo.pCode = reinterpret_cast<const uint32_t*>(vertSpvFileData.data());

    vk::UniqueShaderModule vertShader = device->createShaderModuleUnique(vertShaderCreateInfo);

    size_t fragSpvFileSz = std::filesystem::file_size("shader.frag.spv");

    std::ifstream fragSpvFile("shader.frag.spv", std::ios_base::binary);

    std::vector<char> fragSpvFileData(fragSpvFileSz);
    fragSpvFile.read(fragSpvFileData.data(), fragSpvFileSz);

    vk::ShaderModuleCreateInfo fragShaderCreateInfo;
    fragShaderCreateInfo.codeSize = fragSpvFileSz;
    fragShaderCreateInfo.pCode = reinterpret_cast<const uint32_t*>(fragSpvFileData.data());

    vk::UniqueShaderModule fragShader = device->createShaderModuleUnique(fragShaderCreateInfo);

    vk::PipelineShaderStageCreateInfo shaderStage[2];
    shaderStage[0].stage = vk::ShaderStageFlagBits::eVertex;
    shaderStage[0].module = vertShader.get();
    shaderStage[0].pName = "main";
    shaderStage[1].stage = vk::ShaderStageFlagBits::eFragment;
    shaderStage[1].module = fragShader.get();
    shaderStage[1].pName = "main";

    vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
    pipelineCreateInfo.pViewportState = &viewportState;
    pipelineCreateInfo.pVertexInputState = &vertexInputInfo;
    pipelineCreateInfo.pInputAssemblyState = &inputAssembly;
    pipelineCreateInfo.pRasterizationState = &rasterizer;
    pipelineCreateInfo.pMultisampleState = &multisample;
    pipelineCreateInfo.pColorBlendState = &blend;
    pipelineCreateInfo.layout = pipelineLayout.get();
    pipelineCreateInfo.renderPass = renderpass.get();
    pipelineCreateInfo.subpass = 0;
    pipelineCreateInfo.stageCount = 2;
    pipelineCreateInfo.pStages = shaderStage;

    vk::UniquePipeline pipeline = device->createGraphicsPipelineUnique(nullptr, pipelineCreateInfo).value;

    vk::UniqueSwapchainKHR swapchain;
    std::vector<vk::Image> swapchainImages;
    std::vector<vk::UniqueImageView> swapchainImageViews;
    std::vector<vk::UniqueFramebuffer> swapchainFramebufs;

    auto recreateSwapchain = [&](){
        swapchainFramebufs.clear();
        swapchainImageViews.clear();
        swapchainImages.clear();
        swapchain.reset();

        vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.get());

        vk::SwapchainCreateInfoKHR swapchainCreateInfo;
        swapchainCreateInfo.surface = surface.get();
        swapchainCreateInfo.minImageCount = surfaceCapabilities.minImageCount + 1;
        swapchainCreateInfo.imageFormat = swapchainFormat.format;
        swapchainCreateInfo.imageColorSpace = swapchainFormat.colorSpace;
        swapchainCreateInfo.imageExtent = surfaceCapabilities.currentExtent;
        swapchainCreateInfo.imageArrayLayers = 1;
        swapchainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment;
        swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive;
        swapchainCreateInfo.preTransform = surfaceCapabilities.currentTransform;
        swapchainCreateInfo.presentMode = swapchainPresentMode;
        swapchainCreateInfo.clipped = VK_TRUE;

        swapchain = device->createSwapchainKHRUnique(swapchainCreateInfo);

        swapchainImages = device->getSwapchainImagesKHR(swapchain.get());

        swapchainImageViews.resize(swapchainImages.size());

        for (size_t i = 0; i < swapchainImages.size(); i++) {
            vk::ImageViewCreateInfo imgViewCreateInfo;
            imgViewCreateInfo.image = swapchainImages[i];
            imgViewCreateInfo.viewType = vk::ImageViewType::e2D;
            imgViewCreateInfo.format = swapchainFormat.format;
            imgViewCreateInfo.components.r = vk::ComponentSwizzle::eIdentity;
            imgViewCreateInfo.components.g = vk::ComponentSwizzle::eIdentity;
            imgViewCreateInfo.components.b = vk::ComponentSwizzle::eIdentity;
            imgViewCreateInfo.components.a = vk::ComponentSwizzle::eIdentity;
            imgViewCreateInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
            imgViewCreateInfo.subresourceRange.baseMipLevel = 0;
            imgViewCreateInfo.subresourceRange.levelCount = 1;
            imgViewCreateInfo.subresourceRange.baseArrayLayer = 0;
            imgViewCreateInfo.subresourceRange.layerCount = 1;

            swapchainImageViews[i] = device->createImageViewUnique(imgViewCreateInfo);
        }

        swapchainFramebufs.resize(swapchainImages.size());

        for (size_t i = 0; i < swapchainImages.size(); i++) {
            vk::ImageView frameBufAttachments[1];
            frameBufAttachments[0] = swapchainImageViews[i].get();

            vk::FramebufferCreateInfo frameBufCreateInfo;
            frameBufCreateInfo.width = surfaceCapabilities.currentExtent.width;
            frameBufCreateInfo.height = surfaceCapabilities.currentExtent.height;
            frameBufCreateInfo.layers = 1;
            frameBufCreateInfo.renderPass = renderpass.get();
            frameBufCreateInfo.attachmentCount = 1;
            frameBufCreateInfo.pAttachments = frameBufAttachments;

            swapchainFramebufs[i] = device->createFramebufferUnique(frameBufCreateInfo);
        }
    };

    recreateSwapchain();

    vk::CommandPoolCreateInfo cmdPoolCreateInfo;
    cmdPoolCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
    cmdPoolCreateInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
    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::SemaphoreCreateInfo semaphoreCreateInfo;

    vk::UniqueSemaphore swapchainImgSemaphore, imgRenderedSemaphore;
    swapchainImgSemaphore = device->createSemaphoreUnique(semaphoreCreateInfo);
    imgRenderedSemaphore = device->createSemaphoreUnique(semaphoreCreateInfo);
    
    vk::FenceCreateInfo fenceCreateInfo;
    fenceCreateInfo.flags = vk::FenceCreateFlagBits::eSignaled;

    vk::UniqueFence imgRenderedFence = device->createFenceUnique(fenceCreateInfo);

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        device->waitForFences({ imgRenderedFence.get()}, VK_TRUE, UINT64_MAX);

        vk::ResultValue acquireImgResult = device->acquireNextImageKHR(swapchain.get(), 1'000'000'000, swapchainImgSemaphore.get());
        if(acquireImgResult.result == vk::Result::eSuboptimalKHR || acquireImgResult.result == vk::Result::eErrorOutOfDateKHR) {
            std::cerr << "スワップチェーンを再作成します。" << std::endl;
            recreateSwapchain();
            continue;
        }
        if (acquireImgResult.result != vk::Result::eSuccess) {
            std::cerr << "次フレームの取得に失敗しました。" << std::endl;
            return -1;
        }
        device->resetFences({ imgRenderedFence.get() });

        uint32_t imgIndex = acquireImgResult.value;

        cmdBufs[0]->reset();

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

        vk::ClearValue clearVal[1];
        clearVal[0].color.float32[0] = 0.0f;
        clearVal[0].color.float32[1] = 0.0f;
        clearVal[0].color.float32[2] = 0.0f;
        clearVal[0].color.float32[3] = 1.0f;

        vk::RenderPassBeginInfo renderpassBeginInfo;
        renderpassBeginInfo.renderPass = renderpass.get();
        renderpassBeginInfo.framebuffer = swapchainFramebufs[imgIndex].get();
        renderpassBeginInfo.renderArea = vk::Rect2D({ 0,0 }, { screenWidth, screenHeight });
        renderpassBeginInfo.clearValueCount = 1;
        renderpassBeginInfo.pClearValues = clearVal;

        cmdBufs[0]->beginRenderPass(renderpassBeginInfo, vk::SubpassContents::eInline);

        cmdBufs[0]->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get());
        cmdBufs[0]->draw(3, 1, 0, 0);

        cmdBufs[0]->endRenderPass();

        cmdBufs[0]->end();

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

        vk::Semaphore renderwaitSemaphores[] = { swapchainImgSemaphore.get() };
        vk::PipelineStageFlags renderwaitStages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
        submitInfo.waitSemaphoreCount = 1;
        submitInfo.pWaitSemaphores = renderwaitSemaphores;
        submitInfo.pWaitDstStageMask = renderwaitStages;

        vk::Semaphore renderSignalSemaphores[] = { imgRenderedSemaphore.get() };
        submitInfo.signalSemaphoreCount = 1;
        submitInfo.pSignalSemaphores = renderSignalSemaphores;

        graphicsQueue.submit({ submitInfo }, imgRenderedFence.get());

        vk::PresentInfoKHR presentInfo;

        auto presentSwapchains = { swapchain.get() };
        auto imgIndices = { imgIndex };

        presentInfo.swapchainCount = presentSwapchains.size();
        presentInfo.pSwapchains = presentSwapchains.begin();
        presentInfo.pImageIndices = imgIndices.begin();

        vk::Semaphore presenWaitSemaphores[] = { imgRenderedSemaphore.get() };
        presentInfo.waitSemaphoreCount = 1;
        presentInfo.pWaitSemaphores = presenWaitSemaphores;

        graphicsQueue.presentKHR(presentInfo);
    }

    graphicsQueue.waitIdle();
    glfwTerminate();
    return 0;
}
#version 450
#extension GL_ARB_separate_shader_objects : enable

void main() {
    if(gl_VertexIndex == 0) {
        gl_Position = vec4(0.0, -0.5, 0.0, 1.0);
    } else if(gl_VertexIndex == 1) {
        gl_Position = vec4(0.5, 0.5, 0.0, 1.0);
    } else if(gl_VertexIndex == 2) {
        gl_Position = vec4(-0.5, 0.5, 0.0, 1.0);
    }
}
#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) out vec4 outColor;

void main() {
    outColor = vec4(1.0, 0.0, 0.0, 1.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})

find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(app PRIVATE glfw)