Skip to content

Specification (German)

estard edited this page Feb 13, 2023 · 6 revisions

TGA Spezifikation

Die Trainings Graphics API (TGA) ist eine C++17 API, die darauf ausgelegt ist, einen einfachen Einstieg in die Grafikprogrammierung zu ermöglichen.

Die TGA ist an dem Design moderner Grafik APIs orientiert, um erworbene Fähigkeiten auf vollwertige Grafik APIs übertragen zu können.

Der Funktionsumfang der TGA ist auf die wesentlichen Aspekte, die für das Lösen der Assignments im Praktikum notwendig sind, beschränkt. Ziel ist es, Unübersichtlichkeit und Ablenkung zu vermeiden sowie die Anforderungen an Soft-und Hardware gering zu halten.

Die TGA ist mittels der Vulkan API implementiert und akzeptiert SPIR-V Shader, um nativ auf Windows und Linux verfügbar zu sein und mittels MoltenVK moltenvk auf MacOS funktionieren zu können.

Die aktuelle Entwicklungsversion ist derzeit Open-Source auf https://github.com/Estard/TGA zu finden.

Inhaltsübersicht

Interface und Generelles Design

Alle zur Verwendung von TGA benötigten enums, handles, structs und classes sind in der Datei tga.hpp enthalten und befinden sich im namespace tga::.

Die Klasse tga::Interface ist der Einstiegspunkt der API und enthält alle Funktionen, die für die Interaktion mit der Grafikkarte benötigt werden.

Da das tga::Interface eine abstrakte Klasse ist, muss zusätzlich eine konkrete Implementierung eingebunden werden, wofür derzeit nur die Implementierung der TGA Spezifikation mittels der Vulkan API, definiert in der Header Datei tga_vulkan.hpp, verwendet werden kann.

Eine konkrete Implementierung bietet keine zusätzliche Funktionalität über das Basis Interface. Code, der Verwendung von dem Interface macht, sollte daher keine direkte Referenz zu einer Implementierung haben, sondern sollte einen Pointer zur Basis Klasse ansprechen.

Ressourcen

Management

Alle Ressourcen werden über typsichere Handles angesprochen.

Erstellte Ressourcen sind grundsätzlich immutable. Es ist nur möglich den Inhalt des assoziierten Speichers mittels Shadern oder bestimmten Interface Funktionen (“Interface Funktion” ist eine Funktion, die in der tga::Interface Klasse spezifiziert ist.) zu ändern.

Die Handles zu Ressourcen werden durch das Aufrufen entsprechender Interface::create*Resource* Funktionen erstellt und mittels einer Überladung von Interface::free(*Resource Handle*) wieder freigegeben.

Ein Handle, der ohne entsprechenden Aufruf einer Interface Funktion erstellt wurde, ist ein TGA_NULL_HANDLE. Ob ein Handle ein TGA_NULL_HANDLE ist, kann über den Boolean Operator abgefragt werden.

Alle Interface Funktionen zur Ressourcen Erstellung nehmen eine *Resource*Info struct als Parameter. Diese struct wird als const Reference übergeben, was es erlaubt, temporäre Objekte im Funktionsaufruf zu verwenden. Da Ressourcen immutable sind, bleiben alle in der *Resource*Info struct angegebenen Meta Daten gültig. Der Nutzer (“Nutzer” bezieht sich auf den Nutzer von TGA, also die Person, welche mit dieser API ein Programm erstellt) muss also den aktuellen Stand von Ressourcen nicht abfragen und kann selbst entscheiden, ob er die Info struct abspeichert oder nach der Erstellung eines Handles wieder frei gibt.

Das Design des Ressourcen Managements ist angelehnt an die in Vulkan vorkommenden CreateInfo structs und die in DirectX benutzten _*_DESC structs. Beim Umstieg auf diese APIs sollte daher der generelle Ablauf für die Ressourcen Erzeugung bereits vertraut sein.

Shader

Ein Shader spezifiziert den Code, der für jedes Element in einer Stufe der Grafikpipeline ausgeführt wird.

Zum Erstellen eines tga::Shader Handles wird die folgende Interface Funktion genutzt:

tga::Shader createShader(const ShaderInfo &shaderInfo);

Die ShaderInfo struct ist wie folgt definiert:

struct ShaderInfo{
    ShaderType type; 
    uint8_t const *src; 
    size_t srcSize; 
};
  • type ist ein Wert des tga::ShaderType enums
  • src ist ein Pointer zu SPIR-V Shader Code
  • srcSize ist die Größe des Shader Codes in Bytes

Das tga::ShaderType enum spezifiziert, um welche Art von Shader es sich handelt. Folgende Werte sind möglich:

enum class ShaderType{
    vertex,
    fragment,
    compute
};
  • vertex gibt an, dass es sich um einen Vertex Shader handelt
  • fragment gibt an, dass es sich um einen Fragment bzw. Pixel Shader handelt
  • compute gibt an, dass es sich um einen Compute Shader handelt

Die drei Arten von Shadern, vertex, fragment und compute, wurden gewählt, da sie garantiert auf jeder Implementierung der Vulkan API verfügbar sind. Vertex und Fragment Shader sind ausreichend für das Lösen aller im Praktikum vorkommenden Aufgaben.

Compute Shader bieten interessierten Studenten zusätzliche Möglichkeiten beim Lösen der Assignments und bieten dem Aufgabensteller Mittel, um Funktionen zur Verfügung zu stellen, ohne dabei Implementierungsdetail preiszugeben.

Tessellation und Geometry Shader sind laut Vulkan Spezifikation optional und daher nicht garantiert verfügbar. Im Speziellen hat Apples Metal API keine Unterstützung für Geometry Shader, weshalb über MoltenVK das Benutzen dieser Shader nicht möglich ist. Geometry Shader konnten daher nicht in die TGA aufgenommen werden, ohne MacOS auszuschließen.

Die von der TGA akzeptierte Shader Sprache ist die Standard Portable Intermediate Representation SPIR-V. SPIR-V erlaubt die Nutzung von sowohl der OpenGL Shading Language (GLSL) und der High Level Shading Language (HLSL) als Shader Sprache und kann bei Bedarf zu anderen Shader Sprachen konvertiert werden. Vorteile für den Nutzer sind dabei, dass Syntaxfehler im Shader Code bereits vom Compiler abgefangen werden und nicht erst bei der Ausführung des Programms festgestellt werden.

Die für die TGA empfohlene Shader Sprache ist GLSL mit der GL_KHR_vulkan_glsl Extension glsl-vk, da diese syntaktisch näher an den in der TGA verwendeten Konvention ist.

Buffer

Ein Buffer repräsentiert einen Block an Daten im Speicher der GPU. Buffer werden zur Verwendung entweder direkt über Funktionen oder indirekt über InputSets gebunden.

Zum Erstellen eines tga::Buffer wird die folgende Interface Funktion genutzt:

tga::Buffer createBuffer(const BufferInfo &bufferInfo);

Die BufferInfo struct ist wie folgt definiert:

struct BufferInfo{
    BufferUsage usage; 
    uint8_t const *data; 
    size_t dataSize;
};
  • usage ist ein Wert oder eine Kombination an Werten des tga::BufferUsage enums
  • data ist ein Pointer auf die Daten, die in den VRAM geladen werden sollen
  • dataSize ist die Größe der Daten in Bytes

Das tga::BufferUsage enum spezifiziert, für welche Zwecke ein Buffer verwendet werden kann. Mehrere Werte können mit einem Logischen Oder kombiniert werden. Folgende Werte sind möglich:

enum class BufferUsage: uint32_t{
    undefined = 0x0,
    uniform = 0x1,
    vertex = 0x2,
    index = 0x4,
    storage = 0x8,
    indirect = 0x10
};
  • undefined gibt an, dass der Buffer nicht verwendet wird
  • uniform gibt an, dass der Buffer als Uniform bzw. Constant Buffer verwendet wird
  • vertex gibt an, dass der Buffer als Vertex Buffer verwendet wird
  • index gibt an, dass der Buffer als Index Buffer verwendet wird
  • storage gibt an, dass der Buffer als Storage Buffer verwendet wird
  • indirect gibt an, dass der Buffer als Input für draw*Indirect Befehle verwendet wird

Der Inhalt eines Buffers kann aktualisiert werden mit der Interface Funktion:

void updateBuffer(Buffer buffer, uint8_t const *data, 
                    size_t dataSize, uint32_t offset);
  • buffer ist der Handle zum Buffer, dessen Inhalt aktualisiert werden soll
  • data ist ein Pointer zu den Daten, die die Alten ersetzen sollen
  • dataSize ist die Anzahl der Bytes, die geupdated werden
  • offset ist der Offset in Bytes vom Start des zu aktualisierenden Buffers

Die Funktion blockiert, bis der Transfer abgeschlossen ist.

Eine Kopie des aktuellen Buffer Inhalts kann erhalten werden mit der Interface Funktion:

std::vector<uint8_t> readback(Buffer buffer);
  • buffer ist der Buffer, dessen Inhalt ausgelesen wird
  • Die Rückgabe ist der Inhalt des Buffers als Array von Bytes

Das Konzept eines Buffers ist in jeder modernen Grafik API vorhanden.

  • Uniform Buffer werden benutzt, um Daten, die für jeden Aufruf eines Shaders gleich sind, bereit zu stellen. Ihre Funktionsweise ist ähnlich bis identisch zu Constant Buffern in DirectX und den gleichnamigen Uniform Buffern in OpenGL und Vulkan.
  • Ein Vertex Buffer enthält die Daten für die Vertex Attribute, die der Vertex Shader erwartet.
  • Ein Index Buffer enthält Indizes in einen Vertex Buffer, um Duplikate im Vertex Buffer zu vermeiden. Beide Buffer Arten sind funktional gleich zu ihren Pendants in anderen APIs.
  • Ein Storage Buffer ist ein Buffer, dessen Inhalt von Shadern geändert werden kann. Er ist damit essentiell für das Speichern von Ergebnissen aus Compute Shadern. Storage Buffer sind identisch zu ihren Äquivalenten in OpenGL und Vulkan und am besten mit read-write Buffern aus DirectX zu vergleichen.

Texture

Eine Texture repräsentiert Bilddaten auf der GPU. Texturen werden mittels eines InputSets gebunden oder als Ziel für Render Operationen genutzt.

In der TGA ist der Sampler einer Texture kein separates Objekt sondern ist ein logischer Bestandteil.

Zum Erstellen eines tga::Texture Handles wird die folgende Interface Funktion genutzt:

tga::Texture createTexture(const TextureInfo &textureInfo);

Die TextureInfo struct ist wie folgt definiert:

struct TextureInfo{
    uint32_t width; 
    uint32_t height; 
    Format format; 
    uint8_t const *data; 
    size_t dataSize; 
    SamplerMode samplerMode; 
    AddressMode addressMode; 
    TextureType textureType; 
    uint32_t depthLayers;
};
  • width ist die Breite des Bildes in Pixel
  • height ist die Höhe des Bildes in Pixel
  • format ist ein Wert des tga::Format enums (Siehe: Formate und gibt hier an, wie die Pixel strukturiert sind
  • data ist ein Pointer zu Bilddaten, mit denen die Texture befüllt werden soll
  • dataSize ist die Größe der Bilddaten in Bytes
  • samplerMode ist ein Wert des tga::SamplerMode enums
  • addressMode ist ein Wert des tga::AddressMode enums
  • textureType ist ein Wert des tga::TextureType enums
  • depthLayers beschreibt bei 3D Texturen die Tiefe ansonsten die Anzahl an Ebenen der Texture

Anmerkung: Für format sollten 3-Komponentend (z.B. rgb_unrom) vermieden werden, da auf diese Formate oft keine Schreibzugriffe gestattet sind. Damit erweist sich auch das initiale Erstellen der Texture als problematisch. Es wird empfohlen, auf 4-Komponenten zu padden.

Anmerkung: Die Parameter samplerMode, addressMode, textureType und depthLayers bekommen im Konstruktor Standardwerte zugewiesen und müssen nicht explizit bei jeder Texture angegeben werden. Es ist empfehlenswert sich zu vergewissern, dass die Standardwerte tatsächlich der gewünschten Nutzungweise gerecht werden.

Das tga::SamplerMode enum spezifiziert, in welcher Weise zwischen Pixeln einer Texture interpoliert werden soll, wenn die Texturkoordinaten nicht genau auf einen Pixel treffen. Folgende Werte sind möglich:

enum class SamplerMode{
    nearest,
    linear
};
  • nearest gibt an, dass Nearest Neighbor Interpolation verwendet wird
  • linear gibt an, dass Bilineare Interpolation verwendet wird

Das tga::AddressMode enum spezifiziert, welche Werte beim Zugriff auf die Texture zurückgegeben werden, wenn die Texturkoordinaten außerhalb des 0 bis 1 Bereichs fallen. Folgende Werte sind möglich:

enum class AddressMode{
    clampBorder,
    clampEdge,
    repeat,
    repeatMirror
};
  • clampBorder gibt an, dass Pixel außerhalb der Grenzen in jeder Komponente Null als Wert haben
  • clampEdge gibt an, dass Pixel außerhalb der Grenzen den Wert des nächsten Pixels am Rand des Bildes annehmen
  • repeat gibt an, dass das Bild als Ganzes wiederholt wird, womit effektiv nur die Nachkommastellen der Texturkoordinaten betrachtet werden
  • repeatMirror gibt an, dass das Bild als Ganzes wiederholt wird, jedoch bei jeder Überschreitung einer Ganzzahl Grenze gespiegelt wird.

Das tga::TextureType enum spezifiziert die Art der Texture. Folgende Werte sind möglich:

enum class TextureType{
    _2D,
    _2DArray,
    _3D,
    _Cube
};
  • _2D gibt an, dass es sich um eine 2 dimensionale Texture handelt
  • _2DArray gibt an, dass es sich um eine 2 dimensionale Texture mit mehreren Ebenen handelt
  • _3D gibt an, dass es sich um eine 3 dimensionale Texture handelt
  • _Cube gibt an, dass es sich um eine Cubemap handelt

Anmerkung: enum Typen dürgen nicht mit einer Zahl starten und benötigen daher ‘_’ als Prefix.

Eine Kopie des aktuellen Texture Inhalts kann erhalten werden mit der Interface Funktion:

std::vector<uint8_t> readback(Texture texture);
  • texture ist die Texture, dessen Inhalt ausgelesen wird
  • Die Rückgabe ist der Inhalt der Texture als Array von Bytes

Texturen sind ein grundlegender Bestandteil jeder Grafikpipeline und daher ebenfalls in allen modernen Grafik APIs vertreten. Um den Umgang mit Texturen zu vereinfachen, wurden die Kontrollmöglichkeiten reduziert.

  • Der SamplerMode lässt sich nicht für unterschiedliche Dimensionen der Texturkoordinaten verschieden einstellen.
  • Texture Typen enthalten keine expliziten eindimensionalen Texturen.
  • Es wird keine Kontrolle über anisotropisches Filtern oder Mipmapping gegeben.

Window

Ein Window repräsentiert ein Anwendungsfenster und wird als Ziel für Render Operationen genutzt, um erzeugte Grafiken anzuzeigen.

Zum Erstellen eines tga::Window Handles wird die folgende Interface Funktion genutzt:

tga::Window createWindow(const WindowInfo &windowInfo);

Die WindowInfo struct ist wie folgt definiert:

struct WindowInfo{
    uint32_t width; 
    uint32_t height; 
    PresentMode presentMode; 
    uint32_t framebufferCount;
};
  • width ist die Breite des Fensters in Pixeln
  • height ist die Höhe des Fensters in Pixeln
  • presentMode ist ein Wert des tga::PresentMode enums
  • framebufferCount ist die Anzahl an Framebuffern, die mit dem Fenster assoziiert sind und bestimmt wie weit mehrfach buffering betrieben (double, triple, etc.) wird

Das tga::PresentMode enum spezifiziert, wann ein Backbuffer mit dem aktuellen Frontbuffer getauscht werden soll. Folgende Werte sind möglich:

enum class PresentMode{
    immediate,
    vsync,
};
  • immediate gibt an, dass sofort getauscht werden soll, was eventuell zu Tearing führen kann
  • vsync gibt an, dass bis zum nächsten vertical blank gewartet werden soll

Soll die Größe des Fensters abhängig von der Auflösung des Hauptmonitors sein, so kann diese abgefragt werden mit der Interface Funktion:

std::pair<uint32_t, uint32_t> screenResolution();

Der Titel des Fensters ist standardmäßig der Name der verwendeten Implementierung. Er kann geändert werden mit der Interface Funktion:

void setWindowTitel(Window window, const std::string &title);

Grafiktreiber habe Anforderungen an die minimale und maximale Anzahl an Framebuffern, die mit einem Fenster assoziiert werden können. Die tatsächliche Anzahl kann also abweichen von der in der WindowInfo spezifizierten Zahl. Die tatsächliche Anzahl der Framebuffer kann abgefragt werden mit der Interface Funktion:

uint32_t backbufferCount(Window window);

Da nicht jeder Framebuffer jederzeit verfügbar ist, muss vor dem Aufruf von Render Operationen der Index des nächsten verfügbaren Framebuffers erhalten werden. Diesen Index erhält man mit der Interface Funktion:

uint32_t nextFrame(Window window);

Um zu signalisieren, dass ein Backbuffer bereit zum Präsentieren ist, muss die Interface Funktion

void present(Window window); 

aufgerufen werden. Der zuletzt erhaltene Framebuffer wird dann, wie es dem PresentMode entspricht, mit dem Frontbuffer getauscht.

Ein Fenster ermöglicht auch das Abfragen von User Input über Tastatur und Maus. Eine Abfrage der aktuell gedrückten Tasten geschieht automatisch beim Aufruf von nextFrame oder kann manuell mit der Interface Funktion

void pollEvents(Window window);

getätigt werden.

Die Interface Funktion

bool keyDown(Window window, Key key);

gibt zurück, ob für ein bestimmtes Fenster bei der letzten Input Abfrage, die mit dem Parameter key spezifizierte Taste gedrückt war. Dabei ist key ein Wert des tga::Key enums und enthält sowohl Tasten der Tastatur als auch die der Computermaus, da diese funktional keinen Unterschied haben.

Anmerkung: Das tga::Key enum wird aufgrund seiner hohen Anzahl an Einträgen hier nicht aufgelistet.

Die Interface Funktion

std::pair<int, int> mousePosition(Window window)

gibt die Position der Maus relativ zum Fenster mit x und y Pixel Koordinaten zurück.

Die Interface Funktion

bool windowShouldClose(Window window)

gibt zurück, ob der Nutzer das Anwendungsfenster mittels des x-Knopfs schließen wollte.

Da Ressourcen in der TGA immutable sind, kann die Größe eines Fensters nach der Erstellung nicht mehr geändert werden. Diese Entscheidung ist zugunsten des Nutzers, da Abhängigkeiten zu anderen Ressourcen nicht zufällig invalidiert werden können.

Anwendungsfenster sind grundsätzlich abhängig vom Betriebssystem. Um Cross-Plattform Funktionalität zu gewährleisten, ist die Window System Integration mittels GLFW implementiert. Solange das in der Datei tga_WSI.hpp spezifizierte Interface eingehalten wird, wäre auch eine Implementierung mit einer anderen Bibliothek oder mit betriebssystem-spezifischen APIs möglich.

RenderPass

Ein RenderPass repräsentiert die gesamte Konfiguration der Grafikpipeline.

Zum Erstellen eines tga::RenderPass Handles wird die folgende Interface Funktion genutzt:

tga::RenderPass createRenderPass(const RenderPassInfo &renderPassInfo) 

Die RenderPassInfo struct ist wie folgt definiert:

struct RenderPassInfo{
    std::vector<Shader> shaderStages; 
    std::variant<Texture, Window, std::vector<Texture>> renderTarget; 
    ClearOperation clearOperations; 
    RasterizerConfig rasterizerConfig; 
    PerPixelOperations perPixelOperations; 
    InputLayout inputLayout; 
    VertexLayout vertexLayout;
};
  • shaderStages enthält entweder einen Vertex Shader und einen Fragment Shader oder einen einzelnen Compute Shader
  • renderTarget ist das Ziel der Render Operationen, also wohin das Ergebnis gespeichert wird. Dies kann eine oder mehrere Texturen oder ein einzelnes Window sein.
  • clearOperations ist ein Wert des tga::ClearOperation enums
  • rasterizerConfig ist eine Instanz der tga::RasterizerConfig struct
  • perPixelOperations ist eine Instanz der tga::PerPixelOperations struct
  • inputLayout ist eine Instanz der tga::InputLayout struct
  • vertexLayout ist eine Instanz der tga::VertexLayout struct

Anmerkung: Die maximale Anzahl an Texturen bei renderTarget ist von Hardware Limitierungen abhängig. Derzeit gängige Limits erlauben idr. mindestens 4 Texturen.

Die structs der RenderPassInfo bekommen im Konstruktor Standardwerte zugewiesen und müssen nur bei Bedarf explizit angegeben werden.

Zwar kann jede Texture als renderTarget angegeben werden, es ist jedoch nicht garantiert, dass jede Plattform das Schreiben in jedes Texture Format unterstützt. Da es hierzu keine spezifizierten Garantien gibt, wird dazu geraten, Formate, wie r32_sfloat und r32g32b32a32_sfloat, zu verwenden, die mit hoher Wahrscheinlichkeit unterstützt werden.

Der Depth Buffer wird als Teil des Render Targets angesehen. Haben zwei RenderPasses das selbe Render Target, so verwenden diese auch den selben Depth Buffer.

Das tga::ClearOperation enum gibt an, wie mit bestehenden Inhalten des Color und Depth Buffers beim Setzen des RenderPass verfahren werden soll. Folgende Werte sind möglich:

enum class ClearOperation{
    none,
    color,
    depth,
    all
};
  • none gibt an, dass sowohl Color als auch Depth Buffer unverändert bleiben
  • color gibt an, dass die Werte des Color Buffers auf Null gesetzt werden
  • depth gibt an, dass die Werte des Depth Buffers auf Eins gesetzt werden
  • all gibt an, dass sowohl die Werte des Color Buffers auf Null als auch die Werte des Depth Buffers auf Eins gesetzt werden sollen

Die tga::RasterizerConfig struct gibt an, wie Polygone beim Erstellen von Fragmenten behandelt werden. Die struct hat die folgenden Parameter:

struct RasterizerConfig{
    FrontFace frontFace;
    CullMode cullMode;
    PolygonMode polygonMode;
};
  • frontFace ist ein Wert des tga::FrontFace enums
  • cullMode ist ein Wert des tga::CullMode enums
  • polygonMode ist ein Wert des tga::PolygonMode enums

Das tga::FrontFace enum gibt an, in welcher Reihenfolge die Vertices eines Dreiecks interpretiert werden und hat die folgenden möglichen Werte:

enum class FrontFace{
    clockwise,
    counterclockwise
};
  • clockwise gibt an, dass die Vertices im Uhrzeigersinn angeordnet sind
  • counterclockwise gibt an, dass die Vertices gegen den Uhrzeigersinn angeordnet sind

Das tga::CullMode enum gibt an, welche Seiten eines Dreiecke gerendert werden sollen und hat die folgenden möglichen Werte:

enum class CullMode{
    none,
    front,
    back,
    all
};
  • none gibt an, dass sowohl die Vorder- als auch die Rückseite gerendert werden
  • front gibt an, dass die Vorderseite nicht gerendert werden soll
  • back gibt an, dass die Rückseite nicht gerendert werden soll
  • all gibt an, dass weder Vorder- noch Rückseite gerendert werden sollen

Das tga::PolygonMode enum gibt an, wie Polygone gefüllt werden sollen und hat die folgenden möglichen Werte:

enum class PolygonMode{
    solid,
    wireframe
};
  • solid gibt an, dass die Polygone gefüllt werden sollen
  • wireframe gibt an, dass nur die Kanten der Polygone gezeichnet werden sollen

Die tga::PerPixelOperations struct gibt an, welche Operationen auf erzeugte Pixel angewendet werden sollen. Die struct hat die folgenden Parameter:

struct PerPixelOperations{
    CompareOperation depthCompareOp;
    bool blendEnabled;
    BlendFactor srcBlend;
    BlendFactor dstBlend;
    BlendFactor srcAlphaBlend;
    BlendFactor dstAlphaBlend;
};
  • depthCompareOp ist ein Wert des tga::CompareOperation enums
  • blendEnabled gibt an, ob neu berechnete Pixel mit bereits im RenderTarget stehenden Pixeln gemischt werden
  • srcBlend ist ein Wert des tga::BlendFactor enums und gibt an, welcher Faktor auf die RGB Werte des neu berechneten Pixels angewandt wird
  • srcAlphaBlend ist ein Wert des tga::BlendFactor enums und gibt an, welcher Faktor auf den Alpha Wert des neu berechneten Pixels angewandt wird
  • dstBlend ist ein Wert des tga::BlendFactor enums und gibt an, welcher Faktor auf die RGB Werte des bereits bestehenden Pixels angewandt wird
  • dstAlphaBlend ist ein Wert des tga::BlendFactor enums und gibt an, welcher Faktor auf den Alpha Wert des bereits bestehenden Pixels angewandt wird

Das tga::CompareOperation enum gibt an, wie Depth Testing konfiguriert ist und hat die folgenden möglichen Werte:

enum class CompareOperation{
    ignore,
    equal,
    greater,
    greaterEqual,
    less,
    lessEqual
};
  • ignore gibt an, dass kein Depth Testing betrieben wird
  • equal gibt an, dass eine Pixel den Depth Test besteht, wenn die Depth gleich dem Wert des Depth Buffers ist
  • greater gibt an, dass eine Pixel den Depth Test besteht, wenn die Depth größer als der Wert des Depth Buffers ist
  • greaterEqual gibt an, dass eine Pixel den Depth Test besteht, wenn die Depth größer oder gleich dem Wert des Depth Buffers ist
  • less gibt an, dass eine Pixel den Depth Test besteht, wenn die Depth kleiner als der Wert des Depth Buffers ist
  • lessEqual gibt an, dass eine Pixel den Depth Test besteht, wenn die Depth kleiner oder gleich dem Wert des Depth Buffers ist

Das tga::BlendFactor enum gibt an, welcher Faktor beim Mischen zweier Pixel verwendet wird und kann die folgenden möglichen Werte haben:

enum class BlendFactor{
    zero,
    one,
    srcAlpha,
    oneMinusSrcAlpha,
    dstAlpha,
    oneMinusDstAlpha
};
  • zero gibt an, dass mit 0 multipliziert wird
  • one gibt an, dass mit 1 multipliziert wird
  • srcAlpha gibt an, dass mit dem Alpha Wert des neuen Pixels multipliziert wird
  • oneMinusSrcAlpha gibt an, dass mit 1 minus dem Alpha Wert des neuen Pixels multipliziert wird
  • dstAlpha gibt an, dass mit dem Alpha Wert des bestehenden Pixels multipliziert wird
  • oneMinusDstAlpha gibt an, dass mit 1 minus dem Alpha Wert des bestehenden Pixels multipliziert wird

Die tga::InputLayout struct gibt an, wie Daten für die Shaderstufen organisiert sind. Die struct hat die folgenden Parameter:

struct InputLayout{
    std::vector<SetLayout> setLayouts;
};
  • setLayouts ist ein Array der tga::SetLayout struct

Die tga::SetLayout struct gibt an, welche Bindings innerhalb eines Sets vorkommen. Die struct hat die folgenden Parameter:

struct SetLayout{
    std::vector<BindingLayout> bindingLayouts;
};
  • bindingLayouts ist ein Array der tga::BindingLayout struct

Die tga::BindingLayout struct gibt an, wie ein einzelnes Binding klassifiziert ist. Die struct hat die folgenden Parameter:

struct BindingLayout{
    BindingType type;
    uint32_t count;
};
  • type ist ein Wert des tga::BindingType enums
  • count ist die Anzahl der Array Elemente für dieses Binding

Das tga::BindingType enum gibt an, um welche Art von Binding es sich handelt und hat die folgenden möglichen Werte:

enum class BindingType{
    uniformBuffer,
    sampler,
    storageBuffer
};
  • uniformBuffer gibt an, dass ein Buffer mit Usage uniform erwartet wird
  • sampler gibt an, dass es eine Texture erwartet wird
  • storageBuffer gibt an, dass ein Buffer mit Usage storage erwartet wird

Die tga::VertexLayout struct gibt an, wie die Daten innerhalb eines Vertex Buffers interpretiert werden. Die struct hat die folgenden Parameter:

struct VertexLayout{
    size_t vertexSize;
    std::vector<VertexAttribute> vertexAttributes;
};
  • vertexSize ist die Größe eines einzelnen Vertex in Bytes
  • vertexAttributes ist ein Array der tga::VertexAttribute struct und zeigt an, wie ein Vertex aufgebaut ist

Die tga::VertexAttribute struct gibt an, wie ein Teil eines Vertex aufgebaut ist. Die struct hat die folgenden Parameter:

struct VertexAttribute{
    size_t offset;
    Format format;
};
  • offset ist die Distanz vom Start des Vertex zu Beginn der Attributdaten in Bytes
  • format ist ein Wert des tga::Format enums (Siehe: Formate) und gibt an, wie das Attribut strukturiert ist

In den Low Level Grafik APIs wie Vulkan, DirectX 12 und Metal wird die GPU nicht mehr als State Machine betrachtet und folglich nicht mehr mit einer Reihe von den Zustand verändernden Funktionsaufrufen konfiguriert. Stattdessen wird ein Pipeline State Objekt verwendet, welches die gesamte Konfiguration beschreibt.

Der Name “RenderPass” wurde gewählt, da dieser das Pipeline State Objekt mit der Information verbindet, welches Ziel für die Render Operationen gesetzt ist. Konkret werden die Vulkan Objekte vkPipeline und vkRenderPass vereinigt. Auch wurde der Begriff “RenderPass” als intuitiver eingeschätzt als “Pipeline”, wobei diese Einschätzung auf persönlichen Präferenzen beruht.

Dass der RenderPass alle Informationen über den Zustand der Pipeline vereinigt, hat den Vorteil, dass es für den Nutzer einfacher ist, den genauen Zustand der Grafikpipeline nachzuvollziehen, da keine versteckten Funktionsaufrufe diesen ändern können.

Für mehr Einfachheit und Übersichtlichkeit hat ein RenderPass in der TGA einen reduzierten Funktionsumfang ohne Unterstützung für Multi Sampling oder für nicht der Fenstergröße entsprechenden Viewports.

InputSet

Ein InputSet repräsentiert eine Sammlung von Bindings, die als Input für Shader agieren. Binding beschreibt die Verknüpfung einer Ressource zu einer Shader Variable.

Zum Erstellen eines tga::InputSet wird die folgende Interface Funktion genutzt:

tga::InputSet createInputSet(const InputSetInfo &inputSetInfo);

Die InputSetInfo struct ist wie folgt definiert:

struct InputSetInfo{
    RenderPass targetRenderPass; 
    uint32_t setIndex;   
    std::vector<Binding> bindings; 
};
  • targetRenderPass ist ein valider tga::RenderPass Handle, zu dem das InputSet kompatibel sein soll
  • setIndex spezifiziert den Index des Sets im Bezug auf das InputLayout des RenderPass
  • bindings ist ein Array der tga::Binding struct

Die tga::Binding struct gibt an, welche Position eine Ressource als Binding einnimmt. Die struct hat die folgenden Parameter:

struct Binding{
    std::variant<Buffer, Texture> resource;
    uint32_t slot;
    uint32_t arrayElement;
};
  • resource ist der Buffer oder die Texture, die gebunden werden sollen
  • slot ist der Index, den dieses Binding einnimmt
  • arrayElement spezifiziert, welche Stelle im Array angenommen wird, sollten mehrere Elemente an den selben Slot gebunden werden

TGA InputSets sind angelehnt an Vulkans Descriptor Sets und DirectX 12 Descriptor Tables und erlauben das simultane Austauschen mehrerer im Shader verwendeter Ressourcen.

Logisch zusammengehörende Bindings, wie zum Beispiel Texturen von einem Material, können daher vom Nutzer durch InputSets gruppiert werden.

Durch die Eindeutigkeit, wo welches Set und welches Element in einem Set steht, müssen die Positionen von Shader Variablen nicht extra abgefragt werden, was potentielle Fehlerquellen vermeidet.

Formate

Das tga::Format enum wird an mehreren Stellen in der API verwendet und beschreibt grundsätzlich den Aufbau von Strukturen mit einer variablen Anzahl an Komponenten. Die Namensgebung adaptiert die Konventionen von Vulkan und DirectX.

Es existieren vier Farbkomponenten, r,g,b,a für jeweils den Rot-, Grün-, Blau- und Alpha-Kanal. Die Reihenfolge der Komponenten beschreibt die Reihenfolge im Speicher, wobei in der TGA im Gegensatz zu anderen APIs keine Reihenfolge außer rgba vorhanden ist.

Hinter der Komponente folgt die Größe der Komponente in Bit, womit sich die Größe des gesamten Formats durch Addition der vorkommenden Komponenten bestimmen lässt.

Als Suffix folgt, in welchem Format die Komponenten interpretiert werden.

  • ‘uint’ steht für vorzeichenlosen Integer.
  • ‘sint’ steht für vorzeichenbehafteten Integer.
  • ‘srgb’ steht für Werte, die im sRGB Farbraum kodiert sind.
  • ‘snorm’ steht für vorzeichenbehaftete, normalisierte Integer, dessen Wertebereich als -1 bis 1 interpretiert wird.
  • ‘unorm’ steht für vorzeichenlose, normalisierte Integer, dessen Wertebereich als 0 bis 1 interpretiert wird.
  • ‘sfloat’ steht für vorzeichenbehaftete Floating Point Werte.

Folgende Formate sind in der TGA definiert:

enum class Format{
    undefined,
    r8_uint,
    r8_sint,
    r8_srgb,
    r8_unorm,
    r8_snorm,
    r8g8_uint,
    r8g8_sint,
    r8g8_srgb,
    r8g8_unorm,
    r8g8_snorm,
    r8g8b8_uint,
    r8g8b8_sint,
    r8g8b8_srgb,
    r8g8b8_unorm,
    r8g8b8_snorm,
    r8g8b8a8_uint,
    r8g8b8a8_sint,
    r8g8b8a8_srgb,
    r8g8b8a8_unorm,
    r8g8b8a8_snorm,
    r32_uint,
    r32_sint,
    r32_sfloat,
    r32g32_uint,
    r32g32_sint,
    r32g32_sfloat,
    r32g32b32_uint,
    r32g32b32_sint,
    r32g32b32_sfloat,
    r32g32b32a32_uint,
    r32g32b32a32_sint,
    r32g32b32a32_sfloat,
    r16_sfloat,
    r16g16_sfloat,
    r16g16b16_sfloat,
    r16g16b16a16_sfloat
};

Command Buffer

Command Buffer sind eine Liste an Befehlen, die von der GPU ausgeführt werden sollen und können nach ihrer Erstellung beliebig oft benutzt werden.

Das Konzept des Command Buffers ist in allen Low Level APIs sowie in DirectX 11 vertreten. Die TGA vereinfacht Command Buffer insofern, dass kein zusätzliches Objekt wie ein Command Pool oder ein Deferred Contex für die Nutzung erstellt werden muss.

Verwendung

Die Erstellung eines tga::CommandBuffer Handles benötigt den geordneten Aufruf mehrere Interface Funktionen.

Zuerst muss die Interface Funktion

void beginCommandBuffer();

aufgerufen werden. Diese bereitet die Aufnahme von GPU Befehlen vor.

Soll ein bereits existierender Command Buffer mit neuem Inhalt gefüllt werden, kann alternativ auch die Funktion

void beginCommandBuffer(CommandBuffer commandBuffer);

verwendet werden. Wird ein TGA_NULL_HANDLE übergeben, ist die Funktionsweise gleich der Funktion ohne Parameter.

Anschließend kann eine beliebige Anzahl von unter ‘GPU Befehle’ gelisteter Methoden aufgerufen werden, die in den Command Buffer aufgenommen werden.

Mit der Interface Funktion

CommandBuffer endCommandBuffer();

wird die Aufnahme gestoppt und der Handle zum erstellten tga::CommandBuffer wird zurückgegeben. Dieser Handle sollte auch akzeptiert werden, wenn ein Command Buffer wiederverwertet wurde.

Um einen Command Buffer auszuführen, wird folgende Interface Funktion genutzt:

void execute(CommandBuffer commandBuffer)  

Der Command Buffer wird daraufhin von der GPU asynchron abgearbeitet.

GPU Befehle

Während ein Command Buffer Befehle aufnimmt, können folgende Interface Funktionen aufgerufen werden:

void setRenderPass(RenderPass renderPass, uint32_t framebufferIndex);

Setzt die Konfiguration der GPU so, wie es der RenderPass Handle beschreibt.

  • renderPass ist ein valider tga::RenderPass Handle
  • framebufferIndex ist der Index eines mit dem Render Target des RenderPasses assoziierten Framebuffers

Anmerkung: Der Wert von framebufferIndex muss zum Zeitpunkt der Command Buffer Ausführung zu einem verfügbaren Framebuffer gehören.

void bindVertexBuffer(Buffer buffer);

Bindet einen Vertex Buffer für die Verwendung bei draw und drawIndexed

  • buffer ist ein valider tga::Buffer Handle, der mit tga::BufferUsage::vertex erstellt wurde
void bindIndexBuffer(Buffer buffer);

Bindet einen Index Buffer für die Verwendung bei drawIndexed

  • buffer ist ein valider tga::Buffer Handle, der mit tga::BufferUsage::index erstellt wurde

Anmerkung: Der Inhalt des Index Buffers wird als Array von 32 Bit großer, vorzeichenloser Integer interpretiert

void bindInputSet(InputSet inputSet)

Bindet die Ressourcen eines Input Sets an die zugehörige Stelle im zuvor gesetzten RenderPass

  • inputSet ist ein valider tga::InputSet Handle, der mit dem aktuellen RenderPass kompatibel ist

Anmerkung: Der Aufruf von bindInputSet ist erst nach einem Aufruf von setRenderPass gestattet. Ein InputSet bleibt gebunden, solange der RenderPass eines nachfolgenden Aufrufs von setRenderPass immer noch kompatibel ist.

void draw(uint32_t vertexCount, uint32_t firstVertex, 
            uint32_t instanceCount , uint32_t firstInstance);

Ruft den Vertex Shader (vertexCount x instanceCount)-mal auf.

  • vertexCount ist die Anzahl der Vertices, für die der Vertex Shader aufgerufen wird
  • firstVertex ist der Index des ersten Vertex innerhalb des aktuellen Vertex Buffers
  • instanceCount ist die Anzahl an Instanzen bei instanced rendering
  • firstInstance ist der Index der ersten Instanz bei instanced rendering

Anmerkung: Der Aufruf von draw ist erst nach einem Aufruf von setRenderPass gestattet. Je nach verwendetem Vertex Shader ist ein Aufruf von bindVertexBuffer nicht notwendig.

void drawIndirect(Buffer buffer, uint32_t drawCount, 
                    size_t offset, uint32_t stride);

Ähnlich zu Interface::draw(), aber liest die benötigten Parameter aus buffer

  • buffer ist ein valider tga::Buffer Handle, der mit tga::BufferUsage::indirect erstellt wurde. Der Inhalt des Buffers wird als Instanz von tga::DrawIndirectCommand interpretiert.
  • drawCount ist die Anzahl an indirekt ausführenden Interface::draw() Befehlen.
  • offset ist der Offset des ersten tga::DrawIndirectCommand vom Start des Indirect Buffers.
  • stride gibt bei drawCount>1 den Abstand zwischen aufeinanderfolgenden Instanzen von tga::DrawIndirectCommand an.

Die tga::DrawIndirectCommand struct definiert, wie der Inhalt des Buffers bei einem Aufruf von Interface::drawIndirect() interpretiert wird. Die struct hat die folgenden Parameter:

struct DrawIndirectCommand {
    uint32_t vertexCount;
    uint32_t instanceCount;
    uint32_t firstVertex;
    uint32_t firstInstance;
};
  • vertexCount ist die Anzahl der Vertices, für die der Vertex Shader aufgerufen wird
  • instanceCount ist die Anzahl an Instanzen bei instanced rendering
  • firstVertex ist der Index des ersten Vertex innerhalb des aktuellen Vertex Buffers
  • firstInstance ist der Index der ersten Instanz bei instanced rendering

Anmerkung: Die Reihenfolge der Argumente unterscheidet sich zu einem regulärem Aufruf von Interface::draw(), um den Inhalt des Buffers direkt an die unterliegende Grafik-API weiterzugeben.

void drawIndexed(uint32_t indexCount, uint32_t firstIndex, 
                    uint32_t vertexOffset, 
                    uint32_t instanceCount, uint32_t firstInstance);

Ruft den Vertex Shader (indexCount x instanceCount)-mal auf.

  • indexCount ist die Anzahl der Vertices, für die der Vertex Shader aufgerufen wird
  • firstIndex ist der Index des ersten Index innerhalb des aktuellen Index Buffers
  • vertexOffset ist der Offset des ersten Vertex vom Start des aktuellen Vertex Buffers
  • instanceCount ist die Anzahl an Instanzen bei instanced rendering
  • firstInstance ist der Index der ersten Instanz bei instanced rendering

Anmerkung: Der Aufruf von drawIndexed ist erst nach einem Aufruf von setRenderPass und einem Aufruf von bindIndexBuffer gestattet. Je nach verwendetem Vertex Shader ist ein Aufruf von bindVertexBuffer nicht notwendig.

void drawIndexedIndirect(Buffer buffer, uint32_t drawCount, 
                    size_t offset, uint32_t stride);

Ähnlich zu Interface::drawIndexed(), aber liest die benötigten Parameter aus buffer

  • buffer ist ein valider tga::Buffer Handle, der mit tga::BufferUsage::indirect erstellt wurde. Der Inhalt des Buffers wird als Instanz von _tga::DrawIndexedIndirectCommand interpretiert.
  • drawCount ist die Anzahl an indirekt ausführenden Interface::drawIndexed() Befehlen.
  • offset ist der Offset des ersten tga::DrawIndexedIndirectCommand vom Start des Indirect Buffers.
  • stride gibt bei drawCount>1 den Abstand zwischen aufeinanderfolgenden Instanzen von tga::DrawIndexedIndirectCommand an.

Die tga::DrawIndexedIndirectCommand struct definiert, wie der Inhalt des Buffers bei einem Aufruf von Interface::drawIndexedIndirect() interpretiert wird. Die struct hat die folgenden Parameter:

struct DrawIndexedIndirectCommand {
    uint32_t indexCount;
    uint32_t instanceCount;
    uint32_t firstIndex;
    int32_t vertexOffset;
    uint32_t firstInstance;
};
  • indexCount ist die Anzahl der Vertices, für die der Vertex Shader aufgerufen wird
  • instanceCount ist die Anzahl an Instanzen bei instanced rendering
  • firstIndex ist der Index des ersten Index innerhalb des aktuellen Index Buffers
  • vertexOffset ist der Offset des ersten Vertex vom Start des aktuellen Vertex Buffers
  • firstInstance ist der Index der ersten Instanz bei instanced rendering

Anmerkung: Die Reihenfolge der Argumente unterscheidet sich zu einem regulärem Aufruf von Interface::drawIndexed(), um den Inhalt des Buffers direkt an die unterliegende Grafik-API weiterzugeben.

void dispatch(  uint32_t groupCountX, 
                uint32_t groupCountY, 
                uint32_t groupCountZ);

Ruft einen Compute Shader (groupCountX x groupCountY x groupCountZ x Anzahl lokaler Workgroups des Shaders)-mal auf.

  • groupCountX ist die Anzahl der lokalen Arbeitsgruppen in X-Richtung
  • groupCountY ist die Anzahl der lokalen Arbeitsgruppen in Y-Richtung
  • groupCountZ ist die Anzahl der lokalen Arbeitsgruppen in Z-Richtung

Anmerkung: Der Aufruf von dispatch ist erst nach einem Aufruf von setRenderPass gestattet, wenn der gesetzte RenderPass einen Compute Shader enthält.

Utility Funktionen

Die TGA stellt keine eigene Bibliothek für mathematische Operationen bereit. Stattdessen wird die Cross-Plattform Open Source Bibliothek GLM verwendet.

GLM wurde gewählt, da sie aufgrund ähnlicher Funktionsweise und Namensgebung gut mit der Shader Sprache GLSL harmoniert.

Standardmäßig verwendet GLM die Spezifikationen von OpenGL, was insbesondere Einfluss auf die Erstellung von Projektionsmatrizen hat. Die auf Vulkan basierende Implementierung von TGA übernimmt allerdings die Konventionen der Vulkan API im Bezug auf das Koordinatensystem der Normalized Device Coordinates (NDC) und dem Wertebereich des Depth Buffers.

Da diese Konventionen sich zwischen OpenGL und Vulkan unterscheiden, muss GLM durch die Verwendung von Präprozessor Makros angepasst werden. Diese Anpassungen wurden in der Datei tga_math.hpp getätigt und erlauben es, GLM korrekt konfiguriert zu benutzen.

Für die Erstellung einer Projektionsmatrix, die die korrekte Orientierung der NDC hat, wurde zusätzlich die Template Funktion

glm::perspective_vk(T fovy, T aspect, T zNear, T zFar);

hinzugefügt.

Außerdem wird eine Template Funktion

glm::remap (T value, T fromMin, T fromMax, T toMin, T toMax);

bereitgestellt, mit deren Hilfe ein Wert von einem Wertebereich in einen anderen übertragen werden kann.

Die TGA bietet des Weiteren eine Hilfsbibliothek “tga_utils” für häufig benötigte Speicheroperationen an.

Für das Laden eines Shaders aus einer Datei existiert die Funktion:

tga::Shader loadShader(std::string const& filepath, 
                        tga::ShaderType shaderType, 
                        std::shared_ptr<tga::Interface> tgai)
  • filepath ist der Pfad zum Shader im Dateisystem
  • shaderType ist ein Wert des tga::ShaderType enums und gibt an, welche Art der Shader hat
  • tgai ist ein Pointer auf eine TGA Implementierung, die für das Erstellen verwendet werden soll

Rückgabewert ist ein mit der übergebenen Interface Instance assoziierter tga::Shader Handle.

Für das direkte Laden einer Datei in eine GPU Texture existieren die Funktionen:

TextureBundle loadTexture(std::string const& filepath, 
                            tga::Format format, 
                            tga::SamplerMode samplerMode, 
                            std::shared_ptr<tga::Interface> tgai, 
                            bool doGammaCorrection);

und

TextureBundle loadTexture(std::string const& filepath, 
                            tga::Format format, 
                            tga::SamplerMode samplerMode,
                            tga::AddressMode addressMode,
                            std::shared_ptr<tga::Interface> tgai, 
                            bool doGammaCorrection);
  • filepath ist der Pfad zur Bilddatei im Dateisystem
  • format ist ein Wert des tga::Format enums und gibt an, in welches Format die Pixel übertragen werden sollen
  • samplerMode entspricht dem samplerMode der tga::TextureInfo struct
  • addressMode entspricht dem addressMode der tga::TextureInfo struct
  • tgai ist ein Pointer auf eine TGA Implementierung, die für das Erstellen verwendet werden soll
  • doGammaCorrection gibt an, ob beim Laden eines HDR Bildes die Werte von linear zu sRGB konvertiert werden sollen

Rückgabewert ist ein TextureBundle struct, welches den tga::Texture Handle sowie die Höhe und Breite der geladenen Texture enthält.

Anmerkung: Der Adressier Modus ist bei dieser Funktion nicht änderbar und hat fest den Wert AddressMode::clampBorder.

Für das Laden von Bildern aus Dateien existieren die Funktionen

Image loadImage(std::string const& filepath);

und

HDRImage loadHDRImage(std::string const& filepath, 
                        bool doGammaCorrection);
  • filepath ist jeweils der Pfad zur Bilddatei im Dateisystem
  • doGammaCorrection gibt an, ob beim Laden von HDR Bildern die Werte von linear zu sRGB konvertiert werden sollen

Rückgabewert ist ein Image struct oder HDRImage struct, welche wie folgt aufgebaut sind:

struct Image{
    uint32_t width, height;
    uint32_t components;
    std::vector<uint8_t> data;
};
struct HDRImage{
    uint32_t width, height;
    uint32_t components;
    std::vector<float> data;
};
  • width ist jeweils die Breite des Bildes
  • height ist jeweils die Höhe des Bildes
  • components ist jeweils die Anzahl der Pixel Komponenten
  • data ist ein Array, welches die Bilddaten als Bytes bzw. als Floats enthält

Für das Abspeichern eines Bildes existieren die beiden Funktionen

void writeHDR(std::string const& filepath, 
                uint32_t width, 
                uint32_t height, 
                tga::Format format 
                std::vector<float> const& data);

und

void writePNG(std::string const& filepath, 
                uint32_t width, 
                uint32_t height, 
                tga::Format format 
                std::vector<uint8_t> const& data);
  • filepath ist jeweils der Pfad im Dateisystem, wohin die Datei abgespeichert werden soll
  • width gibt jeweils die Breite des Bildes in Pixel an
  • height gibt jeweils die Höhe des Bildes in Pixel an
  • format ist ein Wert des tga::Format enums und gibt jeweils den Aufbau der Pixel an
  • data ist jeweils ein Array, welches die zu speichernden Bilddaten enthält

Für das Laden und Speichern von Bilddaten wurde die Open Source MIT Bibliothek stb eingebunden, da diese eine breite Unterstützung für gängige Bildformate sowie dem Radiance HDR Format bietet.

Für das Laden einer Wavefront OBJ Datei existiert die Funktion:

Obj loadObj(std::string const& filepath);
  • filepath ist der Pfad zur OBJ Datei im Dateisystem

Rückgabewert ist ein Obj struct, welches wie folgt aufgebaut ist:

struct Obj{
    std::vector<tga::Vertex> vertexBuffer;
    std::vector<uint32_t> indexBuffer;
};
  • vertexBuffer enthält die Vertex Daten in Form der tga::Vertex struct
  • indexBuffer enthält die Daten des Index Buffers

Die tga::Vertex struct ist wie folgt aufgebaut:

struct Vertex{
    glm::vec3 position;
    glm::vec2 uv;
    glm::vec3 normal;
    glm::vec3 tangent;
    static tga::VertexLayout layout();
};
  • position enthält die Position im 3D dimensionalen Object Space
  • uv enthält 2D Texturkoordinaten
  • normal enthält den Normalenvektor des Vertex
  • tangent enthält den Tangentenvektor des Vertex
  • layout() gibt die für die RenderPass Erstellung benötigte tga::VertexLayout struct aus

Für das Laden von OBJ Dateien wurde die Open Source MIT Bibliothek tinyobjloader, da diese leicht integrierbar ist und keine weiteren Abhängigkeiten besitzt.

Da in der TGA oft ein Pointer auf Bytes verlangt wird, existiert, um Pointer Casting zu vermeiden, die Template Funktion

uint8_t* memoryAccess(T &value);

die für ein beliebiges Objekt den Pointer auf den darunter liegenden Speicher zurück gibt.

Da ein std::vector seine Daten auf dem Heap verwaltet, braucht dieser eine besondere Behandlung beim Zugriff auf den Speicher. Dafür existiert die spezialisierte Template Funktion

uint8_t* memoryAccess(std::vector<T> &value);

die den Pointer auf die von einem Vektor verwalteten Daten zurück gibt.