Vulkan. Entwicklerhandbuch. Kette tauschen

Ich veröffentliche weiterhin Übersetzungen des Vulkan-API-Handbuchs (der Link zum Original lautet vulkan-tutorial.com ) und möchte heute die Übersetzung eines neuen Kapitels - Swap-Kette aus dem Abschnitt Zeichnen eines Dreiecks, Unterabschnitt Präsentation - teilen.



Inhalt
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









Kette tauschen





Vulkan hat keinen Standard-Framebuffer, daher benötigt es eine Infrastruktur mit Puffern, in denen Bilder gerendert werden, bevor sie angezeigt werden. Diese Infrastruktur wird als Swap-Kette bezeichnet und muss explizit in Vulkan erstellt werden. Swap Chain ist eine Warteschlange von Bildern, die darauf warten, auf dem Bildschirm angezeigt zu werden. Das Programm fordert zuerst ein Objekt image(VkImage)



zum Zeichnen an und sendet es nach dem Rendern zurück an die Warteschlange. Wie die Warteschlange funktioniert, hängt von den Einstellungen ab. Die Hauptaufgabe der Auslagerungskette besteht jedoch darin, die Ausgabe von Bildern mit der Bildschirmaktualisierungsrate zu synchronisieren.



Überprüfung der Unterstützung der Austauschkette



Einige spezialisierte Grafikkarten haben keine Anzeigeausgänge und können daher keine Bilder auf dem Bildschirm anzeigen. Darüber hinaus ist die Bildschirmzuordnung an das Fenstersystem gebunden und nicht Teil des Vulkan-Kerns. Daher müssen wir die Erweiterung verbinden VK_KHR_swapchain



.



Ändern isDeviceSuitable



wir zunächst die Funktion , um zu überprüfen, ob die Erweiterung unterstützt wird. Wir haben bereits zuvor mit der Liste der unterstützten Erweiterungen gearbeitet, daher sollte es keine Schwierigkeiten geben. Beachten Sie, dass die Vulkan-Headerdatei ein praktisches Makro enthält VK_KHR_SWAPCHAIN_EXTENSION_NAME



, das als " VK_KHR_swapchain



" definiert ist . Der Vorteil dieses Makros besteht darin, dass der Compiler Sie warnt, wenn Sie einen Rechtschreibfehler machen.



Beginnen wir mit der Deklaration einer Liste der erforderlichen Erweiterungen.



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





Erstellen Sie zur zusätzlichen Überprüfung eine neue Funktion mit dem checkDeviceExtensionSupport



Namen isDeviceSuitable



:



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

    bool extensionsSupported = checkDeviceExtensionSupport(device);

    return indices.isComplete() && extensionsSupported;
}

bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    return true;
}
      
      





Lassen Sie uns den Hauptteil der Funktion ändern, um zu überprüfen, ob alle benötigten Erweiterungen in der Liste der unterstützten Erweiterungen enthalten sind.



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();
}
      
      





Hier habe ich std::set<std::string>



die Namen der erforderlichen, aber noch nicht bestätigten Erweiterungen gespeichert. Sie können auch eine verschachtelte Schleife wie in einer Funktion verwenden checkValidationLayerSupport



. Der Leistungsunterschied ist nicht signifikant.



Lassen Sie uns nun das Programm ausführen und sicherstellen, dass unsere Grafikkarte zum Erstellen einer Swap-Kette geeignet ist. Beachten Sie, dass das Vorhandensein einer Anzeigewarteschlange bereits die Unterstützung der Swap-Kettenerweiterung impliziert. Es ist jedoch am besten, dies explizit sicherzustellen.



Erweiterungen anschließen



Um die Swap-Kette zu verwenden, müssen Sie zuerst die Erweiterung aktivieren VK_KHR_swapchain



. Lassen Sie uns dazu die Auffüllung VkDeviceCreateInfo



beim Erstellen des logischen Geräts leicht ändern :



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





Informationsanfrage zur Unterstützung der Swap-Kette



Es reicht nicht aus, nur zu überprüfen, ob die Swap-Kette verfügbar ist. Die Erstellung der Swap-Kette erfordert viel mehr Konfiguration, daher müssen wir weitere Informationen anfordern.



Insgesamt müssen Sie 3 Arten von Eigenschaften überprüfen:



  • Grundlegende Funktionen der Oberfläche, wie z. B. minimale / maximale Anzahl von Bildern in der Auslagerungskette, minimale / maximale Breite und Höhe von Bildern
  • Oberflächenformat (Pixelformat, Farbraum)
  • Verfügbare Betriebsarten


Um mit diesen Daten zu arbeiten, verwenden wir die Struktur:



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





Erstellen wir nun eine Funktion querySwapChainSupport



, die diese Struktur ausfüllt.



SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
    SwapChainSupportDetails details;

    return details;
}
      
      





Beginnen wir mit den Oberflächenfunktionen. Sie sind einfach abzufragen und kehren zur Struktur zurück VkSurfaceCapabilitiesKHR



.



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





Diese Funktion akzeptiert die zuvor erstellten VkPhysicalDevice



und VkSurfaceKHR



. Jedes Mal, wenn wir nach unterstützter Funktionalität fragen, sind diese beiden Parameter die ersten, da sie Schlüsselkomponenten der Swap-Kette sind.



Der nächste Schritt besteht darin, die unterstützten Oberflächenformate abzufragen. Führen Sie dazu das bereits bekannte Ritual mit einem Doppelfunktionsaufruf durch:



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

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





Stellen Sie sicher, dass Sie im Vektor genügend Speicherplatz zuweisen, um alle verfügbaren Formate abzurufen.



Ebenso fordern wir die unterstützten Betriebsarten mit folgender Funktion an vkGetPhysicalDeviceSurfacePresentModesKHR



:



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

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





Wenn alle erforderlichen Informationen in der Struktur enthalten sind, fügen Sie die Funktion hinzu isDeviceSuitable



, um zu überprüfen, ob die Swap-Kette unterstützt wird. In diesem Lernprogramm wird davon ausgegangen, dass die Swap-Kette unterstützt wird, wenn mindestens ein unterstütztes Bildformat und ein unterstützter Modus für die Fensteroberfläche vorhanden sind.



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





Sie müssen die Unterstützung der Swap-Kette erst anfordern, nachdem Sie überprüft haben, dass die Erweiterung verfügbar ist.



Die letzte Zeile der Funktion ändert sich zu:



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





Einstellungen für die Swap-Kette auswählen



Wenn swapChainAdequate



true, wird die Swap-Kette unterstützt. Die Swap-Kette kann jedoch mehrere Modi haben. Lassen Sie uns einige Funktionen schreiben, um die geeigneten Einstellungen für die Erstellung der effizientesten Swap-Kette zu finden.



Insgesamt werden drei Arten von Einstellungen hervorgehoben:

  • Oberflächenformat (Farbtiefe)
  • Betriebsmodus (Bedingungen für das Ändern von Bildern auf dem Bildschirm)
  • Swap-Ausdehnung (Auflösung der Bilder in der Swap-Kette)


Für jede Einstellung suchen wir nach einem "idealen" Wert, und wenn er nicht verfügbar ist, verwenden wir eine Logik, um aus dem zu wählen, was ist.



Oberflächenformat



Fügen wir eine Funktion hinzu, um ein Format auszuwählen:



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

}
      
      





Später werden wir ein Mitglied formats



aus der Struktur SwapChainSupportDetails



als Argument übergeben.



Jedes Element availableFormats



enthält Mitglieder format



und colorSpace



. Das Feld format



definiert die Anzahl und Art der Kanäle. Zum Beispiel VK_FORMAT_B8G8R8A8_SRGB



bedeutet dies, dass wir B-, G-, R- und Alpha-Kanäle mit jeweils 8 Bit haben, was insgesamt 32 Bit pro Pixel entspricht. Ein Flag VK_COLOR_SPACE_SRGB_NONLINEAR_KHR



im Feld colorSpace



zeigt an, ob der SRGB-Farbraum unterstützt wird. Beachten Sie, dass in einer früheren Version der Spezifikation dieses Flag aufgerufen wurde VK_COLORSPACE_SRGB_NONLINEAR_KHR



.



Wir werden SRGB als Farbraum verwenden. SRGB ist ein Standard für die Darstellung von Farben in Bildern, es reproduziert wahrgenommene Farben besser. Deshalb werden wir auch eines der SRGB-Formate als Farbformat verwenden - VK_FORMAT_B8G8R8A8_SRGB



.



Lassen Sie uns die Liste durchgehen und prüfen, ob die von uns benötigte Kombination verfügbar ist:



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





Wenn nicht, können wir die verfügbaren Formate von besser geeignet bis weniger geeignet sortieren, aber in den meisten Fällen können wir einfach das erste aus der Liste nehmen.



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];
}

      
      





Arbeitszeit



Die Betriebsart ist möglicherweise die wichtigste Einstellung für die Swap-Kette, da sie die Bedingungen für das Ändern von Frames auf dem Bildschirm festlegt.



In Vulkan stehen vier Modi zur Verfügung:



  • VK_PRESENT_MODE_IMMEDIATE_KHR



    : , , , .
  • VK_PRESENT_MODE_FIFO_KHR



    : . , . , . , .
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR



    : , . . .
  • VK_PRESENT_MODE_MAILBOX_KHR



    : Dies ist eine weitere Variante des zweiten Modus. Anstatt das Programm zu blockieren, wenn die Warteschlange voll ist, werden Bilder in der Warteschlange durch neue ersetzt. Dieser Modus eignet sich zur Implementierung der dreifachen Pufferung. Damit können Sie das Auftreten von Artefakten mit geringer Latenz vermeiden.


Es ist garantiert, dass nur der Modus verfügbar ist. Daher müssen VK_PRESENT_MODE_FIFO_KHR



wir erneut eine Funktion schreiben, um den besten verfügbaren Modus zu finden:



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





Persönlich finde ich es am besten, dreifache Pufferung zu verwenden. Es vermeidet Artefakte mit geringer Latenz.



Gehen wir also die Liste durch, um die verfügbaren Modi zu überprüfen:



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;
}
      
      





Umfang tauschen



Es bleibt die letzte Eigenschaft zu konfigurieren. Fügen Sie dazu eine Funktion hinzu:



VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}
      
      





Swap-Ausdehnung ist die Auflösung der Bilder in der Swap-Kette, die fast immer mit der Auflösung des Fensters (in Pixel) übereinstimmt, in dem die Bilder gerendert werden. Wir haben den erlaubten Bereich in der Struktur VkSurfaceCapabilitiesKHR



. Vulkan sagt uns, welche Auflösung wir mithilfe eines Feldes einstellen sollen currentExtent



(entspricht der Größe des Fensters). Einige Fenstermanager erlauben jedoch unterschiedliche Auflösungen. Hierzu wird ein spezieller Wert für Breite und Höhe angegeben currentExtent



- der Maximalwert des Typs uint32_t



. In diesem Fall wählen wir aus dem Intervall zwischen minImageExtent



und maxImageExtent



die Auflösung aus, die der Fensterauflösung am besten entspricht. Die Hauptsache ist, die Maßeinheiten korrekt anzugeben.



GLFW verwendet zwei Maßeinheiten: Pixel und Bildschirmkoordinaten . Die Auflösung {WIDTH, HEIGHT}



, die wir beim Erstellen des Fensters angegeben haben, wird in Bildschirmkoordinaten gemessen. Da Vulkan jedoch mit Pixeln arbeitet, muss die Auflösung der Swap-Kette auch in Pixel angegeben werden. Wenn Sie ein hochauflösendes Display verwenden (z. B. das Retina-Display von Apple), stimmen die Bildschirmkoordinaten nicht mit den Pixeln überein: Aufgrund der höheren Pixeldichte ist die Fensterauflösung in Pixel höher als in Bildschirmkoordinaten. Da Vulkan die Swap-Chain-Berechtigung für uns nicht repariert, können wir die ursprüngliche Berechtigung nicht verwenden {WIDTH, HEIGHT}



. Stattdessen sollten wir verwenden glfwGetFramebufferSize



um die Auflösung des Fensters in Pixel abzufragen, bevor es der minimalen und maximalen Bildauflösung zugeordnet wird.



#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;
    }
}
      
      





Funktion max



und min



wird verwendet, um die Werte width



und height



innerhalb der verfügbaren Auflösungen zu begrenzen . Vergessen Sie nicht, die Header-Datei einzuschließen <algorithm>



, um die Funktionen nutzen zu können.



Swap Chain Creation



Wir haben jetzt alle Informationen, die wir benötigen, um eine geeignete Swap-Kette zu erstellen.



Erstellen wir eine Funktion createSwapChain



und rufen sie initVulkan



nach dem Erstellen des logischen Geräts auf.



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);
}
      
      





Jetzt müssen Sie entscheiden, wie viele Bildobjekte sich in der Auslagerungskette befinden sollen. Die Implementierung gibt den Mindestbetrag an, der für die Arbeit erforderlich ist:



uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
      
      





Wenn Sie jedoch nur dieses Minimum verwenden, müssen Sie manchmal warten, bis der Treiber die internen Vorgänge abgeschlossen hat, um das nächste Image zu erhalten. Daher ist es besser, mindestens eine mehr als das angegebene Minimum anzufordern:



uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
      
      





Es ist wichtig, den Höchstbetrag nicht zu überschreiten. Ein Wert 0



gibt an, dass kein Maximum angegeben ist.



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





Die Swap-Kette ist ein Vulkan-Objekt, daher müssen Sie die Struktur füllen, um sie zu erstellen. Der Beginn der Struktur ist uns bereits bekannt:



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





Zuerst wird die Oberfläche angegeben, an die die Swap-Kette angehängt ist, dann - Informationen zum Erstellen von Bildobjekten:



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



gibt die Anzahl der Ebenen an, aus denen jedes Bild besteht. Hier wird es immer einen Wert geben 1



, es sei denn, es handelt sich natürlich um Stereobilder. Das Bitfeld imageUsage



gibt an, für welche Operationen die von der Swap-Kette erhaltenen Bilder verwendet werden. Im Tutorial werden wir direkt auf sie rendern, aber Sie können zuerst auf ein separates Bild rendern, zum Beispiel für die Nachbearbeitung. Verwenden Sie in diesem Fall den Wert VK_IMAGE_USAGE_TRANSFER_DST_BIT



und die Speicheroperation für die Übertragung.



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
}
      
      





Anschließend müssen Sie angeben, wie mit Bildobjekten umgegangen werden soll, die in mehreren Warteschlangenfamilien verwendet werden. Dies gilt für Fälle, in denen eine Grafikfamilie und eine Anzeigefamilie unterschiedliche Familien sind. Wir werden Bilder in der Grafikwarteschlange rendern und sie dann an die Anzeigewarteschlange senden.



Es gibt zwei Möglichkeiten, Bilder mit Zugriff aus mehreren Warteschlangen zu verarbeiten:



  • VK_SHARING_MODE_EXCLUSIVE



    : Ein Objekt gehört zu einer Warteschlangenfamilie und der Besitz muss explizit übertragen werden, bevor es in einer anderen Warteschlangenfamilie verwendet wird. Diese Methode bietet die höchste Leistung.

  • VK_SHARING_MODE_CONCURRENT



    : Objekte können über mehrere Warteschlangenfamilien hinweg verwendet werden, ohne dass der Besitz explizit übertragen wird.



Wenn wir mehrere Warteschlangen haben, werden wir verwenden VK_SHARING_MODE_CONCURRENT



. Bei dieser Methode müssen Sie im Voraus angeben, zwischen welchen Warteschlangenfamilien der Eigentümer geteilt wird. Dies kann mit den Parametern queueFamilyIndexCount



und erfolgen pQueueFamilyIndices



. Wenn die Grafikwarteschlangenfamilie und die Anzeigewarteschlangenfamilie identisch sind, was häufiger vorkommt, verwenden Sie VK_SHARING_MODE_EXCLUSIVE



.



createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
      
      





Sie können festlegen, dass die Bilder in der Auslagerungskette mit einer der unterstützten Transformationen ( supportedTransforms



in capabilities



) angewendet werden , z. B. um 90 Grad im Uhrzeigersinn drehen oder horizontal drehen. Um keine Transformationen anzuwenden, gehen Sie einfach currentTransform



.



createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
      
      





Das Feld compositeAlpha



gibt an, ob der Alphakanal zum Mischen mit anderen Fenstern im Fenstersystem verwendet werden soll. Sie werden wahrscheinlich keinen Alpha-Kanal benötigen, also lassen Sie ihn VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR



.



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





Das Feld presentMode



spricht für sich. Wenn wir es VK_TRUE



in das Feld clipped



einfügen, sind wir nicht an versteckten Pixeln interessiert (zum Beispiel, wenn ein Teil unseres Fensters von einem anderen Fenster abgedeckt wird). Sie können das Abschneiden jederzeit deaktivieren, wenn Sie die Pixel lesen müssen. Lassen Sie das Ausschneiden jedoch zunächst aktiviert.



createInfo.oldSwapchain = VK_NULL_HANDLE;
      
      





Das letzte Feld bleibt - oldSwapChain



. Wenn die Swap-Kette beispielsweise aufgrund der Größenänderung des Fensters ungültig wird, muss sie von Grund auf neu erstellt und im Feld oldSwapChain



ein Link zur alten Swap-Kette angegeben werden. Dies ist ein komplexes Thema, das wir in einem späteren Kapitel behandeln werden. Nehmen wir an, wir haben vorerst nur eine Swap-Kette.



Fügen wir ein Klassenmitglied hinzu, um das Objekt zu speichern VkSwapchainKHR



:



VkSwapchainKHR swapChain;
      
      





Jetzt müssen Sie nur noch anrufen vkCreateSwapchainKHR



, um die Swap-Kette zu erstellen:



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





Die folgenden Parameter werden an die Funktion übergeben: logisches Gerät, Swap-Chain-Informationen, ein optionaler benutzerdefinierter Allokator und ein Zeiger zum Schreiben des Ergebnisses. Keine Überraschungen. Die Swap-Kette muss zerstört werden, vkDestroySwapchainKHR



bevor das Gerät zerstört wird:



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





Führen Sie nun das Programm aus, um sicherzustellen, dass die Swap-Kette erfolgreich erstellt wurde. Wenn Sie eine Fehlermeldung oder eine Meldung wie erhalten « vkGetInstanceProcAddress SteamOverlayVulkanLayer.dll»



, gehen Sie zum Abschnitt FAQ .



Versuchen wir, die Zeile createInfo.imageExtent = extent;



mit aktivierten Validierungsebenen zu entfernen . Eine der Validierungsstufen erkennt den Fehler sofort und benachrichtigt uns:



Bild



Ein Bild von einer Swap-Kette erhalten



Nachdem die Swap-Kette erstellt wurde, müssen die VkImages- Deskriptoren abgerufen werden . Fügen wir ein Klassenmitglied zum Speichern von Deskriptoren hinzu:



std::vector<VkImage> swapChainImages;
      
      





Bildobjekte aus der Swap-Kette werden automatisch zerstört, nachdem die Swap-Kette selbst zerstört wurde, sodass kein Bereinigungscode hinzugefügt werden muss.



Fügen Sie unmittelbar nach dem Aufruf vkCreateSwapchainKHR



den Code hinzu, um die Deskriptoren zu erhalten. Denken Sie daran, dass wir nur die Mindestanzahl von Bildern in der Auslagerungskette angegeben haben, was bedeutet, dass möglicherweise mehr davon vorhanden sind. Daher fordern wir zuerst die tatsächliche Anzahl von Bildern mit der Funktion an vkGetSwapchainImagesKHR



, weisen dann den erforderlichen Speicherplatz im Container zu und rufen ihn erneut vkGetSwapchainImagesKHR



auf, um die Deskriptoren abzurufen.



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





Und das Letzte: Speichern Sie das Format und die Auflösung der Swap-Chain-Bilder in Klassenvariablen. Wir werden sie in Zukunft brauchen.



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

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
      
      





Wir haben jetzt ein Bild zum Zeichnen und Anzeigen. Im nächsten Kapitel zeigen wir Ihnen, wie Sie ein Bild einrichten, das als Renderziele verwendet werden soll, und beginnen mit der Grafikpipeline und den Zeichenbefehlen!



C ++



All Articles