バルカン。開発者ガイド。スワップチェーン

私は引き続きVulkanAPIマニュアルの翻訳を公開しています(オリジナルへのリンクは vulkan-tutorial.comです)。今日は、新しい章の翻訳を共有したいと思います。三角形の描画セクション、プレゼンテーションサブセクションからチェーンを交換します。



コンテンツ
1.



2.



3.



4.







  1. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ









スワップチェーン





Vulkanにはデフォルトのフレームバッファーなどがないため、画像が表示される前にレンダリングされるバッファーを備えたインフラストラクチャが必要です。このインフラストラクチャはスワップチェーンと呼ばれ、Vulkanで明示的に作成する必要があります。スワップチェーンは、画面に表示されるのを待っている画像のキューです。プログラムは最初image(VkImage)



に描画するオブジェクト要求し、 レンダリング後、それをキューに送り返します。キューの動作は設定によって異なりますが、スワップチェーンの主なタスクは、画像の出力を画面のリフレッシュレートと同期させることです。



スワップチェーンサポートの確認



一部の特殊なビデオカードには表示出力がないため、画面に画像を表示できません。さらに、画面マッピングはウィンドウシステムに関連付けられており、Vulkanコアの一部ではありません。したがって、拡張機能を接続する必要があり VK_KHR_swapchain



ます。



まずisDeviceSuitable



機能変更して 、拡張機能がサポートされているかどうかを確認しましょう。サポートされている拡張機能のリストは以前に使用したことがあるので、問題はないはずです。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();
}
      
      





ここで std::set<std::string>



は、必要ながまだ確認されていない拡張子の名前を保存していました。関数のようにネストされたループを使用することもできます checkValidationLayerSupport



パフォーマンスの違いは重要ではありません。



次に、プログラムを実行して、ビデオカードがスワップチェーンの作成に適していることを確認します。表示キューの存在は、スワップチェーン拡張のサポートをすでに意味していることに注意してください。ただし、これを明示的に確認することをお勧めします。



拡張機能の接続



スワップチェーンを使用するには、最初に拡張機能を有効にする必要があります VK_KHR_swapchain



これを行うにはVkDeviceCreateInfo



、論理デバイスを作成するときにパディングを少し変更しましょう



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





スワップチェーンサポート情報リクエスト



スワップチェーンが利用可能かどうかを確認するだけでは不十分です。スワップチェーンの作成にはさらに多くの構成が含まれるため、より多くの情報を求める必要があります。



合計で、次の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つのパラメーターが最初になります。これは、これらがスワップチェーンの主要コンポーネントであるためです。



次のステップは、サポートされているサーフェス形式を照会することです。これを行うには、すでにおなじみの儀式を二重関数呼び出しで実行しましょう。



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

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





使用可能なすべての形式を取得するために、ベクターに十分なスペースを割り当ててください。



同様に、次の関数を使用して、サポートされている動作モードを要求します 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;
      
      





スワップチェーンの設定の選択



場合は swapChainAdequate



真、スワップチェーンがサポートされています。ただし、スワップチェーンにはいくつかのモードがあります。最も効率的なスワップチェーンを作成するための適切な設定を見つけるために、いくつかの関数を書いてみましょう。



合計で、次の3種類の設定を強調します。

  • 表面フォーマット(色深度)
  • 動作モード(画面上のフレームを変更するための条件)
  • スワップ範囲(スワップチェーン内の画像の解像度)


設定ごとに、いくつかの「理想的な」値を探し、それが利用できない場合は、いくつかのロジックを使用して、値から選択します。



表面フォーマット



フォーマットを選択する関数を追加しましょう:



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

}
      
      





後で、formats



構造からメンバーSwapChainSupportDetails



引数として渡し ます。



各要素に availableFormats



はメンバーformat



が含まれます colorSpace



。このフィールド format



は、チャネルの数とタイプを定義します。たとえば、VK_FORMAT_B8G8R8A8_SRGB



それぞれ8ビットのB、G、R、およびアルファチャネルがあり、ピクセルあたり合計32ビットであることを 意味します。VK_COLOR_SPACE_SRGB_NONLINEAR_KHR



フィールドのフラグ は、 colorSpace



SRGB色空間がサポートされているかどうかを示します。以前のバージョンの仕様では、このフラグはと呼ばれていたことに注意してください VK_COLORSPACE_SRGB_NONLINEAR_KHR







色空間としてSRGBを使用します。SRGBは、画像内の色を表現するための標準であり、知覚される色をより適切に再現します。そのため、SRGB形式の1つをカラー形式としても使用します- 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];
}

      
      





労働時間



動作モードは、画面上のフレームを変更するための条件を決定するため、スワップチェーンにとっておそらく最も重要な設定です。



Vulkanで利用できるモードは4つあります。



  • VK_PRESENT_MODE_IMMEDIATE_KHR



    : , , , .
  • VK_PRESENT_MODE_FIFO_KHR



    : . , . , . , .
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR



    : , . . .
  • VK_PRESENT_MODE_MAILBOX_KHR



    :これは2番目のモードの別のバリエーションです。キューがいっぱいになったときにプログラムをブロックする代わりに、キュー内の画像が新しい画像に置き換えられます。このモードは、トリプルバッファリングの実装に適しています。これを使用すると、低レイテンシでアーティファクトの出現を回避できます。


モードのみが使用可能VK_PRESENT_MODE_FIFO_KHR



であることが保証されている ため、ここでも、使用可能な最適なモードを見つけるための関数を作成する必要があります。



VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    return VK_PRESENT_MODE_FIFO_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;
}
      
      





スワップ範囲



最後のプロパティを構成する必要があります。これを行うには、関数を追加します。



VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}
      
      





スワップ範囲は、スワップチェーン内の画像の解像度であり、ほとんどの場合、画像がレンダリングされるウィンドウの解像度(ピクセル単位)と一致します。構造体で許容範囲を取得しました VkSurfaceCapabilitiesKHR



。 Vulkanは、フィールドを使用して設定する必要のある解像度を教えてくれます currentExtent



(ウィンドウのサイズと一致します)。ただし、一部のウィンドウマネージャーでは、さまざまな解像度が許可されています。このために、幅と高さの特別なcurrentExtent



値、つまりタイプの最大値が 指定されます uint32_t



。この場合、minImageExtent



との 間の間隔から maxImageExtent



、ウィンドウの解像度に最も一致する解像度を選択します。主なことは、測定単位を正しく指定することです。



GLFWは、ピクセルと画面座標の2つの測定単位を使用し ます。したがって、{WIDTH, HEIGHT}



ウィンドウの作成時に指定した解像度 は、画面座標で測定されます。ただし、Vulkanはピクセルで動作するため、スワップチェーンの解像度もピクセルで指定する必要があります。高解像度ディスプレイ(AppleのRetinaディスプレイなど)を使用している場合、画面座標がピクセルと一致しません。ピクセル密度が高いため、ウィンドウ解像度は画面座標よりもピクセル単位で高くなります。 Vulkanはスワップチェーンの権限を修正しないため、元の権限を使用することはできません {WIDTH, HEIGHT}



。代わりに、 glfwGetFramebufferSize



ウィンドウの解像度をピクセル単位で照会してから、最小および最大の画像解像度にマッピングします。



#include <cstdint> // Necessary for UINT32_MAX

...

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != UINT32_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::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
        actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));

        return actualExtent;
    }
}
      
      





関数 max



とは、 min



値を制限するために使用される width



と、 height



利用可能な解像度の中。<algorithm>



関数を使用するには、ヘッダーファイルをインクルードすることを忘れないでください



スワップチェーンの作成



これで、適切なスワップチェーンを作成するために必要なすべての情報が得られました。



関数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;
      
      





In imageArrayLayers



は、各画像を構成するレイヤーの数を指定します。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



    :オブジェクトは、所有権を明示的に譲渡することなく、複数のキューファミリ間で使用できます。



複数のキューがある場合は、を使用します VK_SHARING_MODE_CONCURRENT



この方法では、所有権を共有するキューファミリ間で事前に指定する必要があります。これは、パラメータqueueFamilyIndexCount



を使用して実行できます pQueueFamilyIndices



グラフィックキューファミリとディスプレイキューファミリが同じである場合(より一般的)は、を使用します VK_SHARING_MODE_EXCLUSIVE







createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
      
      





スワップチェーン内の画像が、サポートされている変換(supportedTransforms



capabilities



)のいずれかで適用されるように指定できます。 たとえば、時計回りに90度回転したり、水平方向に反転したりできます。変換を適用しない場合は、そのままにしておき currentTransform



ます。



createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
      
      





このフィールド compositeAlpha



は、ウィンドウシステム内の他のウィンドウとブレンドするためにアルファチャネルを使用するかどうかを示します。おそらくアルファチャネルは必要ないので、そのままにしておきます VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR







createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
      
      





フィールド presentMode



はそれ自体を物語っています。VK_TRUE



フィールドに配置し場合 clipped



、非表示のピクセルには関心がありません(たとえば、ウィンドウの一部が別のウィンドウで覆われている場合)。ピクセルを読み取る必要がある場合はいつでもクリッピングをオフにすることができますが、とりあえずクリッピングをオンのままにしておきましょう。



createInfo.oldSwapchain = VK_NULL_HANDLE;
      
      





最後のフィールドは残ります- oldSwapChain



ウィンドウのサイズ変更などによりスワップチェーンが無効になった場合は、最初から再作成する必要があり、フィールド oldSwapChain



に古いスワップチェーンへのリンクを指定します。これは、後の章で説明する複雑なトピックです。今のところ、スワップチェーンが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);
    ...
}
      
      





次に、プログラムを実行して、スワップチェーンが正常に作成されたことを確認します。エラーメッセージまたはのようなメッセージを受け取った場合は « vkGetInstanceProcAddress SteamOverlayVulkanLayer.dll»



FAQセクションにアクセスして ください検証レイヤーを有効にして



行を削除してみましょう createInfo.imageExtent = extent;



検証レベルの1つは、エラーを即座に検出して通知します。



画像



スワップチェーンから画像を取得する



スワップチェーンが作成されたので、VkImages記述子を取得する必要があります 記述子を格納するためのクラスメンバーを追加しましょう。



std::vector<VkImage> swapChainImages;
      
      





スワップチェーン自体が破棄された後、スワップチェーンのイメージオブジェクトは自動的に破棄されるため、クリーンアップコードを追加する必要はありません。



呼び出しの直後に、 vkCreateSwapchainKHR



記述子を取得するためのコードを追加します。スワップチェーン内のイメージの最小数のみを指定したことを忘れないでください。つまり、イメージがさらに多くなる可能性があります。したがって、最初に関数を使用して実際の画像数を要求し vkGetSwapchainImagesKHR



、次にコンテナに必要なスペースを割り当て、それを再度呼び出して 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;
      
      





これで、描画して表示する画像ができました。次の章では、レンダリングターゲットとして使用する画像を設定する方法を示し、グラフィックスパイプラインと描画コマンドの使用を開始します。



C ++



All Articles