Das numerische Format von Grafikdaten und Shader-Berechnungen kann sich erheblich auf die Leistung Ihres Spiels auswirken.
Optimale Formate bieten folgende Vorteile:
- Effizienz der GPU-Cache-Nutzung erhöhen
- Speicherbandbreitenverbrauch reduzieren, wodurch Energie gespart und die Leistung gesteigert wird
- Rechenleistung in Shader-Programmen maximieren
- CPU-RAM-Nutzung Ihres Spiels minimieren
Gleitkommaformate
Bei den meisten Berechnungen und Daten in der modernen 3D-Grafik werden Gleitkommazahlen verwendet. In Vulkan unter Android werden Gleitkommazahlen mit einer Größe von 32 oder 16 Bit verwendet. 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 allgemein unterstützt wird. Die Verwendung dieses Typs wird daher nicht empfohlen. Eine 64-Bit-Gleitkommazahl wird üblicherweise als „doppelte Genauigkeit“ bezeichnet.
Ganzzahlformate
Auch vorzeichenbehaftete und vorzeichenlose Ganzzahlen werden für Daten und Berechnungen verwendet. Die Standardgröße für Ganzzahlen beträgt 32 Bit. Die Unterstützung für andere Bitgrößen ist geräteabhängig. Vulkan-Geräte unter Android unterstützen üblicherweise 16-Bit- und 8-Bit-Ganzzahlen. Vulkan definiert einen 64-Bit-Ganzzahltyp, der jedoch von Vulkan-Geräten unter Android nicht allgemein unterstützt wird. Die Verwendung dieses Typs wird daher nicht empfohlen.
Suboptimales Verhalten bei halber Genauigkeit
Moderne GPU-Architekturen kombinieren zwei 16-Bit-Werte zu einem 32-Bit-Paar und implementieren Anweisungen, die auf das Paar angewendet werden. Um eine optimale Leistung zu erzielen, sollten Sie keine skalaren 16-Bit-Gleitkomma-Variablen verwenden, sondern Daten in Vektoren mit zwei oder vier Elementen vektorisieren. Der Shader-Compiler kann möglicherweise skalare Werte in Vektoroperationen verwenden. Wenn Sie sich jedoch darauf verlassen, dass der Compiler Skalare optimiert, sollten Sie die Compiler-Ausgabe prüfen, um die Vektorisierung zu bestätigen.
Die Konvertierung von und in 32-Bit- und 16-Bit-Gleitkommazahlen ist mit einem Rechenaufwand verbunden. Sie können den Overhead reduzieren, indem Sie die Anzahl der Genauigkeitskonvertierungen in Ihrem Code minimieren.
Führen Sie einen Benchmark durch, um die Leistungsunterschiede zwischen 16-Bit- und 32-Bit-Versionen Ihrer Algorithmen zu ermitteln. Eine halbe Genauigkeit führt nicht immer zu einer Leistungssteigerung, insbesondere bei komplizierten Berechnungen. Algorithmen, die häufig FMA-Anweisungen (Fused Multiply-Add) für vektorisierte Daten verwenden, 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 Verfügbarkeit anderer Formate wird nicht garantiert und ist, falls verfügbar, nicht für alle Anwendungsfälle garantiert.
Vulkan bietet zwei Kategorien der Unterstützung für optionale numerische Formate: Arithmetik und Speicher. Bevor Sie ein bestimmtes Format verwenden, sollten Sie prüfen, ob es von einem Gerät in beiden Kategorien unterstützt wird.
Arithmetische Unterstützung
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 üblicherweise die folgenden Formate für die Arithmetik:
- 32-Bit-Ganzzahl (erforderlich)
- 32-Bit-Gleitkommazahl (erforderlich)
- 8-Bit-Ganzzahl (optional)
- 16-Bit-Ganzzahl (optional)
- 16-Bit-Gleitkommazahl mit halber Genauigkeit (optional)
Wenn Sie prüfen möchten, ob ein Vulkan-Gerät 16-Bit-Ganzzahlen für die Arithmetik unterstützt,
rufen Sie die Funktionen des Geräts mit der Funktion
vkGetPhysicalDeviceFeatures2() ab und prüfen Sie, ob
das shaderInt16 Feld in der VkPhysicalDeviceFeatures2
Ergebnisstruktur „true“ ist.
So prüfen Sie, ob ein Vulkan-Gerät 16-Bit-Gleitkommazahlen oder 8-Bit-Ganzzahlen unterstützt:
- Prüfen Sie, 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ängen Sie einen Zeiger auf die Struktur VkPhysicalDeviceShaderFloat16Int8Features 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 von Shader-Programmen unterstützt.
Obwohl die Unterstützung für die VK_KHR_shader_float16_int8
Erweiterung in Vulkan 1.1 oder im 2022
Android-Baseline-Profil nicht erforderlich ist, wird sie auf Android-Geräten sehr häufig unterstützt.
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 Pufferobjekte
- Push-Konstantenblöcke
- Shader-Ein- 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 die Unterstützung auf dem GPU-Modell basiert. Geräte mit älteren Treibern für eine bestimmte GPU unterstützen möglicherweise keine Speicherpufferobjekte, während Geräte mit neueren Treibern dies tun.
Die Unterstützung für 16-Bit-Formate in einheitlichen Puffern, Push-Konstantenblöcken und Shader-Ein-/Ausgabeschnittstellen hängt im Allgemeinen vom GPU-Hersteller ab. Unter Android unterstützt eine GPU in der Regel entweder alle drei Typen oder keinen von ihnen.
Beispielfunktion zum Testen der Unterstützung für arithmetische und Speicherformate in Vulkan:
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 geringerer Genauigkeit darstellen als eine Gleitkommazahl mit einfacher Genauigkeit. Die halbe Genauigkeit ist oft eine einfache und wahrnehmungslose Wahl gegenüber der einfachen Genauigkeit. Die halbe Genauigkeit ist jedoch nicht in allen Anwendungsfällen praktikabel. Bei einigen Datentypen können der reduzierte Bereich und die geringere Genauigkeit zu Grafikartefakten oder einer falschen Darstellung 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
- Normale, Tangenten- und Bitangentendaten
- Vertex-Farbdaten
- Daten mit geringen Genauigkeitsanforderungen, die auf 0,0 zentriert sind
Datentypen, die nicht für die Darstellung in Gleitkommazahlen mit halber Genauigkeit empfohlen werden, sind:
- Positionsdaten in globalen Weltkoordinaten
- Textur-UVs für Anwendungsfälle mit hoher Genauigkeit wie UI-Elementkoordinaten 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 an die angegebene Genauigkeit. Vulkan-Geräte unter Android verwenden in der Regel 16-Bit-Formate, wenn eine reduzierte Genauigkeit vorgeschlagen wird. Andere Vulkan-Geräte, insbesondere Desktop-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 16-Bit- oder 8-Bit-Zahlenformate in Speicher- und einheitlichen Pufferstrukturen 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.
Reduzierte Genauigkeit in GLSL
Verwenden Sie den Qualifizierer highp vor einem Gleitkommatyp, um eine Gleitkommazahl mit einfacher Genauigkeit vorzuschlagen, und den Qualifizierer mediump für eine Gleitkommazahl mit halber Genauigkeit.
GLSL-Compiler für Vulkan interpretieren den Legacy-Qualifizierer lowp als mediump.
Einige Beispiele für 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 aktivieren:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
Deklarieren Sie skalare, Vektor- und Matrixtypen für 16-Bit-Gleitkommazahlen in GLSL mit den folgenden Keywords:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
Deklarieren Sie skalare und Vektortypen für 16-Bit-Ganzzahlen in GLSL mit den folgenden Keywords:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
Reduzierte Genauigkeit in HLSL
In HLSL wird der Begriff minimale Genauigkeit anstelle von reduzierter Genauigkeit verwendet. Ein Keyword für den Typ mit minimaler Genauigkeit gibt die Mindestgenauigkeit an. Der Compiler kann jedoch eine höhere Genauigkeit verwenden, wenn dies für die Zielhardware besser geeignet ist. Eine 16-Bit-Gleitkommazahl mit minimaler Genauigkeit wird mit dem Keyword min16float angegeben. Vorzeichenbehaftete und vorzeichenlose 16-Bit-Ganzzahlen mit minimaler Genauigkeit werden mit den 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 mit den Keywords half oder float16_t angegeben. Vorzeichenbehaftete und vorzeichenlose 16-Bit-Ganzzahlen werden mit den int16_t
bzw. uint16_t Keywords angegeben. Weitere Beispiele für Deklarationen mit expliziter Genauigkeit:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;
-enable-16bit-types
verwendet werden, um die Unterstützung für 16-Bit-Zahlenformate zu aktivieren.