Inhalt
1.
2.
3.
4.
5.
6. Uniform-
7.
8.
9.
10. -
11. Multisampling
FAQ
2.
3.
4.
-
-
- Window surface
- Swap chain
- Image views
- (pipeline)
5.
- Staging
6. Uniform-
- layout
- sets
7.
- Image view image sampler
- image sampler
8.
9.
10. -
11. Multisampling
FAQ
Kette tauschen
- Überprüfung der Unterstützung der Austauschkette
- Erweiterungen anschließen
- Informationsanfrage zur Unterstützung der Swap-Kette
- Einstellungen für die Swap-Kette auswählen
- Swap Chain Creation
- Ein Bild von einer Swap-Kette erhalten
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:
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 ++