Vulkan. Entwicklerhandbuch

Ich arbeite als technischer Übersetzer für das IT-Unternehmen CG Tribe in Ischewsk, das mich eingeladen hat, einen Beitrag zur Community zu leisten und Übersetzungen interessanter Artikel und Tutorials zu veröffentlichen.



Hier werde ich die Übersetzung des Vulkan API-Handbuchs veröffentlichen. Quelllink - vulkan-tutorial.com . Da ein anderer Habr-Benutzer, kiwhy (https://habr.com/ru/users/kiwhy/), an der Übersetzung desselben Handbuchs beteiligt ist, haben wir vereinbart,

die Lektionen unter uns zu teilen. In meinen Veröffentlichungen werde ich Links zu Kapiteln bereitstellen, die von Kiwhy übersetzt wurden.



Inhalt
1. Einführung



2. Kurzer Überblick



3. Entwicklungsumgebung



4. Zeichnen eines Dreiecks



  1. Vorbereitung auf die Arbeit
  2. (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







1. Einleitung



Siehe den Artikel des Autors kiwhy - habr.com/ru/post/462137



2. Übersicht



Hintergrund von Vulkan



Wie zeichnet man ein Dreieck?



  1. Schritt 1 - Instanz und physische Geräte
  2. Schritt 2 - Logic Unit- und Queue-Familien
  3. Schritt 3 - Fensteroberfläche und Ketten tauschen
  4. Schritt 4 - Bildansichten und Framebuffer
  5. Schritt 5 - Pässe rendern
  6. Schritt 6 - Die Grafik-Pipeline
  7. Schritt 7 - Befehlspool und Befehlspuffer
  8. Schritt 8 - Hauptschleife
  9. Schlussfolgerungen


API-Konzepte



  1. Code-Formatierungsstandard
  2. Validierungsschichten


In diesem Kapitel werden wir mit Vulkan beginnen und sehen, welche Probleme es lösen kann. Wir beschreiben die Schritte, die zum Erstellen Ihres ersten Dreiecks erforderlich sind. Dies gibt Ihnen einen Überblick über den Standard und ermöglicht es Ihnen, die Logik hinter dem Layout der nachfolgenden Kapitel zu verstehen. Wir schließen mit einem Blick auf die Vulkan-API-Struktur und typische Anwendungsfälle.



Voraussetzungen für Vulkan



Wie frühere Grafik-APIs ist Vulkan als plattformübergreifende Abstraktion über die GPU konzipiert . Das Hauptproblem bei den meisten dieser APIs besteht darin, dass sie während ihrer Entwicklung Grafikhardware verwendeten, die auf feste Funktionen beschränkt war. Die Entwickler mussten Scheitelpunktdaten in einem Standardformat bereitstellen und waren in Bezug auf Beleuchtung und Schatten vollständig von den GPU-Herstellern abhängig.



Mit der Entwicklung der Architektur von Grafikkarten tauchten immer mehr programmierbare Funktionen auf. Alle neuen Funktionen mussten auf irgendeine Weise mit vorhandenen APIs kombiniert werden. Dies führte zu unvollständigen Abstraktionen und vielen Hypothesen des Grafiktreibers, wie die Absicht des Programmierers in moderne Grafikarchitekturen übersetzt werden kann. Daher wird eine große Anzahl von Treiberupdates veröffentlicht, um die Leistung in Spielen zu verbessern. Aufgrund der Komplexität solcher Treiber gibt es häufig Diskrepanzen zwischen Anbietern, beispielsweise in der für Shader verwendeten Syntax... Abgesehen davon gab es im letzten Jahrzehnt auch einen Zustrom mobiler Geräte mit leistungsstarker Grafikhardware. Die Architekturen dieser mobilen GPUs können je nach Größe und Leistungsanforderungen stark variieren. Ein solches Beispiel ist das gekachelte Rendern , das durch eine bessere Kontrolle über die Funktionalität eine bessere Leistung erzielen kann. Eine weitere Einschränkung aufgrund des Alters der API ist die eingeschränkte Unterstützung für Multithreading, die zu einem Engpass auf der CPU-Seite führen kann.



Vulkan hilft bei der Lösung dieser Probleme, da es von Grund auf für moderne Grafikarchitekturen entwickelt wurde. Dies reduziert den Overhead auf der Fahrerseite, indem Entwickler ihre Ziele mithilfe einer detaillierten API klar beschreiben können. Mit Vulkan können Sie Befehle in mehreren Threads parallel erstellen und senden. Außerdem werden Kompilierungsdiskrepanzen zwischen Shadern reduziert, indem auf ein standardisiertes Bytecode-Format umgestellt und ein einziger Compiler verwendet wird. Schließlich vereint Vulkan die Kernfunktionen heutiger Grafikkarten, indem Grafik- und Computerfunktionen in einer einzigen API integriert werden.



Wie zeichne ich ein Dreieck?



Wir werden einen kurzen Blick auf die Schritte werfen, die zum Zeichnen eines Dreiecks erforderlich sind. Dies gibt Ihnen einen Überblick über den Prozess. Eine detaillierte Beschreibung jedes Konzepts finden Sie in den folgenden Kapiteln.



Schritt 1 - Instanz und physische Geräte Die



Arbeit mit Vulkan beginnt mit der Konfiguration der Vulkan-API über VkInstance (Instanz). Eine Instanz wird anhand Ihrer Programmbeschreibung und aller gewünschten Erweiterungen erstellt. Nach der Instanziierung können Sie abfragen, welche Hardware Vulkan unterstützt, und ein oder mehrere VkPhysicalDevices auswählen , um Vorgänge auszuführen. Sie können nach Parametern wie VRAM-Größe und Gerätefunktionen fragen, um die gewünschten Geräte auszuwählen, wenn Sie spezielle Grafikkarten verwenden möchten.



Schritt 2 - Familien logischer Geräte und Warteschlangen



Nachdem Sie das entsprechende zu verwendende Hardwaregerät ausgewählt haben, müssen Sie ein VkDevice (logisches Gerät) erstellen , in dem Sie detaillierter beschreiben, welche Funktionen ( VkPhysicalDeviceFeatures ) Sie beispielsweise zum Rendern in mehreren Ansichtsfenstern verwenden. s (Multi Viewport Rendering) und 64-Bit-Floats. Sie müssen auch festlegen, welche Warteschlangenfamilien Sie verwenden möchten. Viele der mit Vulkan ausgeführten Vorgänge, wie z. B. Zeichenbefehle und In-Memory-Vorgänge, werden nach dem Senden an VkQueue asynchron ausgeführt... Warteschlangen werden aus einer Familie von Warteschlangen zugewiesen, wobei jede Familie einen bestimmten Satz von Vorgängen unterstützt. Beispielsweise können separate Familien von Warteschlangen für Grafikoperationen, Berechnungsoperationen und Speicherdatenübertragungen existieren. Darüber hinaus kann ihre Verfügbarkeit als Schlüsselparameter bei der Auswahl eines physischen Geräts verwendet werden. Einige Vulkan-fähige Geräte bieten keine Grafikfunktionen. Alle modernen Vulkan-fähigen Grafikkarten unterstützen jedoch im Allgemeinen alle erforderlichen Warteschlangenvorgänge.



Schritt 3 - Fensteroberflächen- und Austauschketten



Wenn Sie mehr als nur das Rendern außerhalb des Bildschirms interessieren, müssen Sie ein Fenster erstellen, um die gerenderten Bilder anzuzeigen. Windows kann mit nativen Plattform-APIs oder Bibliotheken wie GLFW und SDL erstellt werden . Wir werden GLFW für dieses Tutorial verwenden, auf das wir im nächsten Kapitel näher eingehen werden.



Wir benötigen zwei weitere Komponenten, um das Anwendungsfenster zu rendern: die Fensteroberfläche ( VkSurfaceKHR) und die Showkette ( VkSwapchainKHR). Achten Sie auf den PostfixKHRDies bedeutet, dass diese Objekte Teil der Vulkan-Erweiterung sind. Die Vulkan-API ist vollständig plattformunabhängig, daher müssen wir die standardisierte WSI-Erweiterung (Window System Interface) verwenden, um mit dem Fenstermanager zu interagieren. Surface ist eine plattformübergreifende Fensterabstraktion zum Rendern, die normalerweise durch Verweisen auf ein natives Fensterhandle erstellt wird, beispielsweise HWNDunter Windows. Glücklicherweise verfügt die GLFW-Bibliothek über eine integrierte Funktion zum Arbeiten mit plattformspezifischen Details.



Eine Showkette ist eine Reihe von Renderzielen. Seine Aufgabe ist es sicherzustellen, dass sich das aktuell gerenderte Bild von dem auf dem Bildschirm angezeigten unterscheidet. Auf diese Weise können Sie verfolgen, dass nur gerenderte Bilder angezeigt werden. Jedes Mal, wenn wir einen Rahmen erstellen müssen, müssen wir die Showkette anfordern, um ein Bild zum Rendern bereitzustellen. Nachdem der Rahmen erstellt wurde, wird das Bild an die Anzeigekette zurückgegeben, um irgendwann auf dem Bildschirm angezeigt zu werden. Die Anzahl der Rendering-Ziele und die Bedingungen für die Anzeige der fertigen Bilder auf dem Bildschirm hängen vom aktuellen Modus ab. Diese Modi umfassen Doppelpufferung (vsync) und Dreifachpufferung. Wir werden sie im Kapitel über das Erstellen einer Showkette behandeln.



Einige Plattformen ermöglichen das direkte Rendern auf dem Bildschirm über Erweiterungen VK_KHR_displayund VK_KHR_display_swapchainohne Interaktion mit einem Fenstermanager. Auf diese Weise können Sie eine Oberfläche erstellen, die den gesamten Bildschirm darstellt und beispielsweise zum Implementieren Ihres eigenen Fenstermanagers verwendet werden kann.



Schritt 4 - Bildansichten und Framebuffer



Um in das aus der Anzeigekette erhaltene Bild zu zeichnen, müssen Sie es in einen VkImageView- und einen VkFramebuffer einschließen . Die Bildansicht bezieht sich auf einen bestimmten Teil des verwendeten Bildes, und der Bildpuffer bezieht sich auf die Bildansichten, die als Farb-, Tiefen- und Schablonenpuffer verwendet werden. Da die Anzeigekette viele verschiedene Bilder enthalten kann, erstellen wir im Voraus für jedes Bild eine Bildansicht und einen Framebuffer und wählen während des Zeichnens das gewünschte Bild aus.



Schritt 5 - Renderpässe Die



Renderpässe von Vulkan beschreiben die Art der Bilder, die während des Renderns verwendet werden, wie sie verwendet werden und wie ihr Inhalt behandelt werden soll. Bevor wir das Dreieck zeichnen, teilen wir Vulkan mit, dass wir ein einzelnes Bild als Farbpuffer verwenden möchten und dass wir es vor dem Zeichnen löschen müssen. Wenn der Render-Pass nur den Typ der als Puffer verwendeten Bilder beschreibt, ordnet VkFramebuffer diesen Slots tatsächlich bestimmte Bilder zu.



Schritt 6 - Grafikpipeline Die



Grafikpipeline in Vulkan wird durch Erstellen eines VkPipeline- Objekts konfiguriert . Es beschreibt den konfigurierbaren Status der Grafikkarte, z. B. die Größe des Ansichtsfensters oder den Tiefenpufferbetrieb , sowie den programmierbaren Status mithilfe von VkShaderModule- Objekten . VkShaderModule- Objekte werden aus dem Shader- Bytecode erstellt . Der Treiber muss außerdem angeben, welche Renderziele in der Pipeline verwendet werden sollen. Wir setzen sie, indem wir auf den Renderpass verweisen.



Eines der herausragendsten Merkmale von Vulkan im Vergleich zu vorhandenen APIs ist, dass fast alle Einstellungen der Systemgrafik-Pipeline vorkonfiguriert werden müssen. Dies bedeutet, dass Sie die Grafikpipeline vollständig neu erstellen müssen, wenn Sie zu einem anderen Shader wechseln oder das Scheitelpunktlayout geringfügig ändern möchten. Daher müssen Sie im Voraus viele VkPipeline- Objekte für alle Kombinationen erstellen, die für Rendervorgänge erforderlich sind. Nur einige Grundeinstellungen wie die Größe des Ansichtsfensters und die klare Farbe können dynamisch geändert werden. Alle Zustände müssen explizit beschrieben werden. So gibt es beispielsweise keinen Standardstatus für die Farbmischung.



Glücklicherweise hat der Treiber mehr Optimierungsmöglichkeiten und die Leistung ist vorhersehbarer, da der Prozess eher der vorausgehenden Kompilierung ähnelt, anstatt im laufenden Betrieb zu kompilieren, da signifikante Statusänderungen, wie z. B. das Wechseln zu einer anderen Grafikpipeline, explizit angegeben werden.



Schritt 7 - Befehlspool und Befehlspuffer



Wie bereits erwähnt, müssen viele Vorgänge in Vulkan, z. B. Zeichenvorgänge, in die Warteschlange gestellt werden. Vor dem Senden von Vorgängen müssen sie in den VkCommandBuffer geschrieben werden . Befehlspuffer stammen aus dem VkCommandPool , der einer bestimmten Warteschlangenfamilie zugeordnet ist. Um ein einfaches Dreieck zu zeichnen, müssen wir einen Befehlspuffer mit den folgenden Operationen schreiben:



  • Starten Sie den Renderpass
  • Grafik-Pipeline binden
  • Zeichne 3 Eckpunkte
  • Renderpass beenden


Da die Instanz des Bildes im Framebuffer davon abhängt, welches Bild die Anzeigekette uns liefert, müssen wir für jedes mögliche Bild einen Befehlspuffer schreiben und den auswählen, den wir beim Zeichnen benötigen. Wir können den Befehlspuffer jedes Mal für jeden Frame schreiben, aber dies ist weniger effizient.



Schritt 8 - Hauptschleife



Nachdem wir die Zeichenbefehle an den Befehlspuffer gesendet haben, scheint die Hauptschleife einfach genug zu sein. Zuerst erhalten wir das Bild aus der Showkette mit vkAcquireNextImageKHR. Wir können dann den entsprechenden Befehlspuffer für dieses Image auswählen und mit vkQueueSubmit ausführen . Schließlich geben wir das Bild zur Anzeige an die Anzeigekette zurück vkQueuePresentKHR.



An die Warteschlange gesendete Vorgänge werden asynchron ausgeführt. Daher müssen wir Synchronisationsobjekte - Semaphoren - verwenden, um die richtige Startreihenfolge sicherzustellen. Es ist erforderlich, die Ausführung des Puffers der Zeichenbefehle so zu konfigurieren, dass sie erst ausgeführt wird, nachdem das Bild aus der Anzeigekette abgerufen wurde. Andernfalls kann es vorkommen, dass ein Bild gerendert wird, das noch zur Anzeige auf dem Bildschirm gelesen wird. Der Aufruf vkQueuePresentKHRmuss wiederum warten, bis das Rendern abgeschlossen ist, wofür wir das zweite Semaphor verwenden werden. Es wird über das Ende des Renderns informiert.



Schlussfolgerungen



Diese kurze Übersicht gibt Ihnen einen Überblick über die Arbeit vor dem Zeichnen Ihres ersten Dreiecks. In Wirklichkeit gibt es viel mehr Schritte. Dazu gehören das Zuweisen von Scheitelpunktpuffern, das Erstellen einheitlicher Puffer und das Laden von Texturbildern. All dies werden wir in den nächsten Kapiteln behandeln. Beginnen wir jedoch zunächst einfach. Je weiter wir uns bewegen, desto schwieriger wird das Material. Beachten Sie, dass wir uns für den schwierigen Weg entschieden haben, indem wir zunächst die Scheitelpunktkoordinaten in den Scheitelpunkt-Shader eingebettet haben, anstatt den Scheitelpunktpuffer zu verwenden. Diese Entscheidung beruht auf der Tatsache, dass Sie zum Verwalten der Scheitelpunktpuffer zunächst mit den Befehlspuffern vertraut sein müssen.



Lassen Sie uns kurz zusammenfassen. Um das erste Dreieck zu zeichnen, benötigen wir:



  • Erstellen Sie VkInstance
  • Wählen Sie eine unterstützte Grafikkarte aus ( VkPhysicalDevice )
  • Erstellen Sie VkDevice und VkQueue zum Zeichnen und Anzeigen
  • Fenster, Fensterfläche erstellen und Kette anzeigen
  • Wickeln Sie Bilder der Anzeigekette in VkImageView
  • Erstellen Sie einen Renderpass, der die Renderziele und deren Verwendung definiert
  • Erstellen Sie einen Framebuffer für den Renderpass
  • Konfigurieren Sie die Grafikpipeline
  • Verteilen und schreiben Sie Zeichnungsbefehle für jedes Bild in der Anzeigekette in den Puffer
  • Rendern Sie Frames zu empfangenen Bildern, indem Sie den richtigen Befehlspuffer senden und die Bilder an die Anzeigekette zurückgeben


Trotz der Tatsache, dass es viele Schritte gibt, wird die Bedeutung jedes einzelnen in den folgenden Kapiteln klar. Wenn Sie keinen Schritt herausfinden können, kehren Sie zu diesem Kapitel zurück.



API-Konzepte



Dieses Kapitel schließt mit einem kurzen Überblick darüber, wie Vulkan-APIs auf einer niedrigeren Ebene strukturiert sind.



Codierungsstandard



Alle Vulkan-Funktionen, -Aufzählungen und -Strukturen sind unter einer Überschrift gekennzeichnet vulkan.h, die im von LunarG entwickelten Vulkan SDK enthalten ist. Die Installation des SDK wird im nächsten Kapitel behandelt.



Funktionen werden vkin Kleinbuchstaben vorangestellt , Aufzählungstypen (Aufzählung) und Strukturen werden vorangestellt Vk, und Aufzählungswerte werden vorangestellt VK_. Die API verwendet in großem Umfang Strukturen, um Parameter für Funktionen bereitzustellen. Beispielsweise werden Objekte normalerweise nach dem folgenden Muster erstellt: Bei



Bild



vielen Strukturen in Vulkan müssen Sie den Strukturtyp im Element explizit angeben sType. Ein Mitglied pNextkann auf eine Erweiterungsstruktur verweisen und ist immer vom Typnullptr... Funktionen, die ein Objekt erstellen oder zerstören, haben einen VkAllocationCallbacks- Parameter , mit dem Sie Ihren eigenen Speicherzuweiser verwenden können und der im Handbuch auch einen Typ hat nullptr.



Fast alle Funktionen geben VkResult zurück , bei dem es sich VK_SUCCESSentweder um einen Fehlercode handelt. Die Spezifikation gibt an, welche Fehlercodes jede Funktion zurückgeben kann und was sie bedeuten.



Validierungsschichten



Wie bereits erwähnt, wurde Vulkan entwickelt, um eine hohe Leistung bei geringer Treiberlast zu erzielen. Daher enthält es nur sehr eingeschränkte Funktionen zur automatischen Fehlererkennung und -korrektur. Wenn Sie einen Fehler machen, stürzt der Treiber ab oder schlimmer, arbeitet weiter an Ihrer Grafikkarte, schlägt aber bei anderen Grafikkarten fehl.



Daher können Sie mit Vulkan eine erweiterte Validierung mithilfe einer Funktion ausführen, die als Validierungsebenen bezeichnet wird... Validierungsebenen sind Codeteile, die zwischen der API und dem Grafiktreiber eingefügt werden können, um eine zusätzliche Validierung der Funktionsparameter durchzuführen und Speicherverwaltungsprobleme zu verfolgen. Dies ist insofern praktisch, als Sie sie während der Entwicklung starten und dann beim Starten des Programms ohne zusätzliche Kosten vollständig deaktivieren können. Jeder kann seine eigenen Validierungsebenen schreiben, aber das Vulkan SDK von LunarG bietet einen Standardsatz, den wir im gesamten Lernprogramm verwenden werden. Sie müssen auch eine Rückruffunktion registrieren, um Debug-Nachrichten von Ebenen zu empfangen.



Da die Vorgänge in Vulkan sehr detailliert sind und die Validierungsebenen sehr umfangreich sind, können Sie die Ursache des schwarzen Bildschirms im Vergleich zu OpenGL und Direct3D viel einfacher ermitteln.



Es ist nur noch ein Schritt übrig, bevor wir mit dem Codieren beginnen, und das ist das Einrichten der Entwicklungsumgebung.



All Articles