Das numerische Format von Grafikdaten und Shader-Berechnungen kann erhebliche Auswirkungen auf die Leistung deines Spiels haben.
Optimale Formate zeichnen sich durch Folgendes aus:
- Effizienz der GPU-Cache-Nutzung erhöhen
- Verbrauch der Speicherbandbreite reduzieren, um Energie zu sparen und die Leistung zu steigern
- Maximieren des Rechendurchsatzes in Shader-Programmen
- CPU-RAM-Nutzung Ihres Spiels minimieren
Gleitkommaformate
Die meisten Berechnungen und Daten in modernen 3D-Grafiken verwenden Gleitkommazahlen. Vulkan unter Android verwendet Gleitkommazahlen mit einer Größe von 32 oder 16 Bit. Eine 32-Bit-Gleitkommazahl wird üblicherweise als einfache Genauigkeit oder volle Genauigkeit bezeichnet, eine 16-Bit-Gleitkommazahl als halbe Genauigkeit.
Vulkan definiert einen 64-Bit-Gleitkommatyp, der jedoch von Vulkan-Geräten unter Android nicht häufig unterstützt wird. Seine Verwendung wird nicht empfohlen. Eine 64-Bit-Gleitkommazahl wird im Allgemeinen als Gleitkommazahl mit doppelter Genauigkeit bezeichnet.
Ganzzahlformate
Vorzeichenbehaftete und vorzeichenlose Ganzzahlen werden auch für Daten und Berechnungen verwendet. Die Standardgröße für Ganzzahlen beträgt 32 Bit. Die Unterstützung anderer Bitgrößen ist geräteabhängig. Vulkan-Geräte mit Android unterstützen in der Regel 16-Bit- und 8-Bit-Ganzzahlen. Vulkan definiert einen 64-Bit-Ganzzahltyp, der jedoch von Vulkan-Geräten unter Android nicht häufig unterstützt wird. Seine Verwendung wird nicht empfohlen.
Suboptimales Verhalten bei halber Genauigkeit
Moderne GPU-Architekturen kombinieren zwei 16-Bit-Werte in einem 32-Bit-Paar und implementieren Anweisungen, die auf dem Paar ausgeführt werden. Vermeiden Sie für eine optimale Leistung die Verwendung von skalaren 16-Bit-Gleitkommavariablen. Vektorisieren Sie Daten in Vektoren mit zwei oder vier Elementen. Der Shader-Compiler kann möglicherweise skalare Werte in Vektorvorgängen verwenden. Wenn Sie sich jedoch darauf verlassen, dass der Compiler Skalare optimiert, prüfen Sie die Compilerausgabe, um die Vektorisierung zu bestätigen.
Die Konvertierung von und in 32-Bit- und 16-Bit-Gleitkommazahlen ist mit Rechenaufwand verbunden. Reduzieren Sie den Aufwand, indem Sie die Anzahl der Genauigkeitskonvertierungen in Ihrem Code minimieren.
Vergleichen Sie die Leistungsunterschiede zwischen 16-Bit- und 32-Bit-Versionen Ihrer Algorithmen. Die halbe Genauigkeit führt nicht immer zu einer Leistungsverbesserung, insbesondere bei komplizierten Berechnungen. Algorithmen, die stark auf FMA-Anweisungen (Fused Multiply-Add) für vektorisierte Daten zurückgreifen, sind gute Kandidaten für eine verbesserte Leistung bei halber Genauigkeit.
Unterstützung für numerische Formate
Alle Vulkan-Geräte unter Android unterstützen 32-Bit-Gleitkommazahlen mit einfacher Genauigkeit und 32-Bit-Ganzzahlen in Daten- und Shader-Berechnungen. Die Unterstützung anderer Formate ist nicht garantiert und, falls verfügbar, nicht für alle Anwendungsfälle.
Vulkan unterstützt zwei Kategorien für optionale numerische Formate: Arithmetik und Speicher. Bevor du ein bestimmtes Format verwendest, solltest du prüfen, ob es von einem Gerät in beiden Kategorien unterstützt wird.
Arithmetische Operatoren
Ein Vulkan-Gerät muss die arithmetische Unterstützung für ein numerisches Format deklarieren, damit es in Shader-Programmen verwendet werden kann. Vulkan-Geräte unter Android unterstützen in der Regel die folgenden Formate für die Arithmetik:
- 32-Bit-Ganzzahl (erforderlich)
- 32-Bit-Gleitkomma (erforderlich)
- 8-Bit-Ganzzahl (optional)
- 16-Bit-Ganzzahl (optional)
- 16-Bit-Gleitkommazahl mit halber Genauigkeit (optional)
Wenn du herausfinden möchtest, ob ein Vulkan-Gerät 16-Bit-Ganzzahlen für die Arithmetik unterstützt, rufe die Gerätefunktionen mit der Funktion vkGetPhysicalDeviceFeatures2() ab und prüfe, ob das Feld shaderInt16 in der Ergebnisstruktur VkPhysicalDeviceFeatures2 „true“ ist.
So ermitteln Sie, ob ein Vulkan-Gerät 16-Bit-Gleitkommazahlen oder 8-Bit-Ganzzahlen unterstützt:
- Prüfe, ob das Gerät die Vulkan-Erweiterung VK_KHR_shader_float16_int8 unterstützt. Die Erweiterung ist für die Unterstützung von 16-Bit-Gleitkommazahlen und 8-Bit-Ganzzahlen erforderlich.
- Wenn
VK_KHR_shader_float16_int8unterstützt wird, hänge einen VkPhysicalDeviceShaderFloat16Int8Features-Strukturzeiger an eineVkPhysicalDeviceFeatures2.pNext-Kette an. - Prüfen Sie die Felder
shaderFloat16undshaderInt8der ErgebnisstrukturVkPhysicalDeviceShaderFloat16Int8Features, nachdem SievkGetPhysicalDeviceFeatures2()aufgerufen haben. Wenn der Feldwerttrueist, wird das Format für die Arithmetik des Shader-Programms unterstützt.
Die Unterstützung der Erweiterung VK_KHR_shader_float16_int8 ist zwar keine Anforderung in Vulkan 1.1 oder im Android-Baseline-Profil von 2022, aber auf Android-Geräten sehr verbreitet.
Speicherunterstützung
Ein Vulkan-Gerät muss die Unterstützung für ein optionales numerisches Format für bestimmte Speichertypen deklarieren. Die Erweiterung VK_KHR_16bit_storage deklariert die Unterstützung für 16-Bit-Ganzzahl- und 16-Bit-Gleitkommaformate. Die Erweiterung definiert vier Speichertypen. Ein Gerät kann 16-Bit-Zahlen für keinen, einige oder alle Speichertypen unterstützen.
Die Speichertypen sind:
- Speicherpufferobjekte
- Einheitliche Zwischenspeicherobjekte
- Push-Konstantenblöcke
- Shader-Eingabe- und -Ausgabeschnittstellen
Die meisten, aber nicht alle Vulkan 1.1-Geräte unter Android unterstützen 16-Bit-Formate in Speicherpufferobjekten. Gehen Sie nicht davon aus, dass ein GPU-Modell unterstützt wird. Geräte mit älteren Treibern für eine bestimmte GPU unterstützen möglicherweise keine Speicherpufferobjekte, Geräte mit neueren Treibern dagegen schon.
Die Unterstützung von 16-Bit-Formaten in einheitlichen Puffern, Push-Konstantenblöcken und Shader-Eingabe-/Ausgabeschnittstellen hängt im Allgemeinen vom GPU-Hersteller ab. Unter Android unterstützt eine GPU in der Regel entweder alle drei Typen oder keinen davon.
Beispielfunktion, die die Unterstützung von Vulkan-Arithmetik und ‑Speicherformaten testet:
struct ReducedPrecisionSupportInfo {
// Arithmetic support
bool has_8_bit_int_ = false;
bool has_16_bit_int_ = false;
bool has_16_bit_float_ = false;
// Storage support
bool has_16_bit_SSBO_ = false;
bool has_16_bit_UBO_ = false;
bool has_16_bit_push_ = false;
bool has_16_bit_input_output_ = false;
// Use 16-bit floats if we have arithmetic
// support and at least SSBO storage support.
bool use_16bit_floats_ = false;
};
void CheckFormatSupport(VkPhysicalDevice physical_device,
ReducedPrecisionSupportInfo &info) {
// Retrieve the device extension list so we
// can check for our desired extensions.
uint32_t device_extension_count;
vkEnumerateDeviceExtensionProperties(physical_device, nullptr,
&device_extension_count, nullptr);
std::vector<VkExtensionProperties> device_extensions(device_extension_count);
vkEnumerateDeviceExtensionProperties(physical_device, nullptr,
&device_extension_count, device_extensions.data());
bool has_16_8_extension = HasDeviceExtension("VK_KHR_shader_float16_int8",
device_extensions);
// Initialize the device features structure and
// chain the storage features structure and 8/16-bit
// support structure if applicable.
VkPhysicalDeviceFeatures2 device_features;
memset(&device_features, 0, sizeof(device_features));
device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
VkPhysicalDeviceShaderFloat16Int8Features f16_int8_features;
memset(&f16_int8_features, 0, sizeof(f16_int8_features));
f16_int8_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
VkPhysicalDevice16BitStorageFeatures storage_features;
memset(&storage_features, 0, sizeof(storage_features));
storage_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
device_features.pNext = &storage_features;
if (has_16_8_extension) {
storage_features.pNext = &f16_int8_features;
}
vkGetPhysicalDeviceFeatures2(physical_device, &device_features);
// Parse the storage features and determine
// what kinds of 16-bit storage access are available.
if (storage_features.storageBuffer16BitAccess ||
storage_features.uniformAndStorageBuffer16BitAccess) {
info.has_16_bit_SSBO_ = true;
}
info.has_16_bit_UBO_ = storage_features.uniformAndStorageBuffer16BitAccess;
info.has_16_bit_push_ = storage_features.storagePushConstant16;
info.has_16_bit_input_output_ = storage_features.storageInputOutput16;
info.has_16_bit_int_ = device_features.features.shaderInt16;
if (has_16_8_extension) {
info.has_16_bit_float_ = f16_int8_features.shaderFloat16;
info.has_8_bit_int_ = f16_int8_features.shaderInt8;
}
// Get arithmetic and at least some form of storage
// support before enabling 16-bit float usage.
if (info.has_16_bit_float_ && info.has_16_bit_SSBO_) {
info.use_16bit_floats_ = true;
}
}
Genauigkeitsgrad für Daten
Eine Gleitkommazahl mit halber Genauigkeit kann einen kleineren Wertebereich mit einer geringeren Genauigkeit als eine Gleitkommazahl mit einfacher Genauigkeit darstellen. Die halbe Genauigkeit ist oft eine einfache und wahrnehmungsfreie Wahl gegenüber der einfachen Genauigkeit. Die halbe Genauigkeit ist jedoch möglicherweise nicht in allen Anwendungsfällen praktikabel. Bei einigen Datentypen können der reduzierte Bereich und die geringere Genauigkeit zu grafischen Artefakten oder fehlerhaftem Rendering führen.
Datentypen, die sich gut für die Darstellung in Gleitkommazahlen mit halber Genauigkeit eignen, sind:
- Positionsdaten in lokalen Raumkoordinaten
- Textur-UVs für kleinere Texturen mit begrenztem UV-Wrapping, die auf einen Koordinatenbereich von -1,0 bis 1,0 beschränkt werden können
- Normalen-, Tangenten- und Bitangentendaten
- Vertex-Farbdaten
- Daten mit geringen Genauigkeitsanforderungen, die um 0,0 liegen
Datentypen, die nicht für die Darstellung in Gleitkommazahlen mit halber Genauigkeit empfohlen werden:
- Positionsdaten in globalen Weltkoordinaten
- Textur-UVs für hochpräzise Anwendungsfälle wie Koordinaten von UI-Elementen in einem Atlas-Sheet
Genauigkeit im Shader-Code
Die Shader-Programmiersprachen OpenGL Shading Language (GLSL) und High-level Shader Language (HLSL) unterstützen die Angabe einer reduzierten oder expliziten Genauigkeit für numerische Typen. Die reduzierte Genauigkeit wird als Empfehlung für den Shader-Compiler behandelt. Die explizite Genauigkeit ist eine Anforderung der angegebenen Genauigkeit. Vulkan-Geräte unter Android verwenden im Allgemeinen 16-Bit-Formate, wenn dies durch eine reduzierte Genauigkeit vorgeschlagen wird. Andere Vulkan-Geräte, insbesondere Computer mit Grafikhardware, die keine 16-Bit-Formate unterstützt, ignorieren möglicherweise die reduzierte Genauigkeit und verwenden weiterhin 32-Bit-Formate.
Speichererweiterungen in GLSL
Die entsprechenden GLSL-Erweiterungen müssen definiert werden, um die Unterstützung für numerische 16-Bit- oder 8-Bit-Formate in Speicher- und einheitlichen Zwischenspeicherstrukturen zu aktivieren. Die relevanten Erweiterungsdeklarationen sind:
// Enable 16-bit formats in storage and uniform buffers.
#extension GL_EXT_shader_16bit_storage : require
// Enable 8-bit formats in storage and uniform buffers.
#extension GL_EXT_shader_8bit_storage : require
Diese Erweiterungen sind spezifisch für GLSL und haben kein Äquivalent in HLSL.
Relaxed Precision in GLSL
Verwenden Sie den Qualifizierer highp vor einem Gleitkommatyp, um einen Gleitkommawert mit einfacher Genauigkeit vorzuschlagen, und den Qualifizierer mediump für einen Gleitkommawert mit halber Genauigkeit.
GLSL-Compiler für Vulkan interpretieren den Legacy-Qualifizierer lowp als mediump.
Hier einige Beispiele für eine reduzierte Genauigkeit:
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
Explizite Genauigkeit in GLSL
Fügen Sie die Erweiterung GL_EXT_shader_explicit_arithmetic_types_float16 in Ihren GLSL-Code ein, um die Verwendung von 16-Bit-Gleitkommatypen zu ermöglichen:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
Deklarieren Sie 16-Bit-Gleitkomma-Skalar-, Vektor- und Matrixtypen in GLSL mit den folgenden Schlüsselwörtern:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
Deklarieren Sie 16-Bit-Ganzzahl-Skalar- und -Vektortypen in GLSL mit den folgenden Schlüsselwörtern:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
Relaxed Precision in HLSL
In HLSL wird der Begriff minimale Genauigkeit anstelle von reduzierter Genauigkeit verwendet. Ein Keyword für den minimalen Genauigkeitstyp gibt die Mindestgenauigkeit an. Der Compiler kann jedoch eine höhere Genauigkeit verwenden, wenn diese für die Zielhardware besser geeignet ist. Ein 16-Bit-Gleitkommawert mit minimaler Genauigkeit wird durch das Schlüsselwort min16float angegeben. Vorzeichenbehaftete und vorzeichenlose 16-Bit-Ganzzahlen mit minimaler Genauigkeit werden durch die Keywords min16int bzw. min16uint angegeben. Weitere Beispiele für Deklarationen mit minimaler Genauigkeit:
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
Explizite Genauigkeit in HLSL
Gleitkommazahlen mit halber Genauigkeit werden durch die Schlüsselwörter half oder float16_t angegeben. Vorzeichenbehaftete und vorzeichenlose 16-Bit-Ganzzahlen werden durch die Keywords int16_t bzw. uint16_t angegeben. Weitere Beispiele für explizite Genauigkeitsdeklarationen:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;