Vulkan - Nachfolger von OpenGL

EDIT: Hier geht es um den OpenGL-Nachfolger “Vulkan”: https://www.khronos.org/vulkan Die ersten Beiträge sind die dafür relevanten Teile, die ich aus http://forum.byte-welt.net/java-forum-erste-hilfe-vom-java-welt-kompetenz-zentrum/spiele-und-multimedia-programmierung/16323-licht-opengl.html#post117342 hierher kopiert habe

Naja OpenGL ist mittlerweile begraben worden und Vulkan steigt auf (was für ein passender Name wenn ich darüber nachdenke) bin gespannt was Fancy über das neue Projekt denkt.

Vulkan beobachte ich natürlich auch. Und auch wenn es ursprünglich als „Next Generation OpenGL“ entwickelt wurde, scheint es inzwischen mehr eine Low Level Alternative zu OpenGL zu werden. Aber abwarten was passiert, im Moment ist das was bekannt ist alles noch zu sehr vom Marketing geprägt. Hier hat jemand eine schöne Zusammenfassung geschrieben.

Aber einfach wird Vulkan wohl nicht werden. ~600 Zeilen für ein nacktes Dreieck.

@Marco13 : lol :wink:

Gruß
Fancy

@Fancy : Auch ich habe ein Auge auf Vulkan ( jvulkan.org :wink: ) aber bisher gibt es (auch in dieser „schönen“ Zusammenfassung) ja praktisch keine Informationen, die über die (schon seeeeh high-leveligen) Slides von https://www.khronos.org/vulkan hinausgehen. Das „interessanteste“ war da noch der Screenshot von dem Debugger, in dem Zumindest ein paar Codezeilen zu sehen waren. Deswegen frage ich mich, wo du die Information mit den 600 Zeilen her hast. (Das klingt schon viel. Das sind ja mindestsens 50 mehr als bei (non-deprecated, core) OpenGL :eek: :o) )

@Marco13 : Interessant :wink: Angedacht hab ich so was auch schon (net.nanojl:vk) bin mir aber noch nicht sicher inwieweit ich da wirklich Zeit rein stecke, da meine Wrapper Technik nicht auf Structs optimiert ist und Vulkan sehr struct lastig zu sein scheint.

Die 600 Zeilen hab ich aus diesem Video. Am Anfang stellt jemand von AMD den Aufbau eines Vulkan Programms dar, da sieht man schon ein wenig wie das später funktionieren soll. Am Ende in der Q&A fragt ein Zuschauer nach einem Hallo World und einer der Moderatoren vergleicht dann die 5 Zeilen von früher (glBegin, glVertex, glEnd) mit 600 Zeilen Vulkan. :slight_smile:

Gruß
Fancy

Nachdem ich das Video jetzt endlich mal gesehen habe, bin ich mir nicht sicher, wie groß das Salzkorn ist, das man zu diesen 600 Zeilen dazunehmen sollte. (Hä - klingt blöd).

Der Vergleich mit den GANZ alten OpenGL mit glBegin/glEnd etc. hinkt. Schon beim Übergang zu OpenGL 3.1, beim Wegfallen der Fixed Function Pipeline, trat ja genau dieser Effekt auf: Etwas, was vorher aus 5 Zeilen mit glBegin/glVertex/glEnd bestand, brauchte dann auf einmal einige hundert Zeilen, um

  • Shader zu laden
  • Shader zu compilieren
  • Den Shadern selbst (!)
  • VAO und VBOs händisch aus indizes zusammenzufrickeln
  • Komplizierte draw-Calls abzusetzen
    … Und natürlich eine Externe Matrix-Library (!) :verzweifel:

Schon damals, nämlich am 13.11.2009 (!) hatte ich ja einen entsprechenden Rant abgelassen (an einer Stelle, auf die ich jetzt nicht verlinke, aus der ich mich aber mal dreist zitiere) :

(Die Hervorhebungen habe ich jetzt mal eingefügt, … weil… irgendwie finde ich, dass das zum Thema passt :cool: )

Hätte man damals OpenGL 3.1 als neue API angelegt, hätte man jetzt vielleicht wieder das Problem das man Änderungen hat, die zum vorherigen inkompatibel sind. Ggf. hätte man dann jetzt wieder einen neuen Namen gebraucht. :wink:

Rein von der API hätten die jetzigen Neuerungen imho auch ruhig OpenGL 5.0 heißen können, wie auch ursprünglich geplant. Wenn man OpenGL als Bibliothek ansieht, mit der man Grafik darstellen kann, bei der die jeweilige Major-Version die technische Entwicklung der Grafikkarten widerspiegelt, würde das passen. Nur weil OpenGL 3.1 core zu OpenGL 1.1 inkompatibel ist, heißt ja nicht das man nicht trotzdem noch ein OpenGL 1.1 Programm schreiben kann. Man muss nur beim Anlegen des Kontextes die gewünschte Version übergeben.

Dabei ist Vulkan dann allerdings fundamental anders, es ist zustandslos (*). Außerdem wird der „Kontext“ erstmals über einen Loader bereit gestellt, der (vermutlich) teil der Vulkan Spezifikation sein wird (bei OpenGL ist das nicht so, WGL/GLX sind nicht OpenGL).

Andererseits vermute ich große Teile von modernem OpenGL in Vulkan. Statt glDrawElements(GL_TRIANGLE_STRIP, QUAD_IBO.length, GL_UNSIGNED_SHORT, 0); heißt es dann vkCmdDrawElements(cmdBuffer, VK_TRIANGLE_STRIP, QUAD_IBO.length, VK_UNSIGNED_SHORT, 0); (geraten :wink: ).

Das was der von AMD zeigt, scheint mir mehr eine Schicht zu sein, die nochmal um das übliche (VAO, VBO, IBO, Shader,…(dann nur zustands- und bindungslos) drumherum gewickelt wird. Es ist das was der OpenGL Treiber jetzt im Hintergrund macht und zukünftig in die Anwendung verschoben wird. Einfachere Treiber werden durch komplexere Anwendungen erkauft. Schreibt man eine Engine ermöglicht das fine tuning. Will man nur ein Dreieck, ermöglicht das Kopfschmerzen. :wink:

Aber wir sind wohl beide auf das erste „Hello World“ gespannt :wink:

*Zustandslos: Bei OpenGL empfinde ich static als grundlegend falsch, da ein (GL-)Kontext einen Zustand hat und es mehre verschiedene geben kann. Bei Vulkan scheint das anders zu werden. Dort wird als erster Parameter immer (imho) ein struct übergeben in dem sich der Zustand widerspiegelt. Das würde static legitimieren. Hast Du schon drüber nachgedacht wie Du jVulkan (Jvulkan/JVulkan/JVULKAN) gestalten willst? (1:1 vergleichbar mit JOCL oder mehr Java-risiert?)

Gruß
Fancy

Ich empfand es immer als Schade das wir zwar auf der einen Seite Chips haben die eine Bazillionen Flops können aber es dann an der Kommunikation zwischen CPU und GPU scheitert und dadurch Flaschenhälse entstehen durch die wir viel Rechenpower verschenken.

Die Antwort nach Jahrzenten Misserfolgsforschung und Stillstand lautet jetzt
Macht den ganzen Sch* doch selber!**
!?
Mantle und die Folgereaktionen Dx12 und Vulkan sind keine Lösungen sondern verlagern einfach nur das Problem und die ganze damit verbundene Arbeit.

Der Schritt “Vulkan” deutet für mich an das Khronos sich eindeutig von OGL abgrenzen möchte: Vulkan ist nicht OpenGL! OpenGl ist tot.

OGL hat mir im Vergleich zu Dx nie wirklich gefallen. War halt im Java Umfeld die einzige Möglichkeit aber unnötig kompliziert und praktisch das Gegenteil von Java.
Von daher trauer ich OGL nicht hinterher aber ob Vulkan da soviel besser wird und nicht doch noch komplizierter?
Bis jetzt ist Vulkan das StarCitizen unter den GrafikAPIs, es soll alles können und alle Interessen befriedigen jedoch hat man davon eigentlich nich garnichts gesehen.

[QUOTE=Fancy]Hätte man damals OpenGL 3.1 als neue API angelegt, hätte man jetzt vielleicht wieder das Problem das man Änderungen hat, die zum vorherigen inkompatibel sind. Ggf. hätte man dann jetzt wieder einen neuen Namen gebraucht. :wink:

Rein von der API hätten die jetzigen Neuerungen imho auch ruhig OpenGL 5.0 heißen können, wie auch ursprünglich geplant. Wenn man OpenGL als Bibliothek ansieht, mit der man Grafik darstellen kann, bei der die jeweilige Major-Version die technische Entwicklung der Grafikkarten widerspiegelt, würde das passen. Nur weil OpenGL 3.1 core zu OpenGL 1.1 inkompatibel ist, heißt ja nicht das man nicht trotzdem noch ein OpenGL 1.1 Programm schreiben kann. Man muss nur beim Anlegen des Kontextes die gewünschte Version übergeben.
[/QUOTE]

Es ist ja nicht so, dass die wichtigsten Dinge, die jetzt als die treibenden Kräfte hinter Vulkan dargestellt werden, nicht schon vor 6 Jahren bekannt gewesen wären. Und vielleicht ist der Grund dafür, dass du meinst, Vulkan könnte auch GL5.0 heißen, der, dass der Unterschied zwischen GL3 und Vulkan gar nicht so groß ist - bzw. in mancher Hinsicht schon kleiner als der zwischen GL1 und GL3.

Einerseits stimme ich zu, dass man einfach sagen könnte: GL ist „die erste, dünne und naheliegende Schicht über dem, was die GraKa-Hersteller eben in ihren Treibern machen - und deswegen ändert es sich (ggf. auch fundamental) im Lauf der Zeit“. Aber wenn genug Änderungen „akkumuliert“ sind, so dass die Altlasten wirklich lästig werden und das ganze ausbremsen und Innovationen behindern, muss man eben einen Schnitt machen. Auch wenn ich mich mit vielen Details nicht so auskenne, scheint DAS ja zumindest (teilweise) durch diese „Profiles“ versucht worden zu sein. Aber eben immer mit der angezogenen Handbremse - z.B. resultierend aus dem, was die Überschrift eines der ersten Kapitel des RedBooks war: OpenGL is a state machine. Was zum nächsten Punkt führt…

Das sehe ich auch als einen DER wchtigsten Änderungen von Vulkan gegenüber GL: Eine Zustandsmaschine und Multicore passen eben so gut zusammen wie ein Fisch und ein Fahrrad. Dass die GPU ausgebremst wird, dadurch, dass (etwas plakativ gesagt) alles auf einem Thread passieren muss, und dieser eine Thread eben Daten hin- und her schaufelt und Shader compiliert, anstatt die FPS hoch zu halten, scheint ja wirklich ein Problem gewesen zu sein.

Zugegeben, mit vielen technischen Details (Kontextbereitstellung, Profiles, Loader…) kenne ich mich nicht aus (und WILL ich mich auch nicht auskennen: Ich will irgendwas plattformunabhängiges, was mir ein Fenster aufmacht, wo ich reinzeichnen kann). Aber wie schon angedeutet: Dass bestimmte Dinge in Vulkan wieder auftauchen, die man vom NEUEN (!) GL schon kennt, ist ja klar (das sage ich ja die ganze Zeit): Man will Buffer und Shader verwalten, und sagen, was passieren soll, wenn denn ein „Frame“ erscheinen soll. In diesem Sinne: Solange nicht irgendwann das Vulkan-Hello-World auftaucht, und dort

vkBegin(VK_TRIANGLES);
vkVertex(0,1,2);
vkVertex(3,4,5);
vkVertex(5,6,7);
vkEnd();

drinsteht, ist ja alles in Ordnung :smiley:

Das ist ja auch eine der Fragen, die in dem Video gestellt werden (und eine IMHO sehr gute Frage). Aber auch da sehe ich Parallelen zum Schritt von GL1 auf GL3: „Ich soll ein Dreieck zeichnen? Boah, keine Ahnung, wie das gehen soll. Erklär’ mir das mal, in einem Shader“ → code, code → Schwarzes Fenster → „Hey, in deinem Shader stimmt irgendwas nicht“. (Das ist nicht wertend. Es ist einfach so. Wer sich nicht mit den Dingen beschäftigen will, die nah am Metall sind, soll eben eine Engine verwenden)

Zugegeben, das mit dem „jvulkan.org“ (und „webvulkan.org“ - WebGL wird ja auch langsam erwachsen ;-)) war eine spontan-Aktion, nur zur Sicherheit.

Dass die static-Methoden bei GL eigentlich „Unfug“ sind, hast du schon ein paarmal versucht, mir (als Rechtfertigung des GL-Objektes in JOGL) zu erklären - so richtig kapiert hab’ ich es immernoch nicht :frowning:

Es gibt natürlich einige Dinge, die ich speziell bei JCuda (aber auch bei JOCL) gemacht habe, und wo ich mir später gedacht habe: „Sh!t, das würde ich heute anders machen“. Das bezieht sich weniger auf die Frage, ob es „Low-Level“ oder „High-Level“ sein sollte. Vielmehr auf Fragen des DLL-Loadings, oder wie diese Funktionspointer definiert und an die „echten“ Funktionen gebunden werden. Aber auch auf die Frage: Static oder nicht? Z.B. finde ich es nicht so schön, wenn in den static-Funktionen ein flag sein muss, um Fehler abzufragen. Im Moment ist es eben so:

static void someCudaMethod() {
    int errorcode = nativeCudaMethod();
    if (errorcode != SUCCESS) throw CudaException();
}

Eigentlich könnte man sowas schöner (und mit weniger Overhead) lösen. Nämlich so, wie es IIRC bei JOGL gemacht ist: Es gibt ein „GL“, das direkt die Funktionen aufruft, und ein „DebugGL“, das da drumgewickelt werden KANN, und die Fehlerchecks macht.
Demgegenüber steht, dass bei Vulkan jetzt aber so ein „Vulkan-Objekt“ vielleicht DOCH nicht mehr nötig wäre. Ich hatte dann auch schon Zwischenlösungen in Betracht gezogen - sowas wie

class Vulkan {
    static VulkanInterface v = new NativeVulkan();

    static void someVulkanMethod() {
        v.someVulkanMethod();
    }

    void setDebugging(boolean debug) {
        if (debug) v = new DebugVulkan(new NativeVulkan());
        else       v = new NativeVulkan();
}

aber das muss man sich alles noch genauer überlegen.

Wenn es mit JVulkan wirklich losgeht, würde ich mir über fundierte Empfehlungen natürlich freuen ::manwink

Zur Frage „1:1 oder Java-isiert“: Sowohl bei JCuda als auch bei JOCL stand ursprünglich der Gedanke im Raum, ganz dumm-dreist ein 1:1-Mapping der Original API anzubieten - denn jede Abstraktion, die man an der JNI-Schicht einführen würde, hätte (fast zwangsläufig) zur Folge, dass irgendeine Funktionalität nicht mehr abbildbar wäre. Aber um diese Low-Level-API sollte natürlich (eigentlich, wenn man sich gut genug damit auskennte und Zeit hätte) eine schöne, bequeme, Java-idomatische, Objektorientierte API gewickelt werden.

Und die API von Vulkan (soweit man sie aus den Snippets im Video erahnen kann) scheint ja sehr objektorientiert zu sein. (So objektorientiert, wie eine C-API eben sein kann. Ich erinnere mich daran, dass der WATCOM-C-Debugger mir damals bei C+±Programmen immer ein „this*“ als ersten (Member-)Funktions parameter angezeigt hat ;-)). Es würde sich anbieten, da (über ein Low-Lelve-Interface) eine Schicht zu stülpen, ähnlich wie es sich bei OpenCL schon mit den „CommandQueue“ und „Buffer“-Objekten angeboten hat.

Threadsicherheit wäre dabei natürlich DAS ganz große Thema. Und weitere Detailfragen würden sich immer stellen: Z.B. ob beim Aufsetzen auf eine 1:1-Anbindung nicht SO viele JNI-Calls abgesetzt werden müßten, dass das ausbremst (und es dann nicht vielleicht DOCH besser wäre, die OO-Schicht auf eine abstrahierende JNI-Schicht aufsetzen zu lassen - ggf. parallel zur (dann, auf Wunsch noch optional verwendbaren) 1:1-Abbildung.

[QUOTE=TMII;117778]Ich empfand es immer als Schade das wir zwar auf der einen Seite Chips haben die eine Bazillionen Flops können aber es dann an der Kommunikation zwischen CPU und GPU scheitert und dadurch Flaschenhälse entstehen durch die wir viel Rechenpower verschenken.
[/quote]

Ja, das scheint ja (wie weiter oben schon angedeutet) einer der wichtigsten Punkte bei Vulkan zu sein. Dass man eigentlich „Bazillionen“ von Dreiecken rendern könne, es aber daran scheitert, dass die (eine!) CPU nicht damit nachkommt, der GPU (immer auf demselben Thread) zu sagen: „Mal mal! Mal mal! Mal mal!“ ist schon frustrierend.

Ziemlich drastische Formulierung, aber … grundsätzlich finde ich es nicht falsch, die Option für ein neues Programmierparadigma zu schaffen, und z.B. Aufgaben aus dem Treiber heraus auf die Anwendungs (bzw. Engine-) Seite zu verlagern, wenn das viele der Altlasten-Probleme löst.

Irgendwas sagt mir, dass man auch in 20 Jahren noch GL-Programme mit glBegin/glEnd & Co schreiben KÖNNEN wird. Schon die ganzen CAD-Leute werden die GraKa-Hersteller dazu nötigen, doch gefälligst dafür zu sorgen, dass OpenGL-DisplayLists transparent in Vulkan-Buffer übersetzt werden. Und das muss ja nicht verkehrt sein. Solange für NEUE Software auch NEUE und bessere Möglichkeiten existieren, ist das Ziel ja erreicht. Und wie gut dieses Ziel mit Vulkan erreicht wird, wird sich noch zeigen. Einen Monat nach der Ankündigung kann man noch nicht viel mehr als Spekulationen und Gerüchte (und ggf. ein bißchen Marketinggelaber) erwarten.

EDIT: Hups, ist ein bißchen länger geworden…

[QUOTE=Marco13]In diesem Sinne: Solange nicht irgendwann das Vulkan-Hello-World auftaucht, und dort

vkBegin(VK_TRIANGLES);
vkVertex(0,1,2);
vkVertex(3,4,5);
vkVertex(5,6,7);
vkEnd();

drinsteht, ist ja alles in Ordnung :D[/quote]

Vielleicht:

vkBeginCommandBuffer(cmdBuffer, VK_TRIANGLES);
vkCmdVertex(cmdBuffer, 0, 1, 2);
vkCmdVertex(cmdBuffer, 3, 4, 5);
vkCmdVertex(cmdBuffer, 5, 6, 7);
vkCmdPipelineBarrier(cmdBuffer, barrier);
vkEndCommandBuffer(cmdBuffer);

:smiley:

Ich nutze dazu bei mir ein Java Proxy. Das kann man dann nicht nur für die gesamte Anwendung aktivieren, sondern auch z.B. nur das gl-Objekt einer Klasse damit einwickeln. Außerdem können die meisten Methoden einfach durch gereicht werden (+glGetError()) oder einzelne Methoden eine zusätzliche Prüfung bekommen.

Konkret bei net.nanojl:al
[spoiler]```package net.nanojl.al;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

import net.nanojl.al.header.Al;
import net.nanojl.al.header.AlGetError;
import net.nanojl.injector.Assisted;
import net.nanojl.injector.IscLicense;

import org.slf4j.Logger;

@IscLicense
final class JlAlProxy<T extends Al & AlGetError> implements InvocationHandler {

private final Logger logger;
private final StringBuilder sb = new StringBuilder();

private final T al;
private final boolean trace;
private final boolean exception;

private final Map<String, Method> methods = new HashMap<>();

private boolean enabled;


@Inject
private JlAlProxy(@Assisted final Logger logger, @Assisted final T al, @Assisted final boolean trace, @Assisted final boolean exception) {

    this.logger = logger;
    this.al = al;
    this.trace = trace;
    this.exception = exception;

    for (final Method m : getClass().getMethods())
        methods.put(m.getName(), m);

}


@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

    final String name = method.getName();

    if (!enabled && !name.equals("init") && !name.equals("getDeviceList"))
        throw new AlException(AlException.STATE_EXCEPTION);

    if (methods.containsKey(name))
        if (Arrays.equals(methods.get(name).getParameterTypes(), method.getParameterTypes()))
            methods.get(name).invoke(this, args);

    final Object result = method.invoke(al, args);

    if (name.equals("init"))
        enabled = true;

    if (name.equals("dispose"))
        enabled = false;

    if (trace)
        log(method, args, result);

    if (exception && enabled)
        checkGetError();

    return result;

}


private void log(final Method method, final Object[] args, final Object result) {

    sb.delete(0, sb.length());
    sb.append(method.getName());
    sb.append("(");

    if (args != null && args.length > 0) {

        for (final Object o : args)
            sb.append(o.toString()).append(", ");

        sb.delete(sb.length() - 2, sb.length());

    }

    if (method.getReturnType() != void.class) {

        sb.append("): ");
        sb.append(result);

    } else

        sb.append(") ");

    logger.debug("{}", sb.toString());

}


private void checkGetError() {

    final int error = al.alGetError();

    if (error != 0)
        throw new AlException("alGetError() results in " + error);

}


@SuppressWarnings("unused")
private void alGenSources(final int n, final IntBuffer sources) {

    if (n > sources.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alDeleteSources(final int n, final IntBuffer sources) {

    if (n > sources.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alSourcePlayv(final int ns, final IntBuffer sids) {

    if (ns > sids.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alSourceStopv(final int ns, final IntBuffer sids) {

    if (ns > sids.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alSourceRewindv(final int ns, final IntBuffer sids) {

    if (ns > sids.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alSourcePausev(final int ns, final IntBuffer sids) {

    if (ns > sids.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alSourceQueueBuffers(final int sid, final int numEntries, final IntBuffer bids) {

    if (numEntries > bids.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alSourceUnqueueBuffers(final int sid, final int numEntries, final IntBuffer bids) {

    if (numEntries > bids.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alGenBuffers(final int n, final IntBuffer buffers) {

    if (n > buffers.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alDeleteBuffers(final int n, final IntBuffer buffers) {

    if (n > buffers.remaining())
        throw new AlException(AlException.SIZE_EXCEPTION);

}


@SuppressWarnings("unused")
private void alBufferData(final int bid, final int format, final ByteBuffer data, final int size, final int freq) {

    if (size > data.remaining() * 1)
        throw new AlException(AlException.SIZE_EXCEPTION);

}

}



Nachteil ist natürlich das es (vermutlich) etwas langsamer ist. Ein weiterer Nachteil ist das es zwar mit dem Funktionsumfang von GL1 – GL4 funktioniert, aber ein GLAlles nicht geht, weil ein Java Proxy nicht mit so vielen Methoden erzeugt werden kann.

[QUOTE=Marco13;117789]Wenn es mit JVulkan wirklich losgeht, würde ich mir über fundierte Empfehlungen natürlich freuen ::manwink[/quote]

Ich bin gespannt. Wobei wir ja bei Vulkan wieder alle Anfänger sein werden. ;)

Gruß
Fancy

In bezug auf die API ja. Aber nicht in bezug auf die Frage, wie man „solche“ APIs am „besten“ nach Java rüberschleift (inkulsive der ganzen Fragen von DLL-Handling, FunctionPointern, Typkonvertierungen etc…)

Hattest du nicht auch mal so eine „Invocation Library“ gemacht, wo man mit ganz allgemeinen Stubs (und IIRC, irgendwelchen magischen Inline-Assembler-Rücksprüngen etc) beliebige Funktionen aufrufen konnte?

Ich finde Proxies ja toll. Aber für etwas mit „vielen“ (extrem Performancekritischen) Funktionen erscheinen die mir weniger geeignet. Man müßte mal genauere Zeitmessungen machen. (Der Punkt, das für die JNI-Funktionen bei JCuda und JOCL zu machen, steht schon seit Jahren auf meiner TODO-Liste. Aber zugegeben: Es ist „kaum relevant“. Natürlich wäre es relevant, wenn man Alternativen in betracht ziehen will - z.B. wenn man feststellen will, dass die 1:1-Low-Level-Anbindung durch diesen Overhead für bestimmte Anwendungsszenarien zu langsam wird. Aber diese Alternativen stehen (für ich) nicht im Raum. Und solange man sich nicht ins HotSpot-SCM reinhackt und dafür sorgt, dass die eigenen Funktionen auf einmal Intrinsics sind, dürfte es kaum etwas geben, was einen geringeren Overhead hat, als JNI ;)). Sowas wie MethodHandle (Java Platform SE 8 ) wollte ich mir aber auch schon ewig mal ansehen…

[ot]
BTW: Die Aufrufbarkeit von Methoden ist so eine Sache. Eigentlich müßte man da Chapter 15. Expressions nachimplementieren. Aber zumindest sowas wie das hier sollte man ggf. testen:

    /**
     * Returns whether the given arguments are assignable to the
     * respective types
     * 
     * @param types The types
     * @param args The arguments
     * @return Whether the arguments are assignable
     */
    private static boolean areAssignable(Class<?> types[], Object ...args)
    {
        if (types.length != args.length)
        {
            return false;
        }
        for (int i=0; i<types.length; i++)
        {
            Object arg = args**;
            Class<?> type = types**;
            if (arg != null && !type.isAssignableFrom(arg.getClass()))
            {
                return false;
            }
        }
        return true;
    }

[/ot]

Ja, das taucht demnächst hier im Projektbereich als net.nanojl:stranger auf. :wink: Nachdem ich gerade den net.nanojl:injector soweit fertig gemacht habe, steht das als nächstes an. Zusammen damit kommt dann auch der net.nanojl:al als OpenAL Wrapper, der ist sozusagen das KSKB zum net.nanojl:stranger.

Hmm, entweder haben wir aneinander vorbeigeschrieben oder ich stehe gerade auf dem Schlauch. Bei mir sieht das im wesentlichen so aus:

    void alEnable(final int capability);   
}


final class AlImpl implements Al10 {
    
    private long alEnableAddr = stranger.resolve("alEnable", library);
    
    @Override
    public void alEnable(final int capability) {
        vCall(alEnableAddr, 0, capability);
    }
    
    private native void vCall(final long address, final long alignment, final int i);

}```

Der normale Aufruf ist dann:

Anwendung -> Al10#alEnable -> AlImpl#alEnable -> AlImpl#vCall

Das ist (auf der Java-Seite) so ähnlich wie bei Dir, nur ohne static und ohne Prüfungen. Wenn der Nutzer nun den Proxy um sein al wickelt, verändert sich der Aufruf:

Anwendung -> Al10#alEnable -> JlAlProxy#invoke -> (ggf. JlAlProxy#alEnable) -> AlImpl#alEnable -> AlImpl#vCall -> (ggf. AlImpl#alGetError -> AlImpl#iCall)

Klar ist das langsamer, aber ist das wirklich schlimm? Die Debug-Funktion wird ja höchstens beim Entwickeln genutzt und dabei ist mir das bisher noch nicht negativ aufgefallen. Wenn das tatsächlich zu einem Problem werden würde, könnte man den Proxy immer noch zu einer normalen Klasse machen, die prüft und an die AlImpl delegiert. Das sollte dann auch der JIT optimieren können.

Viele Grüße
Fancy

Bin gespannt. Habe nur noch dunkle (also nicht schlechte, sondern schwache ;-)) Erinnerungen, und meine, dass ich etwas kritisch war, inwieweit dieses Assembler-Zeug denn 1. Plattformunabhängig (32/64bit) und 2. auf spezifiziertem Verhalten aufbauend ist…

Hmm, entweder haben wir aneinander vorbeigeschrieben oder ich stehe gerade auf dem Schlauch. Bei mir sieht das im wesentlichen so aus:

Ersteres. Ich dachte, es würde IMMER über einen Proxy gegangen. Aber so, „normalerweise“, ja nur über ein Interface. Und wo wir schon bei Assembler sind: OB und WAS da optimiert wird, geht vielleicht aus The Black Magic of (Java) Method Dispatch hervor :smiley:

Mal ein kleiner Einwurf als unwissender:

Was heißt denn jetzt die aussage “opengl ist tot”, seid ihr sicher, dass es aufgegeben und ggf bald nicht mehr weiterentwickelt wird?
Lohnt es sich überhaupt nicht mehr, es weiter zu lernen?
Und vor allem, was ist eigentlich mit der mobilen welt (zumindest was android angeht) ?

IMHO ist Vulkan doch mehr oder minder der Nachfolger von AMDs Mantle. Und ich habe das z.B. so verstanden das Vulkan != OpenGL ist, was auch durch den anderen Namen ausgedrückt werden soll. Es scheint ja auch wesentlich lowleveliger.

@mymaksimus Nun, teilweise wurde das schon erwähnt, aber das „TL;DR“ ist verständlich. Kurz:
Vulkan soll OpenGL ersetzen (man kann aber davon ausgehen, dass OpenGL als solches noch eine ganze Weile weiterleben wird). Wichtiger: Viele Konzepte werden gleich oder ähnlich sein. Auch bei Vulkan wird es Buffer mit Vertexdaten und Attributen geben, und Shader (GLSL) die darauf rumrechnen. Und sowas wie Projektions- und Transformationsmatrizen wird man auch in 100 Jahren noch brauchen, wenn man was mit 3D-Grafik machen will - egal, wie die API heißt, mit der man das dann macht :wink: Dass bei Vulkan die API ggf. etwas low-leveliger ist, man ggf. noch mehr aufpassen muss, nichts falsch zu machen, und ggf. noch öfter mit einem schwarzen, leeren Fenster belohnt wird, wenn man DOCH was falsch macht, spielt erstmal keine so große Rolle. Über Details und Eigenheiten von Vulkan (bzw. WAS dort nun genau WIE anders ist, als bei OpenGL) kann man viel spekulieren, aber (auch @Unregistered10 ) der Ausgangspunkt für Vulkan war AMDs Mantle (ein paar erklärende Worte dazu in dem Video auf das Fancy verlinkt hatte: Vulkan and SPIR-V session - YouTube ), d.h. wenn man sich das mal genauer anschaut, hat man vielleicht schon eine GROBE Idee, was einen bei Vulkan erwartet (ist aber auch nur eine Vermutung…)

Jupp, bisher wissen wir noch nichts.

Vulkan bedeutet wohl soviel wie das die Rückwärtskompatibilität zu OGL nicht mehr eingehalten wird und Karten die Vulkan können, können eventuell kein OGL mehr.
Also wird in Zukunft Vulkan ganz langsam OGL ablösen wenn OGL denn nicht weiterentwickelt wird (man weiß nie was die Zukunft bringt, vielleicht gibt es auch "Fan"projekte die OGL weiterentwickeln).

Es ist jetzt nicht so das OGL von heute auf morgen verschwinden würde. Dann müssten binnen einer Nacht alle Grafikkartenhersteller auf Vulkan umstellen und OGL komplett rausschmeißen, alle auf OGL laufende Software der letzten XX Jahre müsste umgeschrieben werden oder einfach nicht mehr funktionieren und jeder Anwender müsste seine Software aktualisieren und Hardware gegen die neue Vulkan-Hardware austauschen.
Aber auch noch in 10Jahren wollen Anwender ihre Software verwenden die auf OGL basiert und auch noch in 10Jahren wird es Computer geben die auf alter Hardware laufen die nur OGL können.
Der mobile Markt ist wieder so eine Sache, Vulkan soll auch für mobile Geräte verwendbar sein und diesmal alles richtig machen was OGL ES versaut hat (alles noch Marketing geblubber) hier wird es eventuell sehr viel länger dauern bis Vulkan alle Endkunden mal erreicht hat. Zumindest OGL hat sehr lange gebraucht…

Ja, das sehe ich ähnlich. Nur ein Gedanke hierzu:

[QUOTE=TMII]
Vulkan bedeutet wohl soviel wie das die Rückwärtskompatibilität zu OGL nicht mehr eingehalten wird und Karten die Vulkan können, können eventuell kein OGL mehr.[/QUOTE]

In ähnlicher Form wurde das auch in dem Video angesprochen: Ich könnte mir gut vorstellen, dass es Abbildungen gibt - und zwar in beide Richtungen (soweit das möglich ist, also ggf. mit Einschränkungen). Also dass man mit Vulkan entwickeln kann, und das dann “irgendwo kurz vor dem Treiber” in OpenGL übersetzt wird, und umgekehrt, man GL entwickeln kann, das dann auf eine “Vulkan-Karte” abgebildet wird.

Schon heute gibt es ja AFAIK solche Ansätze, wo “glBegin/glVertex/glEnd”-Aufrufe in modernes GL übersetzt werden, indem durch diese Aufrufe dann eben VBOs gefüllt werden…

Moin,

Vulkan ist da: https://www.khronos.org/vulkan/
Beispiele von NVIDIA: https://developer.nvidia.com/Vulkan

Hab aber noch nicht rein gesehen…

Gruß
Fancy

Na, das passt ja. Nachdem ich vor 4 Jahren etwas zu JavaGL Rendering auf 404 - Not Found geschrieben hatte, und HEUTE (d.h. gestern) das ganze in Richtung von etwas „auf-GitHub-stellbaren“ gezogen habe, kommt jetzt der Hinweis, das alles, was ich da gemacht habe, schlagartig obsolet ist :rolleyes:

Wenn ich mir den Code so ansehe

CommandList commandList = new CommandList();
commandList.addCommands(
    Commands.setDefaultViewInputs(program, renderingEnvironment.getView()),
    Commands.setDefaultModelAndNormalMatrix(program, MatrixUtils.identity()),
    Commands.setTuple3f(program, Parameters.LIGHT_POSITION0, new Vector3f(10,10,10)),
    Commands.render(texturedCube)
);
renderingEnvironment.addCommandSource(commandList);

und dann über die Vulkan-Samples scrolle


          cmd  = res->createCmdBuffer(m_cmdPool, false, true,true);
          vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,...
          vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, ...
          vkCmdBindVertexBuffers(cmd, 0, 1, &glgeo.vbo, &glgeo.vboOffset);
          vkCmdBindIndexBuffer  (cmd, glgeo.ibo, glgeo.iboOffset, VK_INDEX_TYPE_UINT32);
          vkCmdDrawIndexed(cmd, di.range.count, 1, uint32_t(di.range.offset / sizeof(uint32_t)), 0, 0);
          vkEndCommandBuffer(cmd);

könnte man zwar meinen, dass da gewisse „konzeptuelle Parallelen“ bestehen, aber vermutlich ist das nur Wunschdenken. Auf den ersten Blick sieht es schon SEHR Low-Levelig aus…

Naja, morgen mal die Samples genauer anschauen. (Die sind natürlich wieder :verzweifel: :sick: :grr: geschrieben. Ich will keinen Benchmark mit Vergleichen von 20 über #if und #elif zusammengepfuschten Rendering-Methoden für ein CAD-Modell, sondern ein Dreieck. Einfach nur ein einzelnes Dreieck. „Hallo Welt“. Das hat schon seinen Sinn :rolleyes: )

*** Edit ***

Aber so kann man’s natürlich auch machen: Issues · KhronosGroup/Vulkan-Samples · GitHub

:smiley:

Ein Beispiel mit einem Dreieck gibt es doch im SDK. Zumindest glaube ich das, ich konnte den kurzen Quellcode noch nicht nachvollziehen :smiley: :

tri.c:

/*
 * Copyright (c) 2015-2016 The Khronos Group Inc.
 * Copyright (c) 2015-2016 Valve Corporation
 * Copyright (c) 2015-2016 LunarG, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and/or associated documentation files (the "Materials"), to
 * deal in the Materials without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Materials, and to permit persons to whom the Materials are
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice(s) and this permission notice shall be included in
 * all copies or substantial portions of the Materials.
 *
 * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 *
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
 * USE OR OTHER DEALINGS IN THE MATERIALS.
 *
 * Author: Chia-I Wu <olvaffe@gmail.com>
 * Author: Cody Northrop <cody@lunarg.com>
 * Author: Courtney Goeltzenleuchter <courtney@LunarG.com>
 * Author: Ian Elliott <ian@LunarG.com>
 * Author: Jon Ashburn <jon@lunarg.com>
 * Author: Piers Daniell <pdaniell@nvidia.com>
 */
/*
 * Draw a textured triangle with depth testing.  This is written against Intel
 * ICD.  It does not do state transition nor object memory binding like it
 * should.  It also does no error checking.
 */

#ifndef _MSC_VER
#define _ISOC11_SOURCE /* for aligned_alloc() */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>

#ifdef _WIN32
#pragma comment(linker, "/subsystem:windows")
#define APP_NAME_STR_LEN 80
#endif // _WIN32

#include <vulkan/vulkan.h>

#define DEMO_TEXTURE_COUNT 1
#define VERTEX_BUFFER_BIND_ID 0
#define APP_SHORT_NAME "tri"
#define APP_LONG_NAME "The Vulkan Triangle Demo Program"

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

#if defined(NDEBUG) && defined(__GNUC__)
#define U_ASSERT_ONLY __attribute__((unused))
#else
#define U_ASSERT_ONLY
#endif

#ifdef _WIN32
#define ERR_EXIT(err_msg, err_class)                                           \
    do {                                                                       \
        MessageBox(NULL, err_msg, err_class, MB_OK);                           \
        exit(1);                                                               \
    } while (0)
#else // _WIN32

#define ERR_EXIT(err_msg, err_class)                                           \
    do {                                                                       \
        printf(err_msg);                                                       \
        fflush(stdout);                                                        \
        exit(1);                                                               \
    } while (0)
#endif // _WIN32

#define GET_INSTANCE_PROC_ADDR(inst, entrypoint)                               \
    {                                                                          \
        demo->fp##entrypoint =                                                 \
            (PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk" #entrypoint); \
        if (demo->fp##entrypoint == NULL) {                                    \
            ERR_EXIT("vkGetInstanceProcAddr failed to find vk" #entrypoint,    \
                     "vkGetInstanceProcAddr Failure");                         \
        }                                                                      \
    }

#define GET_DEVICE_PROC_ADDR(dev, entrypoint)                                  \
    {                                                                          \
        demo->fp##entrypoint =                                                 \
            (PFN_vk##entrypoint)vkGetDeviceProcAddr(dev, "vk" #entrypoint);    \
        if (demo->fp##entrypoint == NULL) {                                    \
            ERR_EXIT("vkGetDeviceProcAddr failed to find vk" #entrypoint,      \
                     "vkGetDeviceProcAddr Failure");                           \
        }                                                                      \
    }

struct texture_object {
    VkSampler sampler;

    VkImage image;
    VkImageLayout imageLayout;

    VkDeviceMemory mem;
    VkImageView view;
    int32_t tex_width, tex_height;
};

VKAPI_ATTR VkBool32 VKAPI_CALL
dbgFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
        uint64_t srcObject, size_t location, int32_t msgCode,
        const char *pLayerPrefix, const char *pMsg, void *pUserData) {
    char *message = (char *)malloc(strlen(pMsg) + 100);

    assert(message);

    if (msgFlags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
        sprintf(message, "ERROR: [%s] Code %d : %s", pLayerPrefix, msgCode,
                pMsg);
    } else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
        sprintf(message, "WARNING: [%s] Code %d : %s", pLayerPrefix, msgCode,
                pMsg);
    } else {
        return false;
    }

#ifdef _WIN32
    MessageBox(NULL, message, "Alert", MB_OK);
#else
    printf("%s
", message);
    fflush(stdout);
#endif
    free(message);

    /*
     * false indicates that layer should not bail-out of an
     * API call that had validation failures. This may mean that the
     * app dies inside the driver due to invalid parameter(s).
     * That's what would happen without validation layers, so we'll
     * keep that behavior here.
     */
    return false;
}

typedef struct _SwapchainBuffers {
    VkImage image;
    VkCommandBuffer cmd;
    VkImageView view;
} SwapchainBuffers;

struct demo {
#ifdef _WIN32
#define APP_NAME_STR_LEN 80
    HINSTANCE connection;        // hInstance - Windows Instance
    char name[APP_NAME_STR_LEN]; // Name to put on the window/icon
    HWND window;                 // hWnd - window handle
#else                            // _WIN32
    xcb_connection_t *connection;
    xcb_screen_t *screen;
    xcb_window_t window;
    xcb_intern_atom_reply_t *atom_wm_delete_window;
#endif                           // _WIN32
    VkSurfaceKHR surface;
    bool prepared;
    bool use_staging_buffer;

    VkAllocationCallbacks allocator;

    VkInstance inst;
    VkPhysicalDevice gpu;
    VkDevice device;
    VkQueue queue;
    VkPhysicalDeviceProperties gpu_props;
    VkQueueFamilyProperties *queue_props;
    uint32_t graphics_queue_node_index;

    uint32_t enabled_extension_count;
    uint32_t enabled_layer_count;
    char *extension_names[64];
    char *device_validation_layers[64];

    int width, height;
    VkFormat format;
    VkColorSpaceKHR color_space;

    PFN_vkGetPhysicalDeviceSurfaceSupportKHR
        fpGetPhysicalDeviceSurfaceSupportKHR;
    PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR
        fpGetPhysicalDeviceSurfaceCapabilitiesKHR;
    PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
        fpGetPhysicalDeviceSurfaceFormatsKHR;
    PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
        fpGetPhysicalDeviceSurfacePresentModesKHR;
    PFN_vkCreateSwapchainKHR fpCreateSwapchainKHR;
    PFN_vkDestroySwapchainKHR fpDestroySwapchainKHR;
    PFN_vkGetSwapchainImagesKHR fpGetSwapchainImagesKHR;
    PFN_vkAcquireNextImageKHR fpAcquireNextImageKHR;
    PFN_vkQueuePresentKHR fpQueuePresentKHR;
    uint32_t swapchainImageCount;
    VkSwapchainKHR swapchain;
    SwapchainBuffers *buffers;

    VkCommandPool cmd_pool;

    struct {
        VkFormat format;

        VkImage image;
        VkDeviceMemory mem;
        VkImageView view;
    } depth;

    struct texture_object textures[DEMO_TEXTURE_COUNT];

    struct {
        VkBuffer buf;
        VkDeviceMemory mem;

        VkPipelineVertexInputStateCreateInfo vi;
        VkVertexInputBindingDescription vi_bindings[1];
        VkVertexInputAttributeDescription vi_attrs[2];
    } vertices;

    VkCommandBuffer setup_cmd; // Command Buffer for initialization commands
    VkCommandBuffer draw_cmd;  // Command Buffer for drawing commands
    VkPipelineLayout pipeline_layout;
    VkDescriptorSetLayout desc_layout;
    VkPipelineCache pipelineCache;
    VkRenderPass render_pass;
    VkPipeline pipeline;

    VkShaderModule vert_shader_module;
    VkShaderModule frag_shader_module;

    VkDescriptorPool desc_pool;
    VkDescriptorSet desc_set;

    VkFramebuffer *framebuffers;

    VkPhysicalDeviceMemoryProperties memory_properties;

    bool validate;
    PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback;
    PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback;
    VkDebugReportCallbackEXT msg_callback;

    float depthStencil;
    float depthIncrement;

    bool quit;
    uint32_t current_buffer;
    uint32_t queue_count;
};

// Forward declaration:
static void demo_resize(struct demo *demo);

static bool memory_type_from_properties(struct demo *demo, uint32_t typeBits,
                                        VkFlags requirements_mask,
                                        uint32_t *typeIndex) {
    // Search memtypes to find first index with those properties
    for (uint32_t i = 0; i < 32; i++) {
        if ((typeBits & 1) == 1) {
            // Type is available, does it match user properties?
            if ((demo->memory_properties.memoryTypes**.propertyFlags &
                 requirements_mask) == requirements_mask) {
                *typeIndex = i;
                return true;
            }
        }
        typeBits >>= 1;
    }
    // No memory types matched, return failure
    return false;
}

static void demo_flush_init_cmd(struct demo *demo) {
    VkResult U_ASSERT_ONLY err;

    if (demo->setup_cmd == VK_NULL_HANDLE)
        return;

    err = vkEndCommandBuffer(demo->setup_cmd);
    assert(!err);

    const VkCommandBuffer cmd_bufs[] = {demo->setup_cmd};
    VkFence nullFence = {VK_NULL_HANDLE};
    VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                                .pNext = NULL,
                                .waitSemaphoreCount = 0,
                                .pWaitSemaphores = NULL,
                                .pWaitDstStageMask = NULL,
                                .commandBufferCount = 1,
                                .pCommandBuffers = cmd_bufs,
                                .signalSemaphoreCount = 0,
                                .pSignalSemaphores = NULL};

    err = vkQueueSubmit(demo->queue, 1, &submit_info, nullFence);
    assert(!err);

    err = vkQueueWaitIdle(demo->queue);
    assert(!err);

    vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, cmd_bufs);
    demo->setup_cmd = VK_NULL_HANDLE;
}

static void demo_set_image_layout(struct demo *demo, VkImage image,
                                  VkImageAspectFlags aspectMask,
                                  VkImageLayout old_image_layout,
                                  VkImageLayout new_image_layout) {
    VkResult U_ASSERT_ONLY err;

    if (demo->setup_cmd == VK_NULL_HANDLE) {
        const VkCommandBufferAllocateInfo cmd = {
            .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
            .pNext = NULL,
            .commandPool = demo->cmd_pool,
            .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
            .commandBufferCount = 1,
        };

        err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->setup_cmd);
        assert(!err);

        VkCommandBufferInheritanceInfo cmd_buf_hinfo = {
            .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO,
            .pNext = NULL,
            .renderPass = VK_NULL_HANDLE,
            .subpass = 0,
            .framebuffer = VK_NULL_HANDLE,
            .occlusionQueryEnable = VK_FALSE,
            .queryFlags = 0,
            .pipelineStatistics = 0,
        };
        VkCommandBufferBeginInfo cmd_buf_info = {
            .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
            .pNext = NULL,
            .flags = 0,
            .pInheritanceInfo = &cmd_buf_hinfo,
        };
        err = vkBeginCommandBuffer(demo->setup_cmd, &cmd_buf_info);
        assert(!err);
    }

    VkImageMemoryBarrier image_memory_barrier = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .pNext = NULL,
        .srcAccessMask = 0,
        .dstAccessMask = 0,
        .oldLayout = old_image_layout,
        .newLayout = new_image_layout,
        .image = image,
        .subresourceRange = {aspectMask, 0, 1, 0, 1}};

    if (new_image_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
        /* Make sure anything that was copying from this image has completed */
        image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
    }

    if (new_image_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
        image_memory_barrier.dstAccessMask =
            VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    }

    if (new_image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
        image_memory_barrier.dstAccessMask =
            VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
    }

    if (new_image_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
        /* Make sure any Copy or CPU writes to image are flushed */
        image_memory_barrier.dstAccessMask =
            VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
    }

    VkImageMemoryBarrier *pmemory_barrier = &image_memory_barrier;

    VkPipelineStageFlags src_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    VkPipelineStageFlags dest_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;

    vkCmdPipelineBarrier(demo->setup_cmd, src_stages, dest_stages, 0, 0, NULL,
                         0, NULL, 1, pmemory_barrier);
}

static void demo_draw_build_cmd(struct demo *demo) {
    const VkCommandBufferInheritanceInfo cmd_buf_hinfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO,
        .pNext = NULL,
        .renderPass = VK_NULL_HANDLE,
        .subpass = 0,
        .framebuffer = VK_NULL_HANDLE,
        .occlusionQueryEnable = VK_FALSE,
        .queryFlags = 0,
        .pipelineStatistics = 0,
    };
    const VkCommandBufferBeginInfo cmd_buf_info = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .pNext = NULL,
        .flags = 0,
        .pInheritanceInfo = &cmd_buf_hinfo,
    };
    const VkClearValue clear_values[2] = {
            [0] = {.color.float32 = {0.2f, 0.2f, 0.2f, 0.2f}},
            [1] = {.depthStencil = {demo->depthStencil, 0}},
    };
    const VkRenderPassBeginInfo rp_begin = {
        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
        .pNext = NULL,
        .renderPass = demo->render_pass,
        .framebuffer = demo->framebuffers[demo->current_buffer],
        .renderArea.offset.x = 0,
        .renderArea.offset.y = 0,
        .renderArea.extent.width = demo->width,
        .renderArea.extent.height = demo->height,
        .clearValueCount = 2,
        .pClearValues = clear_values,
    };
    VkResult U_ASSERT_ONLY err;

    err = vkBeginCommandBuffer(demo->draw_cmd, &cmd_buf_info);
    assert(!err);

    vkCmdBeginRenderPass(demo->draw_cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdBindPipeline(demo->draw_cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
                      demo->pipeline);
    vkCmdBindDescriptorSets(demo->draw_cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
                            demo->pipeline_layout, 0, 1, &demo->desc_set, 0,
                            NULL);

    VkViewport viewport;
    memset(&viewport, 0, sizeof(viewport));
    viewport.height = (float)demo->height;
    viewport.width = (float)demo->width;
    viewport.minDepth = (float)0.0f;
    viewport.maxDepth = (float)1.0f;
    vkCmdSetViewport(demo->draw_cmd, 0, 1, &viewport);

    VkRect2D scissor;
    memset(&scissor, 0, sizeof(scissor));
    scissor.extent.width = demo->width;
    scissor.extent.height = demo->height;
    scissor.offset.x = 0;
    scissor.offset.y = 0;
    vkCmdSetScissor(demo->draw_cmd, 0, 1, &scissor);

    VkDeviceSize offsets[1] = {0};
    vkCmdBindVertexBuffers(demo->draw_cmd, VERTEX_BUFFER_BIND_ID, 1,
                           &demo->vertices.buf, offsets);

    vkCmdDraw(demo->draw_cmd, 3, 1, 0, 0);
    vkCmdEndRenderPass(demo->draw_cmd);

    VkImageMemoryBarrier prePresentBarrier = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .pNext = NULL,
        .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
        .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
        .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
        .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};

    prePresentBarrier.image = demo->buffers[demo->current_buffer].image;
    VkImageMemoryBarrier *pmemory_barrier = &prePresentBarrier;
    vkCmdPipelineBarrier(demo->draw_cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
                         VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0,
                         NULL, 1, pmemory_barrier);

    err = vkEndCommandBuffer(demo->draw_cmd);
    assert(!err);
}

static void demo_draw(struct demo *demo) {
    VkResult U_ASSERT_ONLY err;
    VkSemaphore presentCompleteSemaphore;
    VkSemaphoreCreateInfo presentCompleteSemaphoreCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = NULL,
        .flags = 0,
    };

    err = vkCreateSemaphore(demo->device, &presentCompleteSemaphoreCreateInfo,
                            NULL, &presentCompleteSemaphore);
    assert(!err);

    // Get the index of the next available swapchain image:
    err = demo->fpAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX,
                                      presentCompleteSemaphore,
                                      (VkFence)0, // TODO: Show use of fence
                                      &demo->current_buffer);
    if (err == VK_ERROR_OUT_OF_DATE_KHR) {
        // demo->swapchain is out of date (e.g. the window was resized) and
        // must be recreated:
        demo_resize(demo);
        demo_draw(demo);
        vkDestroySemaphore(demo->device, presentCompleteSemaphore, NULL);
        return;
    } else if (err == VK_SUBOPTIMAL_KHR) {
        // demo->swapchain is not as optimal as it could be, but the platform's
        // presentation engine will still present the image correctly.
    } else {
        assert(!err);
    }

    // Assume the command buffer has been run on current_buffer before so
    // we need to set the image layout back to COLOR_ATTACHMENT_OPTIMAL
    demo_set_image_layout(demo, demo->buffers[demo->current_buffer].image,
                          VK_IMAGE_ASPECT_COLOR_BIT,
                          VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
                          VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    demo_flush_init_cmd(demo);

    // Wait for the present complete semaphore to be signaled to ensure
    // that the image won't be rendered to until the presentation
    // engine has fully released ownership to the application, and it is
    // okay to render to the image.

    // FIXME/TODO: DEAL WITH VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
    demo_draw_build_cmd(demo);
    VkFence nullFence = VK_NULL_HANDLE;
    VkPipelineStageFlags pipe_stage_flags =
        VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                                .pNext = NULL,
                                .waitSemaphoreCount = 1,
                                .pWaitSemaphores = &presentCompleteSemaphore,
                                .pWaitDstStageMask = &pipe_stage_flags,
                                .commandBufferCount = 1,
                                .pCommandBuffers = &demo->draw_cmd,
                                .signalSemaphoreCount = 0,
                                .pSignalSemaphores = NULL};

    err = vkQueueSubmit(demo->queue, 1, &submit_info, nullFence);
    assert(!err);

    VkPresentInfoKHR present = {
        .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
        .pNext = NULL,
        .swapchainCount = 1,
        .pSwapchains = &demo->swapchain,
        .pImageIndices = &demo->current_buffer,
    };

    // TBD/TODO: SHOULD THE "present" PARAMETER BE "const" IN THE HEADER?
    err = demo->fpQueuePresentKHR(demo->queue, &present);
    if (err == VK_ERROR_OUT_OF_DATE_KHR) {
        // demo->swapchain is out of date (e.g. the window was resized) and
        // must be recreated:
        demo_resize(demo);
    } else if (err == VK_SUBOPTIMAL_KHR) {
        // demo->swapchain is not as optimal as it could be, but the platform's
        // presentation engine will still present the image correctly.
    } else {
        assert(!err);
    }

    err = vkQueueWaitIdle(demo->queue);
    assert(err == VK_SUCCESS);

    vkDestroySemaphore(demo->device, presentCompleteSemaphore, NULL);
}

static void demo_prepare_buffers(struct demo *demo) {
    VkResult U_ASSERT_ONLY err;
    VkSwapchainKHR oldSwapchain = demo->swapchain;

    // Check the surface capabilities and formats
    VkSurfaceCapabilitiesKHR surfCapabilities;
    err = demo->fpGetPhysicalDeviceSurfaceCapabilitiesKHR(
        demo->gpu, demo->surface, &surfCapabilities);
    assert(!err);

    uint32_t presentModeCount;
    err = demo->fpGetPhysicalDeviceSurfacePresentModesKHR(
        demo->gpu, demo->surface, &presentModeCount, NULL);
    assert(!err);
    VkPresentModeKHR *presentModes =
        (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR));
    assert(presentModes);
    err = demo->fpGetPhysicalDeviceSurfacePresentModesKHR(
        demo->gpu, demo->surface, &presentModeCount, presentModes);
    assert(!err);

    VkExtent2D swapchainExtent;
    // width and height are either both -1, or both not -1.
    if (surfCapabilities.currentExtent.width == (uint32_t)-1) {
        // If the surface size is undefined, the size is set to
        // the size of the images requested.
        swapchainExtent.width = demo->width;
        swapchainExtent.height = demo->height;
    } else {
        // If the surface size is defined, the swap chain size must match
        swapchainExtent = surfCapabilities.currentExtent;
        demo->width = surfCapabilities.currentExtent.width;
        demo->height = surfCapabilities.currentExtent.height;
    }

    VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;

    // Determine the number of VkImage's to use in the swap chain (we desire to
    // own only 1 image at a time, besides the images being displayed and
    // queued for display):
    uint32_t desiredNumberOfSwapchainImages =
        surfCapabilities.minImageCount + 1;
    if ((surfCapabilities.maxImageCount > 0) &&
        (desiredNumberOfSwapchainImages > surfCapabilities.maxImageCount)) {
        // Application must settle for fewer images than desired:
        desiredNumberOfSwapchainImages = surfCapabilities.maxImageCount;
    }

    VkSurfaceTransformFlagsKHR preTransform;
    if (surfCapabilities.supportedTransforms &
        VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
        preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    } else {
        preTransform = surfCapabilities.currentTransform;
    }

    const VkSwapchainCreateInfoKHR swapchain = {
        .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
        .pNext = NULL,
        .surface = demo->surface,
        .minImageCount = desiredNumberOfSwapchainImages,
        .imageFormat = demo->format,
        .imageColorSpace = demo->color_space,
        .imageExtent =
            {
             .width = swapchainExtent.width, .height = swapchainExtent.height,
            },
        .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
        .preTransform = preTransform,
        .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
        .imageArrayLayers = 1,
        .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
        .queueFamilyIndexCount = 0,
        .pQueueFamilyIndices = NULL,
        .presentMode = swapchainPresentMode,
        .oldSwapchain = oldSwapchain,
        .clipped = true,
    };
    uint32_t i;

    err = demo->fpCreateSwapchainKHR(demo->device, &swapchain, NULL,
                                     &demo->swapchain);
    assert(!err);

    // If we just re-created an existing swapchain, we should destroy the old
    // swapchain at this point.
    // Note: destroying the swapchain also cleans up all its associated
    // presentable images once the platform is done with them.
    if (oldSwapchain != VK_NULL_HANDLE) {
        demo->fpDestroySwapchainKHR(demo->device, oldSwapchain, NULL);
    }

    err = demo->fpGetSwapchainImagesKHR(demo->device, demo->swapchain,
                                        &demo->swapchainImageCount, NULL);
    assert(!err);

    VkImage *swapchainImages =
        (VkImage *)malloc(demo->swapchainImageCount * sizeof(VkImage));
    assert(swapchainImages);
    err = demo->fpGetSwapchainImagesKHR(demo->device, demo->swapchain,
                                        &demo->swapchainImageCount,
                                        swapchainImages);
    assert(!err);

    demo->buffers = (SwapchainBuffers *)malloc(sizeof(SwapchainBuffers) *
                                               demo->swapchainImageCount);
    assert(demo->buffers);

    for (i = 0; i < demo->swapchainImageCount; i++) {
        VkImageViewCreateInfo color_attachment_view = {
            .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
            .pNext = NULL,
            .format = demo->format,
            .components =
                {
                 .r = VK_COMPONENT_SWIZZLE_R,
                 .g = VK_COMPONENT_SWIZZLE_G,
                 .b = VK_COMPONENT_SWIZZLE_B,
                 .a = VK_COMPONENT_SWIZZLE_A,
                },
            .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                                 .baseMipLevel = 0,
                                 .levelCount = 1,
                                 .baseArrayLayer = 0,
                                 .layerCount = 1},
            .viewType = VK_IMAGE_VIEW_TYPE_2D,
            .flags = 0,
        };

        demo->buffers**.image = swapchainImages**;

        // Render loop will expect image to have been used before and in
        // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
        // layout and will change to COLOR_ATTACHMENT_OPTIMAL, so init the image
        // to that state
        demo_set_image_layout(
            demo, demo->buffers**.image, VK_IMAGE_ASPECT_COLOR_BIT,
            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);

        color_attachment_view.image = demo->buffers**.image;

        err = vkCreateImageView(demo->device, &color_attachment_view, NULL,
                                &demo->buffers**.view);
        assert(!err);
    }

    demo->current_buffer = 0;

    if (NULL != presentModes) {
        free(presentModes);
    }
}

static void demo_prepare_depth(struct demo *demo) {
    const VkFormat depth_format = VK_FORMAT_D16_UNORM;
    const VkImageCreateInfo image = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pNext = NULL,
        .imageType = VK_IMAGE_TYPE_2D,
        .format = depth_format,
        .extent = {demo->width, demo->height, 1},
        .mipLevels = 1,
        .arrayLayers = 1,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .tiling = VK_IMAGE_TILING_OPTIMAL,
        .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
        .flags = 0,
    };
    VkMemoryAllocateInfo mem_alloc = {
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = NULL,
        .allocationSize = 0,
        .memoryTypeIndex = 0,
    };
    VkImageViewCreateInfo view = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .pNext = NULL,
        .image = VK_NULL_HANDLE,
        .format = depth_format,
        .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
                             .baseMipLevel = 0,
                             .levelCount = 1,
                             .baseArrayLayer = 0,
                             .layerCount = 1},
        .flags = 0,
        .viewType = VK_IMAGE_VIEW_TYPE_2D,
    };

    VkMemoryRequirements mem_reqs;
    VkResult U_ASSERT_ONLY err;
    bool U_ASSERT_ONLY pass;

    demo->depth.format = depth_format;

    /* create image */
    err = vkCreateImage(demo->device, &image, NULL, &demo->depth.image);
    assert(!err);

    /* get memory requirements for this object */
    vkGetImageMemoryRequirements(demo->device, demo->depth.image, &mem_reqs);

    /* select memory size and type */
    mem_alloc.allocationSize = mem_reqs.size;
    pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits,
                                       0, /* No requirements */
                                       &mem_alloc.memoryTypeIndex);
    assert(pass);

    /* allocate memory */
    err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->depth.mem);
    assert(!err);

    /* bind memory */
    err =
        vkBindImageMemory(demo->device, demo->depth.image, demo->depth.mem, 0);
    assert(!err);

    demo_set_image_layout(demo, demo->depth.image, VK_IMAGE_ASPECT_DEPTH_BIT,
                          VK_IMAGE_LAYOUT_UNDEFINED,
                          VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);

    /* create image view */
    view.image = demo->depth.image;
    err = vkCreateImageView(demo->device, &view, NULL, &demo->depth.view);
    assert(!err);
}

static void
demo_prepare_texture_image(struct demo *demo, const uint32_t *tex_colors,
                           struct texture_object *tex_obj, VkImageTiling tiling,
                           VkImageUsageFlags usage, VkFlags required_props) {
    const VkFormat tex_format = VK_FORMAT_B8G8R8A8_UNORM;
    const int32_t tex_width = 2;
    const int32_t tex_height = 2;
    VkResult U_ASSERT_ONLY err;
    bool U_ASSERT_ONLY pass;

    tex_obj->tex_width = tex_width;
    tex_obj->tex_height = tex_height;

    const VkImageCreateInfo image_create_info = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pNext = NULL,
        .imageType = VK_IMAGE_TYPE_2D,
        .format = tex_format,
        .extent = {tex_width, tex_height, 1},
        .mipLevels = 1,
        .arrayLayers = 1,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .tiling = tiling,
        .usage = usage,
        .flags = 0,
    };
    VkMemoryAllocateInfo mem_alloc = {
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = NULL,
        .allocationSize = 0,
        .memoryTypeIndex = 0,
    };

    VkMemoryRequirements mem_reqs;

    err =
        vkCreateImage(demo->device, &image_create_info, NULL, &tex_obj->image);
    assert(!err);

    vkGetImageMemoryRequirements(demo->device, tex_obj->image, &mem_reqs);

    mem_alloc.allocationSize = mem_reqs.size;
    pass =
        memory_type_from_properties(demo, mem_reqs.memoryTypeBits,
                                    required_props, &mem_alloc.memoryTypeIndex);
    assert(pass);

    /* allocate memory */
    err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &tex_obj->mem);
    assert(!err);

    /* bind memory */
    err = vkBindImageMemory(demo->device, tex_obj->image, tex_obj->mem, 0);
    assert(!err);

    if (required_props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
        const VkImageSubresource subres = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .mipLevel = 0,
            .arrayLayer = 0,
        };
        VkSubresourceLayout layout;
        void *data;
        int32_t x, y;

        vkGetImageSubresourceLayout(demo->device, tex_obj->image, &subres,
                                    &layout);

        err = vkMapMemory(demo->device, tex_obj->mem, 0,
                          mem_alloc.allocationSize, 0, &data);
        assert(!err);

        for (y = 0; y < tex_height; y++) {
            uint32_t *row = (uint32_t *)((char *)data + layout.rowPitch * y);
            for (x = 0; x < tex_width; x++)
                row[x] = tex_colors[(x & 1) ^ (y & 1)];
        }

        vkUnmapMemory(demo->device, tex_obj->mem);
    }

    tex_obj->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    demo_set_image_layout(demo, tex_obj->image, VK_IMAGE_ASPECT_COLOR_BIT,
                          VK_IMAGE_LAYOUT_UNDEFINED, tex_obj->imageLayout);
    /* setting the image layout does not reference the actual memory so no need
     * to add a mem ref */
}

static void demo_destroy_texture_image(struct demo *demo,
                                       struct texture_object *tex_obj) {
    /* clean up staging resources */
    vkDestroyImage(demo->device, tex_obj->image, NULL);
    vkFreeMemory(demo->device, tex_obj->mem, NULL);
}

static void demo_prepare_textures(struct demo *demo) {
    const VkFormat tex_format = VK_FORMAT_B8G8R8A8_UNORM;
    VkFormatProperties props;
    const uint32_t tex_colors[DEMO_TEXTURE_COUNT][2] = {
        {0xffff0000, 0xff00ff00},
    };
    uint32_t i;
    VkResult U_ASSERT_ONLY err;

    vkGetPhysicalDeviceFormatProperties(demo->gpu, tex_format, &props);

    for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
        if ((props.linearTilingFeatures &
             VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) &&
            !demo->use_staging_buffer) {
            /* Device can texture using linear textures */
            demo_prepare_texture_image(demo, tex_colors**, &demo->textures**,
                                       VK_IMAGE_TILING_LINEAR,
                                       VK_IMAGE_USAGE_SAMPLED_BIT,
                                       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
        } else if (props.optimalTilingFeatures &
                   VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
            /* Must use staging buffer to copy linear texture to optimized */
            struct texture_object staging_texture;

            memset(&staging_texture, 0, sizeof(staging_texture));
            demo_prepare_texture_image(demo, tex_colors**, &staging_texture,
                                       VK_IMAGE_TILING_LINEAR,
                                       VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
                                       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);

            demo_prepare_texture_image(
                demo, tex_colors**, &demo->textures**,
                VK_IMAGE_TILING_OPTIMAL,
                (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT),
                VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

            demo_set_image_layout(demo, staging_texture.image,
                                  VK_IMAGE_ASPECT_COLOR_BIT,
                                  staging_texture.imageLayout,
                                  VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);

            demo_set_image_layout(demo, demo->textures**.image,
                                  VK_IMAGE_ASPECT_COLOR_BIT,
                                  demo->textures**.imageLayout,
                                  VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);

            VkImageCopy copy_region = {
                .srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
                .srcOffset = {0, 0, 0},
                .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
                .dstOffset = {0, 0, 0},
                .extent = {staging_texture.tex_width,
                           staging_texture.tex_height, 1},
            };
            vkCmdCopyImage(
                demo->setup_cmd, staging_texture.image,
                VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, demo->textures**.image,
                VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);

            demo_set_image_layout(demo, demo->textures**.image,
                                  VK_IMAGE_ASPECT_COLOR_BIT,
                                  VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                  demo->textures**.imageLayout);

            demo_flush_init_cmd(demo);

            demo_destroy_texture_image(demo, &staging_texture);
        } else {
            /* Can't support VK_FORMAT_B8G8R8A8_UNORM !? */
            assert(!"No support for B8G8R8A8_UNORM as texture image format");
        }

        const VkSamplerCreateInfo sampler = {
            .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
            .pNext = NULL,
            .magFilter = VK_FILTER_NEAREST,
            .minFilter = VK_FILTER_NEAREST,
            .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
            .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
            .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
            .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
            .mipLodBias = 0.0f,
            .anisotropyEnable = VK_FALSE,
            .maxAnisotropy = 1,
            .compareOp = VK_COMPARE_OP_NEVER,
            .minLod = 0.0f,
            .maxLod = 0.0f,
            .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
            .unnormalizedCoordinates = VK_FALSE,
        };
        VkImageViewCreateInfo view = {
            .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
            .pNext = NULL,
            .image = VK_NULL_HANDLE,
            .viewType = VK_IMAGE_VIEW_TYPE_2D,
            .format = tex_format,
            .components =
                {
                 VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G,
                 VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A,
                },
            .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1},
            .flags = 0,
        };

        /* create sampler */
        err = vkCreateSampler(demo->device, &sampler, NULL,
                              &demo->textures**.sampler);
        assert(!err);

        /* create image view */
        view.image = demo->textures**.image;
        err = vkCreateImageView(demo->device, &view, NULL,
                                &demo->textures**.view);
        assert(!err);
    }
}

static void demo_prepare_vertices(struct demo *demo) {
    // clang-format off
    const float vb[3][5] = {
        /*      position             texcoord */
        { -1.0f, -1.0f,  0.25f,     0.0f, 0.0f },
        {  1.0f, -1.0f,  0.25f,     1.0f, 0.0f },
        {  0.0f,  1.0f,  1.0f,      0.5f, 1.0f },
    };
    // clang-format on
    const VkBufferCreateInfo buf_info = {
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .pNext = NULL,
        .size = sizeof(vb),
        .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
        .flags = 0,
    };
    VkMemoryAllocateInfo mem_alloc = {
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = NULL,
        .allocationSize = 0,
        .memoryTypeIndex = 0,
    };
    VkMemoryRequirements mem_reqs;
    VkResult U_ASSERT_ONLY err;
    bool U_ASSERT_ONLY pass;
    void *data;

    memset(&demo->vertices, 0, sizeof(demo->vertices));

    err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->vertices.buf);
    assert(!err);

    vkGetBufferMemoryRequirements(demo->device, demo->vertices.buf, &mem_reqs);
    assert(!err);

    mem_alloc.allocationSize = mem_reqs.size;
    pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits,
                                       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
                                       &mem_alloc.memoryTypeIndex);
    assert(pass);

    err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->vertices.mem);
    assert(!err);

    err = vkMapMemory(demo->device, demo->vertices.mem, 0,
                      mem_alloc.allocationSize, 0, &data);
    assert(!err);

    memcpy(data, vb, sizeof(vb));

    vkUnmapMemory(demo->device, demo->vertices.mem);

    err = vkBindBufferMemory(demo->device, demo->vertices.buf,
                             demo->vertices.mem, 0);
    assert(!err);

    demo->vertices.vi.sType =
        VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    demo->vertices.vi.pNext = NULL;
    demo->vertices.vi.vertexBindingDescriptionCount = 1;
    demo->vertices.vi.pVertexBindingDescriptions = demo->vertices.vi_bindings;
    demo->vertices.vi.vertexAttributeDescriptionCount = 2;
    demo->vertices.vi.pVertexAttributeDescriptions = demo->vertices.vi_attrs;

    demo->vertices.vi_bindings[0].binding = VERTEX_BUFFER_BIND_ID;
    demo->vertices.vi_bindings[0].stride = sizeof(vb[0]);
    demo->vertices.vi_bindings[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

    demo->vertices.vi_attrs[0].binding = VERTEX_BUFFER_BIND_ID;
    demo->vertices.vi_attrs[0].location = 0;
    demo->vertices.vi_attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
    demo->vertices.vi_attrs[0].offset = 0;

    demo->vertices.vi_attrs[1].binding = VERTEX_BUFFER_BIND_ID;
    demo->vertices.vi_attrs[1].location = 1;
    demo->vertices.vi_attrs[1].format = VK_FORMAT_R32G32_SFLOAT;
    demo->vertices.vi_attrs[1].offset = sizeof(float) * 3;
}

static void demo_prepare_descriptor_layout(struct demo *demo) {
    const VkDescriptorSetLayoutBinding layout_binding = {
        .binding = 0,
        .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
        .descriptorCount = DEMO_TEXTURE_COUNT,
        .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        .pImmutableSamplers = NULL,
    };
    const VkDescriptorSetLayoutCreateInfo descriptor_layout = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .pNext = NULL,
        .bindingCount = 1,
        .pBindings = &layout_binding,
    };
    VkResult U_ASSERT_ONLY err;

    err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL,
                                      &demo->desc_layout);
    assert(!err);

    const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .pNext = NULL,
        .setLayoutCount = 1,
        .pSetLayouts = &demo->desc_layout,
    };

    err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL,
                                 &demo->pipeline_layout);
    assert(!err);
}

static void demo_prepare_render_pass(struct demo *demo) {
    const VkAttachmentDescription attachments[2] = {
            [0] =
                {
                 .format = demo->format,
                 .samples = VK_SAMPLE_COUNT_1_BIT,
                 .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
                 .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
                 .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
                 .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
                 .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
                 .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
                },
            [1] =
                {
                 .format = demo->depth.format,
                 .samples = VK_SAMPLE_COUNT_1_BIT,
                 .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
                 .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
                 .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
                 .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
                 .initialLayout =
                     VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
                 .finalLayout =
                     VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
                },
    };
    const VkAttachmentReference color_reference = {
        .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
    };
    const VkAttachmentReference depth_reference = {
        .attachment = 1,
        .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
    };
    const VkSubpassDescription subpass = {
        .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
        .flags = 0,
        .inputAttachmentCount = 0,
        .pInputAttachments = NULL,
        .colorAttachmentCount = 1,
        .pColorAttachments = &color_reference,
        .pResolveAttachments = NULL,
        .pDepthStencilAttachment = &depth_reference,
        .preserveAttachmentCount = 0,
        .pPreserveAttachments = NULL,
    };
    const VkRenderPassCreateInfo rp_info = {
        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
        .pNext = NULL,
        .attachmentCount = 2,
        .pAttachments = attachments,
        .subpassCount = 1,
        .pSubpasses = &subpass,
        .dependencyCount = 0,
        .pDependencies = NULL,
    };
    VkResult U_ASSERT_ONLY err;

    err = vkCreateRenderPass(demo->device, &rp_info, NULL, &demo->render_pass);
    assert(!err);
}

static VkShaderModule
demo_prepare_shader_module(struct demo *demo, const void *code, size_t size) {
    VkShaderModuleCreateInfo moduleCreateInfo;
    VkShaderModule module;
    VkResult U_ASSERT_ONLY err;

    moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    moduleCreateInfo.pNext = NULL;

    moduleCreateInfo.codeSize = size;
    moduleCreateInfo.pCode = code;
    moduleCreateInfo.flags = 0;
    err = vkCreateShaderModule(demo->device, &moduleCreateInfo, NULL, &module);
    assert(!err);

    return module;
}

char *demo_read_spv(const char *filename, size_t *psize) {
    long int size;
    void *shader_code;
    size_t retVal;

    FILE *fp = fopen(filename, "rb");
    if (!fp)
        return NULL;

    fseek(fp, 0L, SEEK_END);
    size = ftell(fp);

    fseek(fp, 0L, SEEK_SET);

    shader_code = malloc(size);
    retVal = fread(shader_code, size, 1, fp);
    if (!retVal)
        return NULL;

    *psize = size;

    fclose(fp);
    return shader_code;
}

static VkShaderModule demo_prepare_vs(struct demo *demo) {
    void *vertShaderCode;
    size_t size;

    vertShaderCode = demo_read_spv("tri-vert.spv", &size);

    demo->vert_shader_module =
        demo_prepare_shader_module(demo, vertShaderCode, size);

    free(vertShaderCode);

    return demo->vert_shader_module;
}

static VkShaderModule demo_prepare_fs(struct demo *demo) {
    void *fragShaderCode;
    size_t size;

    fragShaderCode = demo_read_spv("tri-frag.spv", &size);

    demo->frag_shader_module =
        demo_prepare_shader_module(demo, fragShaderCode, size);

    free(fragShaderCode);

    return demo->frag_shader_module;
}

static void demo_prepare_pipeline(struct demo *demo) {
    VkGraphicsPipelineCreateInfo pipeline;
    VkPipelineCacheCreateInfo pipelineCache;

    VkPipelineVertexInputStateCreateInfo vi;
    VkPipelineInputAssemblyStateCreateInfo ia;
    VkPipelineRasterizationStateCreateInfo rs;
    VkPipelineColorBlendStateCreateInfo cb;
    VkPipelineDepthStencilStateCreateInfo ds;
    VkPipelineViewportStateCreateInfo vp;
    VkPipelineMultisampleStateCreateInfo ms;
    VkDynamicState dynamicStateEnables[VK_DYNAMIC_STATE_RANGE_SIZE];
    VkPipelineDynamicStateCreateInfo dynamicState;

    VkResult U_ASSERT_ONLY err;

    memset(dynamicStateEnables, 0, sizeof dynamicStateEnables);
    memset(&dynamicState, 0, sizeof dynamicState);
    dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    dynamicState.pDynamicStates = dynamicStateEnables;

    memset(&pipeline, 0, sizeof(pipeline));
    pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipeline.layout = demo->pipeline_layout;

    vi = demo->vertices.vi;

    memset(&ia, 0, sizeof(ia));
    ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;

    memset(&rs, 0, sizeof(rs));
    rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rs.polygonMode = VK_POLYGON_MODE_FILL;
    rs.cullMode = VK_CULL_MODE_BACK_BIT;
    rs.frontFace = VK_FRONT_FACE_CLOCKWISE;
    rs.depthClampEnable = VK_FALSE;
    rs.rasterizerDiscardEnable = VK_FALSE;
    rs.depthBiasEnable = VK_FALSE;

    memset(&cb, 0, sizeof(cb));
    cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    VkPipelineColorBlendAttachmentState att_state[1];
    memset(att_state, 0, sizeof(att_state));
    att_state[0].colorWriteMask = 0xf;
    att_state[0].blendEnable = VK_FALSE;
    cb.attachmentCount = 1;
    cb.pAttachments = att_state;

    memset(&vp, 0, sizeof(vp));
    vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    vp.viewportCount = 1;
    dynamicStateEnables[dynamicState.dynamicStateCount++] =
        VK_DYNAMIC_STATE_VIEWPORT;
    vp.scissorCount = 1;
    dynamicStateEnables[dynamicState.dynamicStateCount++] =
        VK_DYNAMIC_STATE_SCISSOR;

    memset(&ds, 0, sizeof(ds));
    ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
    ds.depthTestEnable = VK_TRUE;
    ds.depthWriteEnable = VK_TRUE;
    ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
    ds.depthBoundsTestEnable = VK_FALSE;
    ds.back.failOp = VK_STENCIL_OP_KEEP;
    ds.back.passOp = VK_STENCIL_OP_KEEP;
    ds.back.compareOp = VK_COMPARE_OP_ALWAYS;
    ds.stencilTestEnable = VK_FALSE;
    ds.front = ds.back;

    memset(&ms, 0, sizeof(ms));
    ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    ms.pSampleMask = NULL;
    ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

    // Two stages: vs and fs
    pipeline.stageCount = 2;
    VkPipelineShaderStageCreateInfo shaderStages[2];
    memset(&shaderStages, 0, 2 * sizeof(VkPipelineShaderStageCreateInfo));

    shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
    shaderStages[0].module = demo_prepare_vs(demo);
    shaderStages[0].pName = "main";

    shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    shaderStages[1].module = demo_prepare_fs(demo);
    shaderStages[1].pName = "main";

    pipeline.pVertexInputState = &vi;
    pipeline.pInputAssemblyState = &ia;
    pipeline.pRasterizationState = &rs;
    pipeline.pColorBlendState = &cb;
    pipeline.pMultisampleState = &ms;
    pipeline.pViewportState = &vp;
    pipeline.pDepthStencilState = &ds;
    pipeline.pStages = shaderStages;
    pipeline.renderPass = demo->render_pass;
    pipeline.pDynamicState = &dynamicState;

    memset(&pipelineCache, 0, sizeof(pipelineCache));
    pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;

    err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL,
                                &demo->pipelineCache);
    assert(!err);
    err = vkCreateGraphicsPipelines(demo->device, demo->pipelineCache, 1,
                                    &pipeline, NULL, &demo->pipeline);
    assert(!err);

    vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL);

    vkDestroyShaderModule(demo->device, demo->frag_shader_module, NULL);
    vkDestroyShaderModule(demo->device, demo->vert_shader_module, NULL);
}

static void demo_prepare_descriptor_pool(struct demo *demo) {
    const VkDescriptorPoolSize type_count = {
        .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
        .descriptorCount = DEMO_TEXTURE_COUNT,
    };
    const VkDescriptorPoolCreateInfo descriptor_pool = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
        .pNext = NULL,
        .maxSets = 1,
        .poolSizeCount = 1,
        .pPoolSizes = &type_count,
    };
    VkResult U_ASSERT_ONLY err;

    err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL,
                                 &demo->desc_pool);
    assert(!err);
}

static void demo_prepare_descriptor_set(struct demo *demo) {
    VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT];
    VkWriteDescriptorSet write;
    VkResult U_ASSERT_ONLY err;
    uint32_t i;

    VkDescriptorSetAllocateInfo alloc_info = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .pNext = NULL,
        .descriptorPool = demo->desc_pool,
        .descriptorSetCount = 1,
        .pSetLayouts = &demo->desc_layout};
    err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->desc_set);
    assert(!err);

    memset(&tex_descs, 0, sizeof(tex_descs));
    for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
        tex_descs**.sampler = demo->textures**.sampler;
        tex_descs**.imageView = demo->textures**.view;
        tex_descs**.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
    }

    memset(&write, 0, sizeof(write));
    write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    write.dstSet = demo->desc_set;
    write.descriptorCount = DEMO_TEXTURE_COUNT;
    write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    write.pImageInfo = tex_descs;

    vkUpdateDescriptorSets(demo->device, 1, &write, 0, NULL);
}

static void demo_prepare_framebuffers(struct demo *demo) {
    VkImageView attachments[2];
    attachments[1] = demo->depth.view;

    const VkFramebufferCreateInfo fb_info = {
        .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
        .pNext = NULL,
        .renderPass = demo->render_pass,
        .attachmentCount = 2,
        .pAttachments = attachments,
        .width = demo->width,
        .height = demo->height,
        .layers = 1,
    };
    VkResult U_ASSERT_ONLY err;
    uint32_t i;

    demo->framebuffers = (VkFramebuffer *)malloc(demo->swapchainImageCount *
                                                 sizeof(VkFramebuffer));
    assert(demo->framebuffers);

    for (i = 0; i < demo->swapchainImageCount; i++) {
        attachments[0] = demo->buffers**.view;
        err = vkCreateFramebuffer(demo->device, &fb_info, NULL,
                                  &demo->framebuffers**);
        assert(!err);
    }
}

static void demo_prepare(struct demo *demo) {
    VkResult U_ASSERT_ONLY err;

    const VkCommandPoolCreateInfo cmd_pool_info = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
        .pNext = NULL,
        .queueFamilyIndex = demo->graphics_queue_node_index,
        .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
    };
    err = vkCreateCommandPool(demo->device, &cmd_pool_info, NULL,
                              &demo->cmd_pool);
    assert(!err);

    const VkCommandBufferAllocateInfo cmd = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .pNext = NULL,
        .commandPool = demo->cmd_pool,
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = 1,
    };
    err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->draw_cmd);
    assert(!err);

    demo_prepare_buffers(demo);
    demo_prepare_depth(demo);
    demo_prepare_textures(demo);
    demo_prepare_vertices(demo);
    demo_prepare_descriptor_layout(demo);
    demo_prepare_render_pass(demo);
    demo_prepare_pipeline(demo);

    demo_prepare_descriptor_pool(demo);
    demo_prepare_descriptor_set(demo);

    demo_prepare_framebuffers(demo);

    demo->prepared = true;
}

#ifdef _WIN32
static void demo_run(struct demo *demo) {
    if (!demo->prepared)
        return;
    demo_draw(demo);

    if (demo->depthStencil > 0.99f)
        demo->depthIncrement = -0.001f;
    if (demo->depthStencil < 0.8f)
        demo->depthIncrement = 0.001f;

    demo->depthStencil += demo->depthIncrement;
}

// On MS-Windows, make this a global, so it's available to WndProc()
struct demo demo;

// MS-Windows event handling function:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    char tmp_str[] = APP_LONG_NAME;

    switch (uMsg) {
    case WM_CREATE:
        return 0;
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        if (demo.prepared) {
            demo_run(&demo);
            break;
        }
    case WM_SIZE:
        demo.width = lParam & 0xffff;
        demo.height = lParam & 0xffff0000 >> 16;
        demo_resize(&demo);
        break;
    default:
        break;
    }
    return (DefWindowProc(hWnd, uMsg, wParam, lParam));
}

static void demo_create_window(struct demo *demo) {
    WNDCLASSEX win_class;

    // Initialize the window class structure:
    win_class.cbSize = sizeof(WNDCLASSEX);
    win_class.style = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc = WndProc;
    win_class.cbClsExtra = 0;
    win_class.cbWndExtra = 0;
    win_class.hInstance = demo->connection; // hInstance
    win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    win_class.lpszMenuName = NULL;
    win_class.lpszClassName = demo->name;
    win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
    // Register window class:
    if (!RegisterClassEx(&win_class)) {
        // It didn't work, so try to give a useful error:
        printf("Unexpected error trying to start the application!
");
        fflush(stdout);
        exit(1);
    }
    // Create window with the registered class:
    RECT wr = {0, 0, demo->width, demo->height};
    AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
    demo->window = CreateWindowEx(0,
                                  demo->name,           // class name
                                  demo->name,           // app name
                                  WS_OVERLAPPEDWINDOW | // window style
                                      WS_VISIBLE | WS_SYSMENU,
                                  100, 100,           // x/y coords
                                  wr.right - wr.left, // width
                                  wr.bottom - wr.top, // height
                                  NULL,               // handle to parent
                                  NULL,               // handle to menu
                                  demo->connection,   // hInstance
                                  NULL);              // no extra parameters
    if (!demo->window) {
        // It didn't work, so try to give a useful error:
        printf("Cannot create a window in which to draw!
");
        fflush(stdout);
        exit(1);
    }
}
#else  // _WIN32

static void demo_handle_event(struct demo *demo,
                              const xcb_generic_event_t *event) {
    switch (event->response_type & 0x7f) {
    case XCB_EXPOSE:
        demo_draw(demo);
        break;
    case XCB_CLIENT_MESSAGE:
        if ((*(xcb_client_message_event_t *)event).data.data32[0] ==
            (*demo->atom_wm_delete_window).atom) {
            demo->quit = true;
        }
        break;
    case XCB_KEY_RELEASE: {
        const xcb_key_release_event_t *key =
            (const xcb_key_release_event_t *)event;

        if (key->detail == 0x9)
            demo->quit = true;
    } break;
    case XCB_DESTROY_NOTIFY:
        demo->quit = true;
        break;
    case XCB_CONFIGURE_NOTIFY: {
        const xcb_configure_notify_event_t *cfg =
            (const xcb_configure_notify_event_t *)event;
        if ((demo->width != cfg->width) || (demo->height != cfg->height)) {
            demo->width = cfg->width;
            demo->height = cfg->height;
            demo_resize(demo);
        }
    } break;
    default:
        break;
    }
}

static void demo_run(struct demo *demo) {
    xcb_flush(demo->connection);

    while (!demo->quit) {
        xcb_generic_event_t *event;

        event = xcb_poll_for_event(demo->connection);
        if (event) {
            demo_handle_event(demo, event);
            free(event);
        }

        demo_draw(demo);

        if (demo->depthStencil > 0.99f)
            demo->depthIncrement = -0.001f;
        if (demo->depthStencil < 0.8f)
            demo->depthIncrement = 0.001f;

        demo->depthStencil += demo->depthIncrement;

        // Wait for work to finish before updating MVP.
        vkDeviceWaitIdle(demo->device);
    }
}

static void demo_create_window(struct demo *demo) {
    uint32_t value_mask, value_list[32];

    demo->window = xcb_generate_id(demo->connection);

    value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    value_list[0] = demo->screen->black_pixel;
    value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE |
                    XCB_EVENT_MASK_STRUCTURE_NOTIFY;

    xcb_create_window(demo->connection, XCB_COPY_FROM_PARENT, demo->window,
                      demo->screen->root, 0, 0, demo->width, demo->height, 0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, demo->screen->root_visual,
                      value_mask, value_list);

    /* Magic code that will send notification when window is destroyed */
    xcb_intern_atom_cookie_t cookie =
        xcb_intern_atom(demo->connection, 1, 12, "WM_PROTOCOLS");
    xcb_intern_atom_reply_t *reply =
        xcb_intern_atom_reply(demo->connection, cookie, 0);

    xcb_intern_atom_cookie_t cookie2 =
        xcb_intern_atom(demo->connection, 0, 16, "WM_DELETE_WINDOW");
    demo->atom_wm_delete_window =
        xcb_intern_atom_reply(demo->connection, cookie2, 0);

    xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, demo->window,
                        (*reply).atom, 4, 32, 1,
                        &(*demo->atom_wm_delete_window).atom);
    free(reply);

    xcb_map_window(demo->connection, demo->window);
}
#endif // _WIN32

/*
 * Return 1 (true) if all layer names specified in check_names
 * can be found in given layer properties.
 */
static VkBool32 demo_check_layers(uint32_t check_count, char **check_names,
                                  uint32_t layer_count,
                                  VkLayerProperties *layers) {
    for (uint32_t i = 0; i < check_count; i++) {
        VkBool32 found = 0;
        for (uint32_t j = 0; j < layer_count; j++) {
            if (!strcmp(check_names**, layers[j].layerName)) {
                found = 1;
                break;
            }
        }
        if (!found) {
            fprintf(stderr, "Cannot find layer: %s
", check_names**);
            return 0;
        }
    }
    return 1;
}

VKAPI_ATTR void *VKAPI_CALL myrealloc(void *pUserData, void *pOriginal,
                                      size_t size, size_t alignment,
                                      VkSystemAllocationScope allocationScope) {
    return realloc(pOriginal, size);
}

VKAPI_ATTR void *VKAPI_CALL myalloc(void *pUserData, size_t size,
                                    size_t alignment,
                                    VkSystemAllocationScope allocationScope) {
#ifdef _MSC_VER
    return _aligned_malloc(size, alignment);
#else
    return aligned_alloc(alignment, size);
#endif
}

VKAPI_ATTR void VKAPI_CALL myfree(void *pUserData, void *pMemory) {
#ifdef _MSC_VER
    _aligned_free(pMemory);
#else
    free(pMemory);
#endif
}

static void demo_init_vk(struct demo *demo) {
    VkResult err;
    uint32_t instance_extension_count = 0;
    uint32_t instance_layer_count = 0;
    uint32_t device_validation_layer_count = 0;
    demo->enabled_extension_count = 0;
    demo->enabled_layer_count = 0;

    char *instance_validation_layers[] = {
        "VK_LAYER_LUNARG_mem_tracker",
        "VK_LAYER_GOOGLE_unique_objects",
    };

    demo->device_validation_layers[0] = "VK_LAYER_LUNARG_mem_tracker";
    demo->device_validation_layers[1] = "VK_LAYER_GOOGLE_unique_objects";
    device_validation_layer_count = 2;

    /* Look for validation layers */
    VkBool32 validation_found = 0;
    err = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL);
    assert(!err);

    if (instance_layer_count > 0) {
        VkLayerProperties *instance_layers =
            malloc(sizeof(VkLayerProperties) * instance_layer_count);
        err = vkEnumerateInstanceLayerProperties(&instance_layer_count,
                                                 instance_layers);
        assert(!err);

        if (demo->validate) {
            validation_found = demo_check_layers(
                ARRAY_SIZE(instance_validation_layers),
                instance_validation_layers, instance_layer_count,
                instance_layers);
            demo->enabled_layer_count = ARRAY_SIZE(instance_validation_layers);
        }

        free(instance_layers);
    }

    if (demo->validate && !validation_found) {
        ERR_EXIT("vkEnumerateInstanceLayerProperties failed to find"
                 "required validation layer.

"
                 "Please look at the Getting Started guide for additional "
                 "information.
",
                 "vkCreateInstance Failure");
    }

    /* Look for instance extensions */
    VkBool32 surfaceExtFound = 0;
    VkBool32 platformSurfaceExtFound = 0;
    memset(demo->extension_names, 0, sizeof(demo->extension_names));

    err = vkEnumerateInstanceExtensionProperties(
        NULL, &instance_extension_count, NULL);
    assert(!err);

    if (instance_extension_count > 0) {
        VkExtensionProperties *instance_extensions =
            malloc(sizeof(VkExtensionProperties) * instance_extension_count);
        err = vkEnumerateInstanceExtensionProperties(
            NULL, &instance_extension_count, instance_extensions);
        assert(!err);
        for (uint32_t i = 0; i < instance_extension_count; i++) {
            if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME,
                        instance_extensions**.extensionName)) {
                surfaceExtFound = 1;
                demo->extension_names[demo->enabled_extension_count++] =
                    VK_KHR_SURFACE_EXTENSION_NAME;
            }
#ifdef _WIN32
            if (!strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
                        instance_extensions**.extensionName)) {
                platformSurfaceExtFound = 1;
                demo->extension_names[demo->enabled_extension_count++] =
                    VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
            }
#else  // _WIN32
            if (!strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME,
                        instance_extensions**.extensionName)) {
                platformSurfaceExtFound = 1;
                demo->extension_names[demo->enabled_extension_count++] =
                    VK_KHR_XCB_SURFACE_EXTENSION_NAME;
            }
#endif // _WIN32
            if (!strcmp(VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
                        instance_extensions**.extensionName)) {
                if (demo->validate) {
                    demo->extension_names[demo->enabled_extension_count++] =
                        VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
                }
            }
            assert(demo->enabled_extension_count < 64);
        }

        free(instance_extensions);
    }

    if (!surfaceExtFound) {
        ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
                 "the " VK_KHR_SURFACE_EXTENSION_NAME
                 " extension.

Do you have a compatible "
                 "Vulkan installable client driver (ICD) installed?
Please "
                 "look at the Getting Started guide for additional "
                 "information.
",
                 "vkCreateInstance Failure");
    }
    if (!platformSurfaceExtFound) {
#ifdef _WIN32
        ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
                 "the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME
                 " extension.

Do you have a compatible "
                 "Vulkan installable client driver (ICD) installed?
Please "
                 "look at the Getting Started guide for additional "
                 "information.
",
                 "vkCreateInstance Failure");
#else  // _WIN32
        ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
                 "the " VK_KHR_XCB_SURFACE_EXTENSION_NAME
                 " extension.

Do you have a compatible "
                 "Vulkan installable client driver (ICD) installed?
Please "
                 "look at the Getting Started guide for additional "
                 "information.
",
                 "vkCreateInstance Failure");
#endif // _WIN32
    }
    const VkApplicationInfo app = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pNext = NULL,
        .pApplicationName = APP_SHORT_NAME,
        .applicationVersion = 0,
        .pEngineName = APP_SHORT_NAME,
        .engineVersion = 0,
        .apiVersion = VK_API_VERSION,
    };
    VkInstanceCreateInfo inst_info = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .pNext = NULL,
        .pApplicationInfo = &app,
        .enabledLayerCount = demo->enabled_layer_count,
        .ppEnabledLayerNames = (const char *const *)instance_validation_layers,
        .enabledExtensionCount = demo->enabled_extension_count,
        .ppEnabledExtensionNames = (const char *const *)demo->extension_names,
    };

    uint32_t gpu_count;

    demo->allocator.pfnAllocation = myalloc;
    demo->allocator.pfnFree = myfree;
    demo->allocator.pfnReallocation = myrealloc;

    err = vkCreateInstance(&inst_info, &demo->allocator, &demo->inst);
    if (err == VK_ERROR_INCOMPATIBLE_DRIVER) {
        ERR_EXIT("Cannot find a compatible Vulkan installable client driver "
                 "(ICD).

Please look at the Getting Started guide for "
                 "additional information.
",
                 "vkCreateInstance Failure");
    } else if (err == VK_ERROR_EXTENSION_NOT_PRESENT) {
        ERR_EXIT("Cannot find a specified extension library"
                 ".
Make sure your layers path is set appropriately
",
                 "vkCreateInstance Failure");
    } else if (err) {
        ERR_EXIT("vkCreateInstance failed.

Do you have a compatible Vulkan "
                 "installable client driver (ICD) installed?
Please look at "
                 "the Getting Started guide for additional information.
",
                 "vkCreateInstance Failure");
    }

    /* Make initial call to query gpu_count, then second call for gpu info*/
    err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, NULL);
    assert(!err && gpu_count > 0);

    if (gpu_count > 0) {
        VkPhysicalDevice *physical_devices =
            malloc(sizeof(VkPhysicalDevice) * gpu_count);
        err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count,
                                         physical_devices);
        assert(!err);
        /* For tri demo we just grab the first physical device */
        demo->gpu = physical_devices[0];
        free(physical_devices);
    } else {
        ERR_EXIT("vkEnumeratePhysicalDevices reported zero accessible devices."
                 "

Do you have a compatible Vulkan installable client"
                 " driver (ICD) installed?
Please look at the Getting Started"
                 " guide for additional information.
",
                 "vkEnumeratePhysicalDevices Failure");
    }

    /* Look for validation layers */
    validation_found = 0;
    demo->enabled_layer_count = 0;
    uint32_t device_layer_count = 0;
    err =
        vkEnumerateDeviceLayerProperties(demo->gpu, &device_layer_count, NULL);
    assert(!err);

    if (device_layer_count > 0) {
        VkLayerProperties *device_layers =
            malloc(sizeof(VkLayerProperties) * device_layer_count);
        err = vkEnumerateDeviceLayerProperties(demo->gpu, &device_layer_count,
                                               device_layers);
        assert(!err);

        if (demo->validate) {
            validation_found = demo_check_layers(device_validation_layer_count,
                                                 demo->device_validation_layers,
                                                 device_layer_count,
                                                 device_layers);
            demo->enabled_layer_count = device_validation_layer_count;
        }

        free(device_layers);
    }

    if (demo->validate && !validation_found) {
        ERR_EXIT("vkEnumerateDeviceLayerProperties failed to find "
                 "a required validation layer.

"
                 "Please look at the Getting Started guide for additional "
                 "information.
",
                 "vkCreateDevice Failure");
    }

    /* Loog for device extensions */
    uint32_t device_extension_count = 0;
    VkBool32 swapchainExtFound = 0;
    demo->enabled_extension_count = 0;
    memset(demo->extension_names, 0, sizeof(demo->extension_names));

    err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL,
                                               &device_extension_count, NULL);
    assert(!err);

    if (device_extension_count > 0) {
        VkExtensionProperties *device_extensions =
                malloc(sizeof(VkExtensionProperties) * device_extension_count);
        err = vkEnumerateDeviceExtensionProperties(
            demo->gpu, NULL, &device_extension_count, device_extensions);
        assert(!err);

        for (uint32_t i = 0; i < device_extension_count; i++) {
            if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME,
                        device_extensions**.extensionName)) {
                swapchainExtFound = 1;
                demo->extension_names[demo->enabled_extension_count++] =
                    VK_KHR_SWAPCHAIN_EXTENSION_NAME;
            }
            assert(demo->enabled_extension_count < 64);
        }

        free(device_extensions);
    }

    if (!swapchainExtFound) {
        ERR_EXIT("vkEnumerateDeviceExtensionProperties failed to find "
                 "the " VK_KHR_SWAPCHAIN_EXTENSION_NAME
                 " extension.

Do you have a compatible "
                 "Vulkan installable client driver (ICD) installed?
Please "
                 "look at the Getting Started guide for additional "
                 "information.
",
                 "vkCreateInstance Failure");
    }

    if (demo->validate) {
        demo->CreateDebugReportCallback =
            (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(
                demo->inst, "vkCreateDebugReportCallbackEXT");
        if (!demo->CreateDebugReportCallback) {
            ERR_EXIT(
                "GetProcAddr: Unable to find vkCreateDebugReportCallbackEXT
",
                "vkGetProcAddr Failure");
        }
        VkDebugReportCallbackCreateInfoEXT dbgCreateInfo;
        dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
        dbgCreateInfo.flags =
            VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
        dbgCreateInfo.pfnCallback = dbgFunc;
        dbgCreateInfo.pUserData = NULL;
        dbgCreateInfo.pNext = NULL;
        err = demo->CreateDebugReportCallback(demo->inst, &dbgCreateInfo, NULL,
                                              &demo->msg_callback);
        switch (err) {
        case VK_SUCCESS:
            break;
        case VK_ERROR_OUT_OF_HOST_MEMORY:
            ERR_EXIT("CreateDebugReportCallback: out of host memory
",
                     "CreateDebugReportCallback Failure");
            break;
        default:
            ERR_EXIT("CreateDebugReportCallback: unknown failure
",
                     "CreateDebugReportCallback Failure");
            break;
        }
    }

    // Having these GIPA queries of device extension entry points both
    // BEFORE and AFTER vkCreateDevice is a good test for the loader
    GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfaceCapabilitiesKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfaceFormatsKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfacePresentModesKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfaceSupportKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, CreateSwapchainKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, DestroySwapchainKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, GetSwapchainImagesKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, AcquireNextImageKHR);
    GET_INSTANCE_PROC_ADDR(demo->inst, QueuePresentKHR);

    vkGetPhysicalDeviceProperties(demo->gpu, &demo->gpu_props);

    // Query with NULL data to get count
    vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_count,
                                             NULL);

    demo->queue_props = (VkQueueFamilyProperties *)malloc(
        demo->queue_count * sizeof(VkQueueFamilyProperties));
    vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_count,
                                             demo->queue_props);
    assert(demo->queue_count >= 1);

    // Graphics queue and MemMgr queue can be separate.
    // TODO: Add support for separate queues, including synchronization,
    //       and appropriate tracking for QueueSubmit
}

static void demo_init_device(struct demo *demo) {
    VkResult U_ASSERT_ONLY err;

    float queue_priorities[1] = {0.0};
    const VkDeviceQueueCreateInfo queue = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        .pNext = NULL,
        .queueFamilyIndex = demo->graphics_queue_node_index,
        .queueCount = 1,
        .pQueuePriorities = queue_priorities};

    VkDeviceCreateInfo device = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext = NULL,
        .queueCreateInfoCount = 1,
        .pQueueCreateInfos = &queue,
        .enabledLayerCount = demo->enabled_layer_count,
        .ppEnabledLayerNames =
            (const char *const *)((demo->validate)
                                      ? demo->device_validation_layers
                                      : NULL),
        .enabledExtensionCount = demo->enabled_extension_count,
        .ppEnabledExtensionNames = (const char *const *)demo->extension_names,
    };

    err = vkCreateDevice(demo->gpu, &device, NULL, &demo->device);
    assert(!err);
}

static void demo_init_vk_swapchain(struct demo *demo) {
    VkResult U_ASSERT_ONLY err;
    uint32_t i;

// Create a WSI surface for the window:
#ifdef _WIN32
    VkWin32SurfaceCreateInfoKHR createInfo;
    createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
    createInfo.pNext = NULL;
    createInfo.flags = 0;
    createInfo.hinstance = demo->connection;
    createInfo.hwnd = demo->window;

    err =
        vkCreateWin32SurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface);

#else  // _WIN32
    VkXcbSurfaceCreateInfoKHR createInfo;
    createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
    createInfo.pNext = NULL;
    createInfo.flags = 0;
    createInfo.connection = demo->connection;
    createInfo.window = demo->window;

    err = vkCreateXcbSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface);
#endif // _WIN32

    // Iterate over each queue to learn whether it supports presenting:
    VkBool32 *supportsPresent =
        (VkBool32 *)malloc(demo->queue_count * sizeof(VkBool32));
    for (i = 0; i < demo->queue_count; i++) {
        demo->fpGetPhysicalDeviceSurfaceSupportKHR(demo->gpu, i, demo->surface,
                                                   &supportsPresent**);
    }

    // Search for a graphics and a present queue in the array of queue
    // families, try to find one that supports both
    uint32_t graphicsQueueNodeIndex = UINT32_MAX;
    uint32_t presentQueueNodeIndex = UINT32_MAX;
    for (i = 0; i < demo->queue_count; i++) {
        if ((demo->queue_props**.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
            if (graphicsQueueNodeIndex == UINT32_MAX) {
                graphicsQueueNodeIndex = i;
            }

            if (supportsPresent** == VK_TRUE) {
                graphicsQueueNodeIndex = i;
                presentQueueNodeIndex = i;
                break;
            }
        }
    }
    if (presentQueueNodeIndex == UINT32_MAX) {
        // If didn't find a queue that supports both graphics and present, then
        // find a separate present queue.
        for (uint32_t i = 0; i < demo->queue_count; ++i) {
            if (supportsPresent** == VK_TRUE) {
                presentQueueNodeIndex = i;
                break;
            }
        }
    }
    free(supportsPresent);

    // Generate error if could not find both a graphics and a present queue
    if (graphicsQueueNodeIndex == UINT32_MAX ||
        presentQueueNodeIndex == UINT32_MAX) {
        ERR_EXIT("Could not find a graphics and a present queue
",
                 "Swapchain Initialization Failure");
    }

    // TODO: Add support for separate queues, including presentation,
    //       synchronization, and appropriate tracking for QueueSubmit.
    // NOTE: While it is possible for an application to use a separate graphics
    //       and a present queues, this demo program assumes it is only using
    //       one:
    if (graphicsQueueNodeIndex != presentQueueNodeIndex) {
        ERR_EXIT("Could not find a common graphics and a present queue
",
                 "Swapchain Initialization Failure");
    }

    demo->graphics_queue_node_index = graphicsQueueNodeIndex;

    demo_init_device(demo);

    vkGetDeviceQueue(demo->device, demo->graphics_queue_node_index, 0,
                     &demo->queue);

    // Get the list of VkFormat's that are supported:
    uint32_t formatCount;
    err = demo->fpGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface,
                                                     &formatCount, NULL);
    assert(!err);
    VkSurfaceFormatKHR *surfFormats =
        (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR));
    err = demo->fpGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface,
                                                     &formatCount, surfFormats);
    assert(!err);
    // If the format list includes just one entry of VK_FORMAT_UNDEFINED,
    // the surface has no preferred format.  Otherwise, at least one
    // supported format will be returned.
    if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) {
        demo->format = VK_FORMAT_B8G8R8A8_UNORM;
    } else {
        assert(formatCount >= 1);
        demo->format = surfFormats[0].format;
    }
    demo->color_space = surfFormats[0].colorSpace;

    // Get Memory information and properties
    vkGetPhysicalDeviceMemoryProperties(demo->gpu, &demo->memory_properties);
}

static void demo_init_connection(struct demo *demo) {
#ifndef _WIN32
    const xcb_setup_t *setup;
    xcb_screen_iterator_t iter;
    int scr;

    demo->connection = xcb_connect(NULL, &scr);
    if (demo->connection == NULL) {
        printf("Cannot find a compatible Vulkan installable client driver "
               "(ICD).
Exiting ...
");
        fflush(stdout);
        exit(1);
    }

    setup = xcb_get_setup(demo->connection);
    iter = xcb_setup_roots_iterator(setup);
    while (scr-- > 0)
        xcb_screen_next(&iter);

    demo->screen = iter.data;
#endif // _WIN32
}

#ifdef _WIN32
static void demo_init(struct demo *demo, HINSTANCE hInstance, LPSTR pCmdLine)
#else  // _WIN32
static void demo_init(struct demo *demo, const int argc, const char *argv[])
#endif // _WIN32
{
    bool argv_error = false;

    memset(demo, 0, sizeof(*demo));

#ifdef _WIN32
    demo->connection = hInstance;
    strncpy(demo->name, APP_SHORT_NAME, APP_NAME_STR_LEN);

    if (strncmp(pCmdLine, "--use_staging", strlen("--use_staging")) == 0)
        demo->use_staging_buffer = true;
    else if (strlen(pCmdLine) != 0) {
        fprintf(stderr, "Do not recognize argument \"%s\".
", pCmdLine);
        argv_error = true;
    }
#else  // _WIN32
    for (int i = 0; i < argc; i++) {
        if (strncmp(argv**, "--use_staging", strlen("--use_staging")) == 0)
            demo->use_staging_buffer = true;
    }
#endif // _WIN32
    if (argv_error) {
        fprintf(stderr, "Usage:
  %s [--use_staging]
", APP_SHORT_NAME);
        fflush(stderr);
        exit(1);
    }

    demo_init_connection(demo);
    demo_init_vk(demo);

    demo->width = 300;
    demo->height = 300;
    demo->depthStencil = 1.0;
    demo->depthIncrement = -0.01f;
}

static void demo_cleanup(struct demo *demo) {
    uint32_t i;

    demo->prepared = false;

    for (i = 0; i < demo->swapchainImageCount; i++) {
        vkDestroyFramebuffer(demo->device, demo->framebuffers**, NULL);
    }
    free(demo->framebuffers);
    vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL);

    if (demo->setup_cmd) {
        vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->setup_cmd);
    }
    vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->draw_cmd);
    vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL);

    vkDestroyPipeline(demo->device, demo->pipeline, NULL);
    vkDestroyRenderPass(demo->device, demo->render_pass, NULL);
    vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL);
    vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL);

    vkDestroyBuffer(demo->device, demo->vertices.buf, NULL);
    vkFreeMemory(demo->device, demo->vertices.mem, NULL);

    for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
        vkDestroyImageView(demo->device, demo->textures**.view, NULL);
        vkDestroyImage(demo->device, demo->textures**.image, NULL);
        vkFreeMemory(demo->device, demo->textures**.mem, NULL);
        vkDestroySampler(demo->device, demo->textures**.sampler, NULL);
    }

    for (i = 0; i < demo->swapchainImageCount; i++) {
        vkDestroyImageView(demo->device, demo->buffers**.view, NULL);
    }

    vkDestroyImageView(demo->device, demo->depth.view, NULL);
    vkDestroyImage(demo->device, demo->depth.image, NULL);
    vkFreeMemory(demo->device, demo->depth.mem, NULL);

    demo->fpDestroySwapchainKHR(demo->device, demo->swapchain, NULL);
    free(demo->buffers);

    vkDestroyDevice(demo->device, NULL);
    vkDestroySurfaceKHR(demo->inst, demo->surface, NULL);
    vkDestroyInstance(demo->inst, &demo->allocator);

    free(demo->queue_props);

#ifndef _WIN32
    xcb_destroy_window(demo->connection, demo->window);
    xcb_disconnect(demo->connection);
    free(demo->atom_wm_delete_window);
#endif // _WIN32
}

static void demo_resize(struct demo *demo) {
    uint32_t i;

    // Don't react to resize until after first initialization.
    if (!demo->prepared) {
        return;
    }
    // In order to properly resize the window, we must re-create the swapchain
    // AND redo the command buffers, etc.
    //
    // First, perform part of the demo_cleanup() function:
    demo->prepared = false;

    for (i = 0; i < demo->swapchainImageCount; i++) {
        vkDestroyFramebuffer(demo->device, demo->framebuffers**, NULL);
    }
    free(demo->framebuffers);
    vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL);

    if (demo->setup_cmd) {
        vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->setup_cmd);
    }
    vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->draw_cmd);
    vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL);

    vkDestroyPipeline(demo->device, demo->pipeline, NULL);
    vkDestroyRenderPass(demo->device, demo->render_pass, NULL);
    vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL);
    vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL);

    vkDestroyBuffer(demo->device, demo->vertices.buf, NULL);
    vkFreeMemory(demo->device, demo->vertices.mem, NULL);

    for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
        vkDestroyImageView(demo->device, demo->textures**.view, NULL);
        vkDestroyImage(demo->device, demo->textures**.image, NULL);
        vkFreeMemory(demo->device, demo->textures**.mem, NULL);
        vkDestroySampler(demo->device, demo->textures**.sampler, NULL);
    }

    for (i = 0; i < demo->swapchainImageCount; i++) {
        vkDestroyImageView(demo->device, demo->buffers**.view, NULL);
    }

    vkDestroyImageView(demo->device, demo->depth.view, NULL);
    vkDestroyImage(demo->device, demo->depth.image, NULL);
    vkFreeMemory(demo->device, demo->depth.mem, NULL);

    free(demo->buffers);

    // Second, re-perform the demo_prepare() function, which will re-create the
    // swapchain:
    demo_prepare(demo);
}

#ifdef _WIN32
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR pCmdLine, int nCmdShow) {
    MSG msg;   // message
    bool done; // flag saying when app is complete

    demo_init(&demo, hInstance, pCmdLine);
    demo_create_window(&demo);
    demo_init_vk_swapchain(&demo);

    demo_prepare(&demo);

    done = false; // initialize loop condition variable
    /* main message loop*/
    while (!done) {
        PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
        if (msg.message == WM_QUIT) // check for a quit message
        {
            done = true; // if found, quit app
        } else {
            /* Translate and dispatch to event queue*/
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        RedrawWindow(demo.window, NULL, NULL, RDW_INTERNALPAINT);
    }

    demo_cleanup(&demo);

    return (int)msg.wParam;
}
#else  // _WIN32
int main(const int argc, const char *argv[]) {
    struct demo demo;

    demo_init(&demo, argc, argv);
    demo_create_window(&demo);
    demo_init_vk_swapchain(&demo);

    demo_prepare(&demo);
    demo_run(&demo);

    demo_cleanup(&demo);

    return 0;
}
#endif // _WIN32

Edit: lol, noch nicht mal vollständig:

 * Draw a textured triangle with depth testing.  This is written against Intel
 * ICD.  It does not do state transition nor object memory binding like it
 * should.  It also does no error checking.

(Sehr gut übrigens, das auf dem neuen Server jetzt auch längere Beiträge erlaubt sind… :wink: )

Gruß
Fancy