Vulkan Tutorial (Drawing a triangle/Presentation/Swap chain) 日本語訳

この記事は [Swap chain - Vulkan Tutorial](https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain) の日本語訳です。

# Swap chain

Vulkan は「デフォルトフレームバッファ」というコンセプトを持たないので、画面に表示する前にレンダリングするためのバッファを所有する基盤が必要です。その基盤はスワップチェインとして知られ、Vulkanでは明示的に作成しなければなりません。スワップチェインは本質的には、画面に表示されるのを待っている画像のキューです。アプリケーションは画像を取得して描画し、それをキューに返却します。具体的にキューがどのように動作するかや、キューからの画像を表示する条件は、スワップチェインがどのようにセットアップされたかに依存しますが、スワップチェインの一般的な目的は、画像の表示と画面のリフレッシュレートの同期を取ることです。

## Checking for swap chain support

全てのグラフィックスカードが、画像を直接画面に表示する機能を持っているとは限りません。その理由は様々で、例えば、それがサーバ用に設計されていて、ディスプレイ出力を一切持っていない、などです。次に、画像表示はウィンドウシステムとウィンドウに関連するサーフェスに深く結びついているので、それらは実際にはVulkanコアの部分ではありません。`VK_KHR_swapchain`デバイス拡張を、それがサポートされているか調べた後に有効化する必要があります。

そのために、まず`isDeviceSuitable`関数を拡張して、この拡張機能がサポートされているかどうかチェックします。以前、[`VkPhysicalDevice`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDevice.html)がサポートしている拡張機能をリストアップする方法を見たので、これを行うのは簡単でしょう。Vulkanヘッダは`VK_KHR_SWAPCHAIN_EXTENSION_NAME`という素晴らしいマクロを提供していて、これは`VK_KHR_swapchain`として定義されています。このマクロを使うことによって、スペルミスをコンパイラが検出できるという利点があります。

最初に、有効にするバリデーションレイヤのリストと同じように、必要なデバイス拡張のリストを宣言してください。

```cpp
const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
```

次に、`checkDeviceExtensionSupport`という新しい関数を追加して、`isDeviceSuitable`から追加のチェックとして呼び出します。

```cpp
bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);

    bool extensionsSupported = checkDeviceExtensionSupport(device);

    return indices.isComplete() && extensionsSupported;
}

bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    return true;
}
```

拡張機能を列挙して、必要な拡張機能が全てその中にあるかチェックするように、関数の中身を変更してください。

```cpp
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    uint32_t extensionCount;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

    std::vector<VkExtensionProperties> availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

    std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

    for (const auto& extension : availableExtensions) {
        requiredExtensions.erase(extension.extensionName);
    }

    return requiredExtensions.empty();
}
```

ここでは、未確認の必須の拡張機能を表現するのに、文字列の`set`を使うようにしました。この方法で、利用可能な拡張機能を列挙していく中で簡単にそれらに印をつけることができます。もちろん、`checkValidationLayerSupport`のようなネストしたループを使うこともできます。パフォーマンスの違いは重要ではありません。ここで、コードを実行してあなたのグラフィックスカードが実際にスワップチェインを作成する能力を持っているか確認してください。前章で確認したように、プレゼンテーションキューが利用可能であることは、スワップチェイン拡張機能がサポートされていることを暗に意味します。しかし、物事を明確にするのは良いことなので、この拡張機能は明示的に有効にする必要があります。

## Enabling device extensions

スワップチェインを使用するには、まず`VK_KHR_swapchain`拡張を有効にする必要があります。この拡張を有効にするのに必要なのは、論理デバイス作成の構造体に小さな変更を加えることだけです。

```cpp
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
```

その際、既存の`createInfo.enabledExtensionCount = 0;`の行を置き換えることに注意してください。

## Querying details of swap chain support

スワップチェインが利用可能かどうかをチェックするだけでは十分ではありません。なぜなら、実際にはウィンドウサーフェスと互換性がないかもしれないからです。また、スワップチェインの作成は、インスタンスやデバイスの作成よりも多くの設定を伴うので、次に進む前に、いくつかの詳細を問い合わせる必要があります。

チェックが必要なプロパティは、基本的に3種類あります。
* 基本的なサーフェスの能力(スワップチェイン内の画像の最小・最大値、画像の幅と高さの最小・最大値)
* サーフェスのフォーマット(ピクセルフォーマット、カラースペース)
* 利用可能なプレゼンテーションモード

`findQueueFamilies`と同様、問い合わされたときに詳細を渡すために構造体を使います。前述した3種類のプロパティは、次の構造体と構造体のリストの形で表されます。

```cpp
struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};
```

この構造体を返す`querySwapChainSupport`関数を新たに追加します。

```cpp
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
    SwapChainSupportDetails details;

    return details;
}
```

この節では、この情報を含む構造体をどうやって取得するか説明します。構造体の意味や実際に含まれているデータについては、次の節で述べます。

基本的なサーフェスの能力から始めましょう。これらのプロパティの取得方法は簡単で、`VkSurfaceCapabilitiesKHR`構造体として返されます。

```cpp
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
```

この関数は、指定された[`VkPhysicalDevice`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDevice.html)とサーフェスウィンドウ`VkSurfaceKHR`を、サポートされている機能を判断するときの考慮に入れます。これらはスワップチェインの主要なコンポーネントなので、サポートを問い合わせる関数は全て、パラメータの最初にこの2つを取ります。

次のステップはサポートされているサーフェスのフォーマットを問い合わせることです。構造体のリストなので、2度の関数呼び出しというおなじみの儀式に従います。

```cpp
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

if (formatCount != 0) {
    details.formats.resize(formatCount);
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
```

vectorが、利用可能なフォーマットを全て保持できるようにリサイズされていることを確認してください。最後に、`vkGetPhysicalDeviceSurfacePresentModesKHR`を使った全く同じ方法で、サポートされているプレゼンテーションモードを問い合わせます。

```cpp
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

if (presentModeCount != 0) {
    details.presentModes.resize(presentModeCount);
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
```

全ての詳細は構造体に格納したので、スワップチェインのサポートが十分かどうか検証するために使えるように、`isDeviceSuitable`をもう一度拡張しましょう。今あるウィンドウサーフェスで、1つ以上の画像フォーマットと1つ以上のプレゼンテーションモードがサポートされていれば、このチュートリアルではスワップチェインのサポートは十分です。

```cpp
bool swapChainAdequate = false;
if (extensionsSupported) {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
    swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
```

拡張機能が利用可能であることを確認した後にのみ、スワップチェインのサポートを問い合わせることが重要です。関数の最後の行は次のように変更します。

```cpp
return indices.isComplete() && extensionsSupported && swapChainAdequate;
```

## Choosing the right settings for the swap chain

もし`swapChainAdequate`の条件が満たされていればサポートは確実に十分ですが、最適性の異なる多くのモードがまだ存在する可能性があります。私達は、可能な最良のスワップチェインの設定を探すために、いくつかの関数を書きます。決定しなければいけない3種類の設定は：

* サーフェスのフォーマット(カラー深度)
* プレゼンテーションモード(画像をスクリーンにスワップする条件)
* スワップの大きさ(スワップチェイン内の画像の解像度)

それぞれの設定で理想的な値があり、もしそれが利用可能であればそれを使い、そうでなければ次に良いものを探すロジックを作ります。

### Surface format

設定関数は次のように始まります。後で`SwapChainSupportDetails`構造体の`formats`メンバーを引数として追加します。

```cpp
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {

}
```

それぞれの`VkSurfaceFormatKHR`は`format`と`colorSpace`メンバーを含んでいます。`format`メンバーはカラーチャンネルと型を指定します。例えば、`VK_FORMAT_B8G8R8A8_SRGB`はB、G、Rそしてアルファチャンネルをこの順番で、それぞれ8ビット符号無し整数型、合計で1ピクセルあたり32ビットで格納することを意味します。`colorSpace`メンバーは、SRGB色空間がサポートされているかどうかを`VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`フラグを使って表します。かつて古いバージョンの仕様書では、このフラグは`VK_COLORSPACE_SRGB_NONLINEAR_KHR`と呼ばれていたことに注意してください。

色空間としてSRGBが利用可能であれば、そのほうが[知覚する色が正確なので](http://stackoverflow.com/questions/12524623/)、そちらを使うようにします。これは、私達が後で使うテクスチャのような画像に対しては、より標準的な色空間でもあります。そのため、SRGBカラーフォーマットを使うこととし、最も一般的なものは`VK_FORMAT_B8G8R8A8_SRGB`となります。

リストを巡回して、適切な組み合わせが利用可能であるかどうか見てみましょう。

```cpp
for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}
```

これに失敗したとき、利用可能なフォーマットを、どれだけ"良い"かに基づいてランキングすることもできますが、ほとんどの場合は、最初に見つかったフォーマットに決定するだけで大丈夫です。

```cpp
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}
```

### Presentation mode

プレゼンテーションモードは、おそらくスワップチェインの設定のなかで最も重要です。なぜなら、それが画面に画像を表示する、実際の条件を表しているからです。Vulkanでは、4つのモードが利用可能です。

* `VK_PRESENT_MODE_IMMEDIATE_KHR`：アプリケーションによってサブミットされた画像はすぐに画面に送られ、ティアリングを引き起こす可能性がある
* `VK_PRESENT_MODE_FIFO_KHR`：スワップチェインはキューであり、ディスプレイはリフレッシュされたときにキューの前方から画像を取得し、プログラムはレンダリングした画像をキューの後方に挿入する。キューがいっぱいのときはプログラムは待たなければいけない。これは最近のゲームにおける垂直同期と一番似ている。ディスプレイがリフレッシュされる瞬間のことは"Vブランク"として知られる。
* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`：このモードは、アプリケーションが遅れて、Vブランク時にキューが空のときだけ、1つ前のモードと違う。次のVブランクを待つ代わりに、届いた画像をすぐに転送する。これはティアリングが発生する可能性がある。
* `VK_PRESENT_MODE_MAILBOX_KHR`：これは2番目のモードの変種。キューがいっぱいのときにアプリケーションをブロックする代わりに、既にキューに入っている画像を単純に新しい画像で置き換える。このモードは、できる限り高速にフレームをレンダリングしながらティアリングを避けられ、標準の垂直同期よりもレイテンシーの問題を少なくするのに使用できる。これは一般的に"トリプルバッファリング"として知られるが、3つのバッファが存在するだけでは必ずしもフレームレートが向上するわけではない。

常に利用可能であることが保証されているのは`VK_PRESENT_MODE_FIFO_KHR`だけなので、利用可能なもののなかで最適なモードを探す関数を書く必要があります。

```cpp
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    return VK_PRESENT_MODE_FIFO_KHR;
}
```

個人的には`VK_PRESENT_MODE_MAILBOX_KHR`は電力使用量が重要でないなら良いトレードオフになると思います。これは、できるだけVブランク直前の最新の画像をレンダリングすることによって、低レイテンシーを維持しながらティアリングを避けることができます。モバイル機器では、電力使用量がより重要なので、代わりに`VK_PRESENT_MODE_FIFO_KHR`を使いたいかもしれません。今は、リストを巡回して`VK_PRESENT_MODE_MAILBOX_KHR`が利用可能かどうか見ることにしましょう。

```cpp
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}
```

### Swap extent

主要なプロパティは残り1つとなりましたので、最後の一つの関数を追加します。

```cpp
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}
```

スワップ範囲とはスワップチェインの画像の解像度のことで、ほとんど常に、描画するウィンドウのピクセル単位の解像度と同じです（詳細は後述します）。可能な解像度の範囲は、`VkSurfaceCapabilitiesKHR`構造体の中で定義されています。Vulkanは、`currentExtent`メンバーの幅と高さに値を設定することで、ウィンドウにマッチする解像度を伝えてくれます。しかし、一部のウィンドウマネージャはここで違う動作を許可しており、これは`currentExtent`の幅と高さに特殊な値（`uint32_t`の最大値）を設定することによって示されます。この場合、`minImageExtent`と`maxImageExtent`の範囲内でウィンドウに最も適合する解像度を選択します。ただし、解像度を正しい単位で指定しなければいけません。

GLFWは大きさをはかるのに2つの単位を使います：ピクセルと[スクリーン座標](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems)です。例えば、以前ウィンドウを作成するときに指定した解像度`{WIDTH, HEIGHT}`はスクリーン座標でした。しかし、Vulkanはピクセル単位で動作するので、スワップチェインの大きさもピクセル単位で指定する必要があります。残念なことに、高DPIのディスプレイ(AppleのRetinaディスプレイのような)を使っている場合、スクリーン座標とピクセルは一致しません。ピクセル密度が高いため、ウィンドウのピクセル単位での解像度はスクリーン座標での解像度より大きくなります。だからもし、Vulkanがスワップ範囲を修正してくれない場合、元の`{WIDTH, HEIGHT}`を単純に使うことはできません。代わりに、`glfwGetFramebufferSize`を使ってウィンドウのピクセル単位での解像度を問い合わせてから、画像の大きさの最小と最大に対してマッチングさせる必要があります。

```cpp
#include <cstdint> // Necessary for uint32_t
#include <limits> // Necessary for std::numeric_limits
#include <algorithm> // Necessary for std::clamp

...

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
        return capabilities.currentExtent;
    } else {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);

        VkExtent2D actualExtent = {
            static_cast<uint32_t>(width),
            static_cast<uint32_t>(height)
        };

        actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
        actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);

        return actualExtent;
    }
}
```

ここで`width`と`height`の値を、実装でサポートされている最小値と最大値の間に拘束するために、`clamp`関数が使われています。

## Creating the swap chain

このように、ランタイムに決定しなければいけない選択を助けてくれるヘルパー関数が全てそろったので、ついにスワップチェインを作成するのに必要な情報を全て手に入れました。

これらの呼び出し結果を受け取るところから始まる`createSwapChain`関数を作成し、論理デバイスを作成した後に`initVulkan`から呼び出すようにします。

```cpp
void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
}

void createSwapChain() {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

    VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
    VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
    VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
}
```

これらのプロパティ以外に、スワップチェインに何枚の画像を持たせるかを決定する必要があります。実装は動作するために必要な最低限の数を指定します。

```cpp
uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
```

しかし、単純にこの最小値を採用することは、次にレンダリングする画像を取得する前にドライバの内部処理が完了するのを待たなければ行けないときがあるということを意味しています。ですので、最小値より1以上大きい値を要求することを推奨します。

```cpp
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
```

このとき、画像の枚数の最大値を超えないようにしなければいけません。ここで、`0`は最大値が無いことを表す特別な値です。

```cpp
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
}
```

Vulkanオブジェクトでは恒例となりましたが、スワップチェインオブジェクトを作成するために大きな構造体を埋める必要があります。お馴染みのものから始まります：

```cpp
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
```

スワップチェインに結び付けられるサーフェスを指定した後、スワップチェインの画像の詳細を指定します。

```cpp
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
```

`imageArrayLayers`はそれぞれの画像が何枚のレイヤから成るかを指定します。これは、立体視3Dアプリケーションを開発しない限りは、常に`1`となります。`imageUsage`ビットフィールドは、スワップチェインの画像をどのような種類の操作に使うかを指定します。このチュートリアルでは、それらに直接レンダリングするので、カラーアタッチメントとして使うと指定します。また、ポストプロセッシングのような操作をするために、別の画像にいったんレンダリングするといったことも可能です。その場合、代わりに`VK_IMAGE_USAGE_TRANSFER_DST_BIT`のような値を設定し、メモリ操作を用いてレンダリングした画像をスワップチェイン画像に転送します。

```cpp
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    createInfo.queueFamilyIndexCount = 2;
    createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0; // Optional
    createInfo.pQueueFamilyIndices = nullptr; // Optional
}
```

次に、複数のキューファミリーをまたいで使われるスワップチェイン画像を、どのように扱うかを指定する必要があります。私達のアプリケーションで言えば、グラフィックスキューファミリーとプレゼンテーションキューが異なっているような場合です。グラフィックスキューからのスワップチェイン画像に描画し、それをプレゼンテーションキューでサブミットします。複数のキューからアクセスされる画像の扱い方は２種類あります。

* `VK_SHARING_MODE_EXCLUSIVE`：画像は一度に１つのキューファミリーによって所有され、別のキューファミリーで使用するときには先に明示的に所有権を譲渡しなければいけない。このオプションは最高のパフォーマンスを提供する。
* `VK_SHARING_MODE_CONCURRENT`：画像は明示的な所有権の譲渡無しに、複数のキューファミリーをまたがって使用できる。

キューファミリーが異なっている場合、このチュートリアルでは共同モード(concurrent mode)を使います。これは所有権の章を設けるのを避けるためで、というのも、これらには後ほど説明したほうが良い概念が含まれているからです。共同モードは、どのキューファミリーの所有権が共有されるかを、`queueFamilyIndexCount`と`pQueueFamilyIndices`パラメータを使って、前もって指定する必要があります。もし、ほとんどのハードウェアではそうであるように、グラフィックスキューとプレゼンテーションキューが同じなら、排他モード(exclusive mode)を選択するべきです。なぜなら、共同モードは少なくとも２つの異なるキューファミリーを指定するように要求するからです。

```cpp
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
```

90度時計回りに回転や、水平反転などのような、スワップチェイン画像に適用する一定の変換を、もしそれがサポートされていれば(`capabilities`の`supportedTransforms`)、指定することができます。どんな変換もしたくない場合、単純に現在の変換を指定してください。

```cpp
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
```

`compositeAlpha`は、ウィンドウシステム上で他のウィンドウとのブレンドのためにアルファチャンネルを使うかどうかを指定します。ほとんどの場合、アルファチャンネルを単に無視したいでしょうから、`VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`を指定します。

```cpp
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
```

`presentMode`メンバは見たままです。`clipped`メンバが`VK_TRUE`に設定されているときは、例えば他のウィンドウが前面にあるときなど、見えないピクセルの色について気にしないことを意味します。これらのピクセルを読み取って結果を取得する必要が無い限り、クリッピングを有効にすることで最良のパフォーマンスを得ることができます。

```cpp
createInfo.oldSwapchain = VK_NULL_HANDLE;
```

最後に残ったのは`oldSwapChain`です。Vulkanではアプリケーションの実行中に、ウィンドウがリサイズされたなどの理由で、スワップチェインが無効または最適でない状態になる可能性があります。この場合、実際にはスワップチェインをいちから再作成する必要があり、古いものへの参照をこのフィールドで指定しなければなりません。これは複雑な話題であり、[将来の章](https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation)でより詳しく学びます。今は、１つのスワップチェインだけを作成することとします。

それでは、`VkSwapchainKHR`オブジェクトを格納するメンバを追加します。

```cpp
VkSwapchainKHR swapChain;
```

スワップチェインを作成するには、単純に`vkCreateSwapchainKHR`を呼び出すだけです。

```cpp
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
    throw std::runtime_error("failed to create swap chain!");
}
```

引数は論理デバイス、スワップチェイン作成情報、オプションのカスタムアロケータ、ハンドルを格納する変数へのポインタです。特筆すべきことはありません。これは`vkDestroySwapchainKHR`を使って、デバイスより前にクリーンアップされる必要があります。

```cpp
void cleanup() {
    vkDestroySwapchainKHR(device, swapChain, nullptr);
    ...
}
```

それでは、アプリケーションを実行してスワップチェインの作成に成功するか確認してください！もしこの時点で`vkCreateSwapchainKHR`でのアクセス違反のエラーが発生したり、`Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll`のようなメッセージが表示された場合は、Steamオーバーレイレイヤに関する[FAQエントリ](https://vulkan-tutorial.com/FAQ)を参照してください。

バリデーションレイヤを有効にした状態で`createInfo.imageExtent = extent;`の行を削除してみてください。バリデーションレイヤのうちの一つが直ちに間違いを見つけ、手助けとなるメッセージが表示されるところを見ることができるでしょう。
![](https://vulkan-tutorial.com/images/swap_chain_validation_layer.png)

## Retrieving the swap chain images

これでスワップチェインは作成できましたので、あとはその中の[`VkImage`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkImage.html)のハンドルを取得するだけです。後の章でレンダリング処理をする際に、これらを参照します。ハンドルを格納するためのメンバをクラスに追加します。

```cpp
std::vector<VkImage> swapChainImages;
```

これらの画像はスワップチェインの実装によって作成され、スワップチェインが破棄されるときに自動的にクリーンアップされますので、クリーンアップのコードを追加する必要はありません。

`createSwapChain`関数の最後、`vkCreateSwapchainKHR`呼び出しのすぐ後に、ハンドルを取得するコードを追加します。この取得処理は、以前Vulkanからオブジェクトの配列を取得した時と似ています。スワップチェインの画像の枚数として、最小値だけを指定したのを思い出してください。実装にはそれ以上の枚数でスワップチェインを作成することが許可されています。そのため、はじめに`vkGetSwapchainImagesKHR`で最終的な画像の枚数を取得し、コンテナをリサイズしてから、最後にハンドルを取得するために再び関数を呼び出します。

```cpp
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
```

最後に、スワップチェインの画像として選択したフォーマットとサイズを、メンバ変数に格納します。これらは後の章で必要になるでしょう。

```cpp
VkSwapchainKHR swapChain;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
```

これで私達は、描画できてウィンドウに表示することができる画像のセットを手に入れました。次の章では、これらの画像をレンダーターゲットとしてセットアップする方法を説明し、実際のグラフィックスパイプラインとドローコマンドについて見ていきます。

[C++ code](https://vulkan-tutorial.com/code/06_swap_chain_creation.cpp)

前の章
次の章