Vulkan. Entwicklerhandbuch. Zeichne ein Dreieck

Ich bin Übersetzer für CG Tribe in Ischewsk und lade weiterhin Übersetzungen des Vulkan API-Handbuchs hoch. Quelllink - vulkan-tutorial.com .



Diese Veröffentlichung ist der Übersetzung des Abschnitts Zeichnen eines Dreiecks gewidmet, nämlich des Unterabschnitts Setup, des Basiscodes und der Instanzkapitel.



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









Basiscode







Allgemeine Struktur



Im vorherigen Kapitel haben wir beschrieben, wie Sie ein Projekt für Vulkan erstellen, es richtig konfigurieren und mithilfe eines Code-Snippets testen. In diesem Kapitel beginnen wir mit den Grundlagen.



Betrachten Sie den folgenden Code:



#include <vulkan/vulkan.h>

#include <iostream>
#include <stdexcept>
#include <cstdlib>

class HelloTriangleApplication {
public:
    void run() {
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    void initVulkan() {

    }

    void mainLoop() {

    }

    void cleanup() {

    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
      
      





Zunächst fügen wir die Vulkan-Header-Datei aus dem LunarG SDK hinzu. Header-Dateien stdexcepts



und iostream



werden zur Fehlerbehandlung und -verteilung verwendet. Die Header-Datei cstdlib



enthält Makros EXIT_SUCCESS



und EXIT_FAILURE



.



Das Programm selbst ist in die HelloTriangleApplication-Klasse eingebunden, in der wir Vulkan-Objekte als private Mitglieder der Klasse speichern. Dort werden wir auch Funktionen zum Initialisieren jedes Objekts hinzufügen, die von der Funktion aufgerufen werden initVulkan



. Danach erstellen wir eine Hauptschleife zum Rendern von Frames. Füllen Sie dazu eine Funktion aus, mainLoop



in der die Schleife ausgeführt wird, bis das Fenster geschlossen wird. Nach dem Schließen des Fensters und dem Verlassen mainLoop



Ressourcen müssen freigegeben werden. Füllen Sie dazu aus cleanup



.



Wenn während des Betriebs ein kritischer Fehler auftritt, wird eine Ausnahme std::runtime_error



ausgelöst, die in der Funktion abgefangen wird main



, und die Beschreibung wird in angezeigt std::cerr



. Einer dieser Fehler kann beispielsweise eine Meldung sein, dass die erforderliche Erweiterung nicht unterstützt wird. Um viele der Standardausnahmetypen zu behandeln, fangen wir einen allgemeineren ab std::exception



.



In fast jedem folgenden Kapitel werden neue Funktionen hinzugefügt, die von aufgerufen werden initVulkan



, sowie neue Vulkan-Objekte, die am cleanup



Ende des Programms freigegeben werden müssen.



Ressourceneinteilung



Wenn Vulkan-Objekte nicht mehr benötigt werden, müssen sie zerstört werden. Mit C ++ können Sie die Zuweisung von Ressourcen mithilfe von RAII oder intelligenten Zeigern, die von der Header-Datei bereitgestellt werden, automatisch freigeben <memory>



. In diesem Tutorial haben wir uns jedoch entschlossen, explizit zu schreiben, wann Vulkan-Objekte zugewiesen und freigegeben werden sollen. Dies ist schließlich die Besonderheit von Vulkans Arbeit - jede Operation detailliert zu beschreiben, um mögliche Fehler zu vermeiden.



Nach dem Lesen des Lernprogramms können Sie die automatische Ressourcenverwaltung implementieren, indem Sie C ++ - Klassen schreiben, die Vulkan-Objekte im Konstruktor empfangen und im Destruktor freigeben. Sie können auch Ihren eigenen Deleter für std::unique_ptr



oder implementieren std::shared_ptr



, abhängig von den Anforderungen. Das RAII-Konzept wird für größere Programme empfohlen, es ist jedoch hilfreich, mehr darüber zu erfahren.



Vulkan-Objekte werden direkt mit einer Funktion wie vkCreateXXX erstellt oder über ein anderes Objekt mit einer Funktion wie vkAllocateXXX zugewiesen . Nachdem Sie sichergestellt haben, dass das Objekt nirgendwo anders verwendet wird, müssen Sie es mit vkDestroyXXX oder vkFreeXXX zerstören . Die Parameter für diese Funktionen unterscheiden sich normalerweise je nach Objekttyp, es gibt jedoch einen gemeinsamen Parameter: pAllocator



... Dies ist ein optionaler Parameter, mit dem Sie Rückrufe für die benutzerdefinierte Speicherzuweisung verwenden können. Wir werden es im Handbuch nicht brauchen, wir werden es als Argument übergeben nullptr



.



GLFW-Integration



Vulkan funktioniert gut, ohne ein Fenster zu erstellen, wenn Offscreen-Rendering verwendet wird, aber viel besser, wenn das Ergebnis auf dem Bildschirm sichtbar ist.

Ersetzen Sie zuerst die Leitung durch #include <vulkan/vulkan.h>



folgende:



#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
      
      





Fügen Sie eine Funktion hinzu initWindow



und fügen Sie ihren Aufruf aus der Methode run



vor anderen Aufrufen hinzu. Wir werden initWindow



GLFW verwenden, um ein Fenster zu initialisieren und zu erstellen.



void run() {
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
}

private:
    void initWindow() {

    }
      
      





Der allererste Aufruf von initWindow



muss eine Funktion sein glfwInit()



, die die GLFW-Bibliothek initialisiert. GLFW wurde ursprünglich für die Arbeit mit OpenGL entwickelt. Wir benötigen keinen OpenGL-Kontext. Geben Sie daher an, dass wir ihn nicht mit dem folgenden Aufruf erstellen müssen:



glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
      
      





Wir werden die Möglichkeit zur Größenänderung des Fensters vorübergehend deaktivieren, da für die Behandlung dieser Situation eine gesonderte Überlegung erforderlich ist:



glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
      
      





Es bleibt ein Fenster zu erstellen. Fügen Sie dazu ein privates Mitglied hinzu GLFWwindow* window;



und initialisieren Sie das Fenster mit:



window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
      
      





Die ersten drei Parameter definieren die Breite, Höhe und den Titel des Fensters. Der vierte Parameter ist optional. Hier können Sie den Monitor angeben, auf dem das Fenster angezeigt wird. Der letzte Parameter ist spezifisch für OpenGL.



Es wäre schön, Konstanten für die Breite und Höhe des Fensters zu verwenden, da wir diese Werte an anderer Stelle benötigen. Fügen Sie vor der Klassendefinition die folgenden Zeilen hinzu HelloTriangleApplication



:



const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
      
      





und ersetzen Sie den Aufruf, um ein Fenster mit zu erstellen



window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
      
      





Sie sollten die folgende Funktion haben initWindow



:



void initWindow() {
    glfwInit();

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}


      
      





Beschreiben wir die Hauptschleife in der Methode mainLoop



, mit der die Anwendung ausgeführt wird, bis das Fenster geschlossen wird:



void mainLoop() {
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}
      
      





Dieser Code sollte keine Fragen aufwerfen. Es behandelt Ereignisse wie das Drücken der X-Taste, bevor der Benutzer das Fenster schließt. Auch aus dieser Schleife rufen wir eine Funktion auf, um einzelne Frames zu rendern.



Nach dem Schließen des Fensters müssen wir Ressourcen freigeben und GLFW beenden. Fügen wir zunächst den cleanup



folgenden Code hinzu:



void cleanup() {
    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





Als Ergebnis sehen Sie nach dem Starten des Programms ein Fenster mit einem Namen Vulkan



, das angezeigt wird, bis das Programm geschlossen wird. Nachdem wir nun ein Skelett für die Arbeit mit Vulkan haben, können wir unser erstes Vulkan-Objekt erstellen!



C ++ - Code











Beispiel







Instanziierung



Als erstes müssen Sie eine Instanz erstellen, um die Bibliothek zu initialisieren. Eine Instanz ist die Verbindung zwischen Ihrem Programm und der Vulkan-Bibliothek. Um sie zu erstellen, müssen Sie dem Treiber einige Informationen zu Ihrem Programm bereitstellen.



Fügen Sie eine Methode hinzu createInstance



und rufen Sie sie von einer Funktion aus auf initVulkan



.



void initVulkan() {
    createInstance();
}
      
      





Fügen Sie unserer Klasse ein Instanzmitglied hinzu, das ein Instanzhandle enthält:



private:
VkInstance instance;
      
      





Jetzt müssen wir eine spezielle Struktur mit Informationen über das Programm ausfüllen. Technisch gesehen sind die Daten optional. Dadurch kann der Treiber jedoch nützliche Informationen erhalten, um die Arbeit mit Ihrem Programm zu optimieren. Diese Struktur heißt VkApplicationInfo



:



void createInstance() {
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = "Hello Triangle";
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_0;
}
      
      





Wie bereits erwähnt, erfordern viele Strukturen in Vulkan eine explizite Typdefinition im sType- Member . Wie viele andere enthält auch diese Struktur ein Element pNext



, mit dem Sie Informationen für Erweiterungen bereitstellen können. Wir verwenden die Wertinitialisierung, um die Struktur mit Nullen zu füllen.



Die meisten Informationen in Vulkan werden durch Strukturen geleitet. Sie müssen also eine weitere Struktur ausfüllen, um genügend Informationen zum Erstellen einer Instanz bereitzustellen. Die folgende Struktur ist erforderlich. Sie teilt dem Treiber mit, welche globalen Erweiterungen und Validierungsebenen wir verwenden möchten. "Global" bedeutet, dass die Erweiterungen für das gesamte Programm und nicht für ein bestimmtes Gerät gelten.



VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
      
      





Die ersten beiden Parameter werfen keine Fragen auf. Die nächsten beiden Mitglieder definieren die erforderlichen globalen Erweiterungen. Wie Sie bereits wissen, ist die Vulkan-API vollständig plattformunabhängig. Dies bedeutet, dass Sie eine Erweiterung benötigen, um mit dem Fenstersystem zu interagieren. GLFW verfügt über eine praktische integrierte Funktion, die eine Liste der erforderlichen Erweiterungen zurückgibt.



uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
      
      





Die letzten beiden Strukturelemente definieren, welche globalen Validierungsebenen eingeschlossen werden sollen. Wir werden im nächsten Kapitel ausführlicher darauf eingehen. Lassen Sie diese Werte daher vorerst leer.



createInfo.enabledLayerCount = 0;
      
      





Jetzt haben Sie alles Notwendige getan, um eine Instanz zu erstellen. Rufen Sie an vkCreateInstance



:



VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
      
      





In der Regel sind die Parameter von Funktionen zum Erstellen von Objekten in dieser Reihenfolge:



  • Zeiger auf eine Struktur mit den notwendigen Informationen
  • Zeiger auf benutzerdefinierten Allokator
  • Zeiger auf die Variable, in die der Deskriptor des neuen Objekts geschrieben wird


Wenn alles richtig gemacht wurde, wird der Instanzdeskriptor in der Instanz gespeichert . Fast alle Vulkan-Funktionen geben einen VkResult-Wert zurück , der entweder ein VK_SUCCESS



Fehlercode sein kann. Wir müssen das Ergebnis nicht speichern, um sicherzustellen, dass die Instanz erstellt wurde. Verwenden wir eine einfache Überprüfung:



if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
    throw std::runtime_error("failed to create instance!");
}
      
      





Führen Sie nun das Programm aus, um zu überprüfen, ob die Instanz erfolgreich erstellt wurde.



Unterstützte Erweiterungen überprüfen



Wenn wir uns die Vulkan- Dokumentation ansehen , können wir feststellen, dass einer der möglichen Fehlercodes ist VK_ERROR_EXTENSION_NOT_PRESENT



. Wir können einfach die erforderlichen Erweiterungen angeben und nicht mehr funktionieren, wenn sie nicht unterstützt werden. Dies ist für wichtige Erweiterungen wie die Benutzeroberfläche des Fenstersystems sinnvoll. Was ist jedoch, wenn wir auf optionale Funktionen testen möchten?



Verwenden Sie die Funktion vkEnumerateInstanceExtensionProperties, um eine Liste der unterstützten Erweiterungen vor dem Instanziieren abzurufen... Der erste Parameter der Funktion ist optional. Sie können Erweiterungen nach einer bestimmten Validierungsebene filtern, sodass wir sie vorerst leer lassen. Die Funktion erfordert auch einen Zeiger auf eine Variable, in die die Anzahl der Erweiterungen geschrieben wird, und einen Zeiger auf einen Speicherbereich, in den Informationen über sie geschrieben werden sollen.



Um Speicher zum Speichern von Erweiterungsinformationen zuzuweisen, müssen Sie zunächst die Anzahl der Erweiterungen kennen. Lassen Sie den letzten Parameter leer, um die Anzahl der Erweiterungen anzufordern:



uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
      
      





Weisen Sie ein Array zum Speichern von Erweiterungsinformationen zu (vergessen Sie nicht include <vector>



):



std::vector<VkExtensionProperties> extensions(extensionCount);
      
      





Sie können jetzt Informationen zu Erweiterungen anfordern.



vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
      
      





Jede VkExtensionProperties- Struktur enthält den Namen und die Version der Erweiterung. Sie können mit einer einfachen for-Schleife aufgelistet werden ( \t



hier ist die Registerkarte Einrückung):



std::cout << "available extensions:\n";

for (const auto& extension : extensions) {
    std::cout << '\t' << extension.extensionName << '\n';
}
      
      





Sie können diesen Code einer Funktion hinzufügen, createInstance



um weitere Informationen zur Vulkan-Unterstützung zu erhalten. Sie können auch versuchen, eine Funktion zu erstellen, die überprüft, ob alle von der Funktion zurückgegebenen Erweiterungen glfwGetRequiredInstanceExtensions



in der Liste der unterstützten Erweiterungen enthalten sind.





Reinigung



VkInstance muss vor dem Schließen des Programms zerstört werden. Dies kann cleanup



mit der Funktion VkDestroyInstance erfolgen :



void cleanup() {
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





Die Parameter für die Funktion vkDestroyInstance sind selbsterklärend . Wie im vorherigen Kapitel erwähnt, akzeptieren die Zuweisungs- und Freigabefunktionen in Vulkan optionale Zeiger auf benutzerdefinierte Zuweiser, die wir nicht verwenden und die wir übergeben nullptr



. Alle anderen Vulkan-Ressourcen müssen bereinigt werden, bevor die Instanz zerstört wird.



Bevor wir mit komplexeren Schritten fortfahren, müssen wir die Validierungsebenen einrichten, um das Debuggen zu vereinfachen.



C ++ - Code



All Articles