サーフェスが取得出来たら、次は「スワップチェーン」というオブジェクトを作成する必要があります。
これは何なのかというと、少し説明が難しいオブジェクトなのですが、一言で言えば「画面に表示されようとしている画像の連なり」を表すオブジェクトです。
コンピュータのアニメーションというのは高速で絵が切り替わる、いわゆる「パラパラ漫画」の仕組みで実現されているというのは皆さんご存じですね。このパラパラ漫画の1コマ1コマは一般に「フレーム」と呼ばれます。ゲームをやっている人は良く聞くかも知れません。
そして(今どきの)一般的なコンピュータは、アニメーションを描画・表示する際に「描いている途中」が見えないよう、2枚以上のキャンバスを用意して、1枚を使って現在のフレームを表示させている裏で別の1枚に次のフレームを描画する、という仕組みを採用しています。

この「2枚以上のキャンバス」を管理し、そしてサーフェスと連携して表示するところまでやってくれるのがスワップチェーンというオブジェクトです。

Vulkanにおける表示処理は、このスワップチェーンを介して行うことになります。サーフェスを直接いじることはありません。
では、さっそくスワップチェーンを作成しましょう。下準備が必要です。スワップチェーンもサーフェスと同様に拡張機能なので、機能の有効化をする必要があります。前回までのコードに処理を追加していきます。
まずサーフェス作成処理の後の部分に、論理デバイスの作成までの処理を追加してください。復習ですね。
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);
問題なく動けばOKです。
スワップチェーンは論理デバイスのcreateSwapchainKHRメソッドで作成できるのですが、このままだと拡張機能が有効化されていないためスワップチェーンを作成しようとしてもエラーで落ちます。
機能の有効化のため、これからここに2か所ほど手を加えます。
まず、物理デバイスの選択処理に少し条件を加えます。処理を以下のように書き換えてください。
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;
}
}
std::vector<vk::ExtensionProperties> extProps = physicalDevices[i].enumerateDeviceExtensionProperties();
bool supportsSwapchainExtension = false;
for (size_t j = 0; j < extProps.size(); j++)
{
if (extProps[j].extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
supportsSwapchainExtension = true;
break;
}
}
if (existsGraphicsQueue && supportsSwapchainExtension)
{
physicalDevice = physicalDevices[i];
existsSuitablePhysicalDevice = true;
break;
}
}
これは何をしているかというと、enumerateDeviceExtensionProperties
メソッドでその物理デバイスがサポートしている拡張機能の一覧を取得しています。その中にスワップチェーンの拡張機能の名前が含まれていなければそのデバイスは使わない、という風にしています。
先ほども述べたようにスワップチェーンは標準の機能ではなく拡張機能であり、全ての物理デバイスがスワップチェーンの機能をサポートしているわけではありません。つまり「あっちのGPUではスワップチェーンが使えるけれどこっちのGPUでは使えない」ということがあり得ます。なので対応した物理デバイス(GPU)を選ぶ必要があったんですね。
VK_KHR_SWAPCHAIN_EXTENSION_NAME
というのは、拡張機能の名前を表す定数です。中身としては文字列"VK_KHR_swapchain"
です。名前の文字列をそのまま書いても良いですが、上記のように書いた方がタイプミスしたときにコンパイラがエラーを出してくれます。
次は論理デバイスの作成部を書き換えます。
再三言うようにスワップチェーンは拡張機能なので、機能を有効化する必要があります。サーフェスのときと同じですね。しかし、ひとつだけサーフェスのときと違う点があります。それはスワップチェーンは「デバイスレベル」の拡張機能だということです。
実は拡張機能には、インスタンス単位でオン・オフするものとデバイス単位でオン・オフするものがあります。サーフェスはインスタンスレベルの拡張機能なのでインスタンス作成時に有効化しましたが、スワップチェーンはデバイス単位での拡張機能なので、デバイス作成時に有効化する必要があるのです。
論理デバイス作成時のvk::DeviceCreateInfo構造体の初期化処理に、以下の内容を追加してください。
auto devRequiredExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
devCreateInfo.enabledExtensionCount = devRequiredExtensions.size();
devCreateInfo.ppEnabledExtensionNames = devRequiredExtensions.begin();
上の記述を追加し、エラーなく実行出来たら成功です。これでスワップチェーンの機能を有効化した論理デバイスが得られました。
ではようやくスワップチェーンの作成です。以下のコードを追加してください。
vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.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::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;
vk::UniqueSwapchainKHR swapchain = device->createSwapchainKHRUnique(swapchainCreateInfo);
結構面倒ですね。初期化用構造体に情報を指定して→論理デバイスのメソッドで作成、という部分は見慣れたものですが、初期化用構造体の中身を決めるところが少しややこしいです。
各メンバについて解説しましょう。
surfaceは画像の表示を行う先となるサーフェスです。これは分かりやすいですね。
swapchainCreateInfo.surface = surface.get();
この後が問題です。
imageFormat
やimageColorSpace
などでは、スワップチェーンが取り扱う画像の形式などを指定します。しかしこれらに指定できる値はサーフェスとデバイスの事情によって制限されるものであり、自由に決めることができるものではないのです。そのためきっちりと情報を集める必要があります。
vk::SurfaceCapabilitiesKHR surfaceCapabilities =
physicalDevice.getSurfaceCapabilitiesKHR(surface.get());
std::vector<vk::SurfaceFormatKHR> surfaceFormats =
physicalDevice.getSurfaceFormatsKHR(surface.get());
std::vector<vk::PresentModeKHR> surfacePresentModes =
physicalDevice.getSurfacePresentModesKHR(surface.get());
この3つのAPIは、「サーフェスの情報」および「ある物理デバイスが対象のサーフェスを扱う能力」の情報を取得するAPIです。ここで手に入れた情報を用いて種々の値を決めます。
swapchainCreateInfo.minImageCount = surfaceCapabilities.minImageCount + 1;
minImageCount
はスワップチェーンが扱うイメージの数です。上で「2枚以上のキャンバスを用いて」と説明しましたが、これがその数ですね。getSurfaceCapabilitiesKHRで得られたminImageCount(最小値)とmaxImageCount(最大値)の間の数なら何枚でも問題ないのですが、vulkan-tutorials.comの記述に従って最小値+1としています。
std::vector<vk::SurfaceFormatKHR> surfaceFormats = physicalDevice.getSurfaceFormatsKHR(surface.get());
(中略)
vk::SurfaceFormatKHR swapchainFormat = surfaceFormats[0];
(中略)
swapchainCreateInfo.imageFormat = swapchainFormat.format;
swapchainCreateInfo.imageColorSpace = swapchainFormat.colorSpace;
imageFormat
とimageColorSpace
はスワップチェーンの画像フォーマットを表します。ここに指定する値は、必ずgetSurfaceFormatsKHR
が返した配列に含まれる組み合わせでなければなりません。ここでは簡単のため、配列の一番最初のものを採用しています。
なお、ここで選択した値は後で使うので変数に保持しています。
swapchainCreateInfo.imageExtent = surfaceCapabilities.currentExtent;
imageExtent
はスワップチェーンの画像サイズを表します。getSurfaceCapabilitiesKHRで得られたminImageExtent(最小値)とmaxImageExtent(最大値)の間でなければなりません。currentExtentで現在のサイズが得られるため、それを指定しています。
このシリーズではウィンドウを640×480で初期化しているため、currentExtentの中身はほぼ間違いなく640×480になっていることでしょう。
swapchainCreateInfo.preTransform = surfaceCapabilities.currentTransform;
preTransform
は表示時の画面反転・画面回転などのオプションを表します。これもgetSurfaceCapabilitiesKHRの戻り値に依存します。supportedTransformsでサポートされている中に含まれるものでなければなりません。currentTransformを設定すれば現在の画面設定が反映されるので無難です。
std::vector<vk::PresentModeKHR> surfacePresentModes = physicalDevice.getSurfacePresentModesKHR(surface.get());
(中略)
vk::PresentModeKHR swapchainPresentMode = surfacePresentModes[0];
(中略)
swapchainCreateInfo.presentMode = swapchainPresentMode;
presentMode
は表示処理のモードを示すものです。大体お察しかもしれませんが、getSurfacePresentModesKHRの戻り値の配列に含まれる値でないといけません。ここでは簡単のため、最初の要素を参照しています。
swapchainCreateInfo.imageArrayLayers = 1;
swapchainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment;
swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive;
swapchainCreateInfo.clipped = VK_TRUE;
この4つについては固定で構いません。これについてはここでは詳しい説明は避けます。
気になる人は公式ドキュメントを参照してください。
これにてようやくスワップチェーンが作成できました。ここまでのコードを実行してみましょう。
問題なく動いた方もいるかもしれませんが、環境によってはエラーが出る可能性があります。なぜでしょう。
デバイスとサーフェスの事情が問題になってくるという話は上で少し触れましたね。デバイスとサーフェスの相性次第では、getSurfacePresentModesKHRとgetSurfaceFormatsKHRの戻り値が空ということがあり得ます。この場合は使えるモードがなく、死にます。
物理デバイスを選択する部分の処理で、デバイスがサーフェスを間違いなくサポートしていることを確かめましょう。
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;
}
}
std::vector<vk::ExtensionProperties> extProps = physicalDevices[i].enumerateDeviceExtensionProperties();
bool supportsSwapchainExtension = false;
for (size_t j = 0; j < extProps.size(); j++)
{
if (extProps[j].extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
supportsSwapchainExtension = true;
break;
}
}
bool supportsSurface =
!physicalDevices[i].getSurfaceFormatsKHR(surface.get()).empty() ||
!physicalDevices[i].getSurfacePresentModesKHR(surface.get()).empty();
if (existsGraphicsQueue && supportsSwapchainExtension && supportsSurface)
{
physicalDevice = physicalDevices[i];
existsSuitablePhysicalDevice = true;
break;
}
}
これでサーフェスと相性の悪いGPUを事前に弾くことができました。
今回はスワップチェーンを作成しました。次回はスワップチェーンのイメージへの描画をします。3章までの内容の復習と応用になります。
#include <iostream>
#include <vulkan/vulkan.hpp>
#include <GLFW/glfw3.h>
const uint32_t screenWidth = 640;
const uint32_t screenHeight = 480;
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)
{
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 (extProps[j].extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
supportsSwapchainExtension = true;
break;
}
}
bool supportsSurface =
!physicalDevices[i].getSurfaceFormatsKHR(surface.get()).empty() ||
!physicalDevices[i].getSurfacePresentModesKHR(surface.get()).empty();
if (existsGraphicsQueue && supportsSwapchainExtension && supportsSurface)
{
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::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.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::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;
vk::UniqueSwapchainKHR swapchain = device->createSwapchainKHRUnique(swapchainCreateInfo);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
glfwTerminate();
return 0;
}