この記事は Swap chain - Vulkan Tutorial の日本語訳です。
Swap chain
Vulkan は「デフォルトフレームバッファ」というコンセプトを持たないので、画面に表示する前にレンダリングするためのバッファを所有する基盤が必要です。その基盤はスワップチェインとして知られ、Vulkanでは明示的に作成しなければなりません。スワップチェインは本質的には、画面に表示されるのを待っている画像のキューです。アプリケーションは画像を取得して描画し、それをキューに返却します。具体的にキューがどのように動作するかや、キューからの画像を表示する条件は、スワップチェインがどのようにセットアップされたかに依存しますが、スワップチェインの一般的な目的は、画像の表示と画面のリフレッシュレートの同期を取ることです。
Checking for swap chain support
全てのグラフィックスカードが、画像を直接画面に表示する機能を持っているとは限りません。その理由は様々で、例えば、それがサーバ用に設計されていて、ディスプレイ出力を一切持っていない、などです。次に、画像表示はウィンドウシステムとウィンドウに関連するサーフェスに深く結びついているので、それらは実際にはVulkanコアの部分ではありません。VK_KHR_swapchain
デバイス拡張を、それがサポートされているか調べた後に有効化する必要があります。
そのために、まずisDeviceSuitable
関数を拡張して、この拡張機能がサポートされているかどうかチェックします。以前、VkPhysicalDevice
がサポートしている拡張機能をリストアップする方法を見たので、これを行うのは簡単でしょう。VulkanヘッダはVK_KHR_SWAPCHAIN_EXTENSION_NAME
という素晴らしいマクロを提供していて、これはVK_KHR_swapchain
として定義されています。このマクロを使うことによって、スペルミスをコンパイラが検出できるという利点があります。
最初に、有効にするバリデーションレイヤのリストと同じように、必要なデバイス拡張のリストを宣言してください。
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
次に、checkDeviceExtensionSupport
という新しい関数を追加して、isDeviceSuitable
から追加のチェックとして呼び出します。
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
return indices.isComplete() && extensionsSupported;
}
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
return true;
}
拡張機能を列挙して、必要な拡張機能が全てその中にあるかチェックするように、関数の中身を変更してください。
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
拡張を有効にする必要があります。この拡張を有効にするのに必要なのは、論理デバイス作成の構造体に小さな変更を加えることだけです。
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
その際、既存のcreateInfo.enabledExtensionCount = 0;
の行を置き換えることに注意してください。
Querying details of swap chain support
スワップチェインが利用可能かどうかをチェックするだけでは十分ではありません。なぜなら、実際にはウィンドウサーフェスと互換性がないかもしれないからです。また、スワップチェインの作成は、インスタンスやデバイスの作成よりも多くの設定を伴うので、次に進む前に、いくつかの詳細を問い合わせる必要があります。
チェックが必要なプロパティは、基本的に3種類あります。
- 基本的なサーフェスの能力(スワップチェイン内の画像の最小・最大値、画像の幅と高さの最小・最大値)
- サーフェスのフォーマット(ピクセルフォーマット、カラースペース)
- 利用可能なプレゼンテーションモード
findQueueFamilies
と同様、問い合わされたときに詳細を渡すために構造体を使います。前述した3種類のプロパティは、次の構造体と構造体のリストの形で表されます。
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
この構造体を返すquerySwapChainSupport
関数を新たに追加します。
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
return details;
}
この節では、この情報を含む構造体をどうやって取得するか説明します。構造体の意味や実際に含まれているデータについては、次の節で述べます。
基本的なサーフェスの能力から始めましょう。これらのプロパティの取得方法は簡単で、VkSurfaceCapabilitiesKHR
構造体として返されます。
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
この関数は、指定されたVkPhysicalDevice
とサーフェスウィンドウVkSurfaceKHR
を、サポートされている機能を判断するときの考慮に入れます。これらはスワップチェインの主要なコンポーネントなので、サポートを問い合わせる関数は全て、パラメータの最初にこの2つを取ります。
次のステップはサポートされているサーフェスのフォーマットを問い合わせることです。構造体のリストなので、2度の関数呼び出しというおなじみの儀式に従います。
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
vectorが、利用可能なフォーマットを全て保持できるようにリサイズされていることを確認してください。最後に、vkGetPhysicalDeviceSurfacePresentModesKHR
を使った全く同じ方法で、サポートされているプレゼンテーションモードを問い合わせます。
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つ以上のプレゼンテーションモードがサポートされていれば、このチュートリアルではスワップチェインのサポートは十分です。
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
拡張機能が利用可能であることを確認した後にのみ、スワップチェインのサポートを問い合わせることが重要です。関数の最後の行は次のように変更します。
return indices.isComplete() && extensionsSupported && swapChainAdequate;
Choosing the right settings for the swap chain
もしswapChainAdequate
の条件が満たされていればサポートは確実に十分ですが、最適性の異なる多くのモードがまだ存在する可能性があります。私達は、可能な最良のスワップチェインの設定を探すために、いくつかの関数を書きます。決定しなければいけない3種類の設定は:
- サーフェスのフォーマット(カラー深度)
- プレゼンテーションモード(画像をスクリーンにスワップする条件)
- スワップの大きさ(スワップチェイン内の画像の解像度)
それぞれの設定で理想的な値があり、もしそれが利用可能であればそれを使い、そうでなければ次に良いものを探すロジックを作ります。
Surface format
設定関数は次のように始まります。後でSwapChainSupportDetails
構造体のformats
メンバーを引数として追加します。
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が利用可能であれば、そのほうが知覚する色が正確なので、そちらを使うようにします。これは、私達が後で使うテクスチャのような画像に対しては、より標準的な色空間でもあります。そのため、SRGBカラーフォーマットを使うこととし、最も一般的なものはVK_FORMAT_B8G8R8A8_SRGB
となります。
リストを巡回して、適切な組み合わせが利用可能であるかどうか見てみましょう。
for (const auto& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
これに失敗したとき、利用可能なフォーマットを、どれだけ"良い"かに基づいてランキングすることもできますが、ほとんどの場合は、最初に見つかったフォーマットに決定するだけで大丈夫です。
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
だけなので、利用可能なもののなかで最適なモードを探す関数を書く必要があります。
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
が利用可能かどうか見ることにしましょう。
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つとなりましたので、最後の一つの関数を追加します。
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
}
スワップ範囲とはスワップチェインの画像の解像度のことで、ほとんど常に、描画するウィンドウのピクセル単位の解像度と同じです(詳細は後述します)。可能な解像度の範囲は、VkSurfaceCapabilitiesKHR
構造体の中で定義されています。Vulkanは、currentExtent
メンバーの幅と高さに値を設定することで、ウィンドウにマッチする解像度を伝えてくれます。しかし、一部のウィンドウマネージャはここで違う動作を許可しており、これはcurrentExtent
の幅と高さに特殊な値(uint32_t
の最大値)を設定することによって示されます。この場合、minImageExtent
とmaxImageExtent
の範囲内でウィンドウに最も適合する解像度を選択します。ただし、解像度を正しい単位で指定しなければいけません。
GLFWは大きさをはかるのに2つの単位を使います:ピクセルとスクリーン座標です。例えば、以前ウィンドウを作成するときに指定した解像度{WIDTH, HEIGHT}
はスクリーン座標でした。しかし、Vulkanはピクセル単位で動作するので、スワップチェインの大きさもピクセル単位で指定する必要があります。残念なことに、高DPIのディスプレイ(AppleのRetinaディスプレイのような)を使っている場合、スクリーン座標とピクセルは一致しません。ピクセル密度が高いため、ウィンドウのピクセル単位での解像度はスクリーン座標での解像度より大きくなります。だからもし、Vulkanがスワップ範囲を修正してくれない場合、元の{WIDTH, HEIGHT}
を単純に使うことはできません。代わりに、glfwGetFramebufferSize
を使ってウィンドウのピクセル単位での解像度を問い合わせてから、画像の大きさの最小と最大に対してマッチングさせる必要があります。
#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
から呼び出すようにします。
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);
}
これらのプロパティ以外に、スワップチェインに何枚の画像を持たせるかを決定する必要があります。実装は動作するために必要な最低限の数を指定します。
uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
しかし、単純にこの最小値を採用することは、次にレンダリングする画像を取得する前にドライバの内部処理が完了するのを待たなければ行けないときがあるということを意味しています。ですので、最小値より1以上大きい値を要求することを推奨します。
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
このとき、画像の枚数の最大値を超えないようにしなければいけません。ここで、0
は最大値が無いことを表す特別な値です。
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
Vulkanオブジェクトでは恒例となりましたが、スワップチェインオブジェクトを作成するために大きな構造体を埋める必要があります。お馴染みのものから始まります:
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
スワップチェインに結び付けられるサーフェスを指定した後、スワップチェインの画像の詳細を指定します。
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
のような値を設定し、メモリ操作を用いてレンダリングした画像をスワップチェイン画像に転送します。
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
}
次に、複数のキューファミリーをまたいで使われるスワップチェイン画像を、どのように扱うかを指定する必要があります。私達のアプリケーションで言えば、グラフィックスキューファミリーとプレゼンテーションキューが異なっているような場合です。グラフィックスキューからのスワップチェイン画像に描画し、それをプレゼンテーションキューでサブミットします。複数のキューからアクセスされる画像の扱い方は2種類あります。
-
VK_SHARING_MODE_EXCLUSIVE
:画像は一度に1つのキューファミリーによって所有され、別のキューファミリーで使用するときには先に明示的に所有権を譲渡しなければいけない。このオプションは最高のパフォーマンスを提供する。 -
VK_SHARING_MODE_CONCURRENT
:画像は明示的な所有権の譲渡無しに、複数のキューファミリーをまたがって使用できる。
キューファミリーが異なっている場合、このチュートリアルでは共同モード(concurrent mode)を使います。これは所有権の章を設けるのを避けるためで、というのも、これらには後ほど説明したほうが良い概念が含まれているからです。共同モードは、どのキューファミリーの所有権が共有されるかを、queueFamilyIndexCount
とpQueueFamilyIndices
パラメータを使って、前もって指定する必要があります。もし、ほとんどのハードウェアではそうであるように、グラフィックスキューとプレゼンテーションキューが同じなら、排他モード(exclusive mode)を選択するべきです。なぜなら、共同モードは少なくとも2つの異なるキューファミリーを指定するように要求するからです。
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
90度時計回りに回転や、水平反転などのような、スワップチェイン画像に適用する一定の変換を、もしそれがサポートされていれば(capabilities
のsupportedTransforms
)、指定することができます。どんな変換もしたくない場合、単純に現在の変換を指定してください。
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
compositeAlpha
は、ウィンドウシステム上で他のウィンドウとのブレンドのためにアルファチャンネルを使うかどうかを指定します。ほとんどの場合、アルファチャンネルを単に無視したいでしょうから、VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
を指定します。
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
presentMode
メンバは見たままです。clipped
メンバがVK_TRUE
に設定されているときは、例えば他のウィンドウが前面にあるときなど、見えないピクセルの色について気にしないことを意味します。これらのピクセルを読み取って結果を取得する必要が無い限り、クリッピングを有効にすることで最良のパフォーマンスを得ることができます。
createInfo.oldSwapchain = VK_NULL_HANDLE;
最後に残ったのはoldSwapChain
です。Vulkanではアプリケーションの実行中に、ウィンドウがリサイズされたなどの理由で、スワップチェインが無効または最適でない状態になる可能性があります。この場合、実際にはスワップチェインをいちから再作成する必要があり、古いものへの参照をこのフィールドで指定しなければなりません。これは複雑な話題であり、将来の章でより詳しく学びます。今は、1つのスワップチェインだけを作成することとします。
それでは、VkSwapchainKHR
オブジェクトを格納するメンバを追加します。
VkSwapchainKHR swapChain;
スワップチェインを作成するには、単純にvkCreateSwapchainKHR
を呼び出すだけです。
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}
引数は論理デバイス、スワップチェイン作成情報、オプションのカスタムアロケータ、ハンドルを格納する変数へのポインタです。特筆すべきことはありません。これはvkDestroySwapchainKHR
を使って、デバイスより前にクリーンアップされる必要があります。
void cleanup() {
vkDestroySwapchainKHR(device, swapChain, nullptr);
...
}
それでは、アプリケーションを実行してスワップチェインの作成に成功するか確認してください!もしこの時点でvkCreateSwapchainKHR
でのアクセス違反のエラーが発生したり、Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll
のようなメッセージが表示された場合は、Steamオーバーレイレイヤに関するFAQエントリを参照してください。
バリデーションレイヤを有効にした状態でcreateInfo.imageExtent = extent;
の行を削除してみてください。バリデーションレイヤのうちの一つが直ちに間違いを見つけ、手助けとなるメッセージが表示されるところを見ることができるでしょう。
Retrieving the swap chain images
これでスワップチェインは作成できましたので、あとはその中のVkImage
のハンドルを取得するだけです。後の章でレンダリング処理をする際に、これらを参照します。ハンドルを格納するためのメンバをクラスに追加します。
std::vector<VkImage> swapChainImages;
これらの画像はスワップチェインの実装によって作成され、スワップチェインが破棄されるときに自動的にクリーンアップされますので、クリーンアップのコードを追加する必要はありません。
createSwapChain
関数の最後、vkCreateSwapchainKHR
呼び出しのすぐ後に、ハンドルを取得するコードを追加します。この取得処理は、以前Vulkanからオブジェクトの配列を取得した時と似ています。スワップチェインの画像の枚数として、最小値だけを指定したのを思い出してください。実装にはそれ以上の枚数でスワップチェインを作成することが許可されています。そのため、はじめにvkGetSwapchainImagesKHR
で最終的な画像の枚数を取得し、コンテナをリサイズしてから、最後にハンドルを取得するために再び関数を呼び出します。
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
最後に、スワップチェインの画像として選択したフォーマットとサイズを、メンバ変数に格納します。これらは後の章で必要になるでしょう。
VkSwapchainKHR swapChain;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
...
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
これで私達は、描画できてウィンドウに表示することができる画像のセットを手に入れました。次の章では、これらの画像をレンダーターゲットとしてセットアップする方法を説明し、実際のグラフィックスパイプラインとドローコマンドについて見ていきます。
前の章
次の章