Il formato numerico dei dati grafici e dei calcoli degli shader può avere un impatto significativo sulle prestazioni del gioco.
I formati ottimali:
- Aumentare l'efficienza dell'utilizzo della cache della GPU
- Riduci il consumo di larghezza di banda della memoria, risparmia energia e aumenta le prestazioni
- Massimizzare la velocità di calcolo nei programmi shader
- Riduci al minimo l'utilizzo della RAM della CPU del tuo gioco
Formati a virgola mobile
La maggior parte dei calcoli e dei dati nella grafica 3D moderna utilizza numeri con virgola mobile. Vulkan su Android utilizza numeri in virgola mobile di dimensioni pari a 32 o 16 bit. Un numero a virgola mobile a 32 bit viene comunemente definito a precisione singola o a precisione completa; un numero a virgola mobile a 16 bit, a precisione intermedia.
Vulkan definisce un tipo a virgola mobile a 64 bit, ma questo tipo non è supportato comunemente dai dispositivi Vulkan su Android e il suo utilizzo non è consigliato. Un numero con rappresentazione in virgola mobile di 64 bit viene comunemente definito a precisione doppia.
Formati interi
I numeri interi con segno e senza segno vengono utilizzati anche per i dati e i calcoli. La dimensione dell'intero standard è di 32 bit. Il supporto di altre dimensioni in bit dipende dal dispositivo. I dispositivi Vulkan con Android supportano in genere interi di 16 e 8 bit. Vulkan definisce un tipo di numero intero a 64 bit, ma questo tipo non è supportato comunemente dai dispositivi Vulkan su Android e il suo utilizzo non è consigliato.
Comportamento non ottimale a precisione dimezzata
Le architetture GPU moderne combinano due valori a 16 bit in una coppia a 32 bit e implementano istruzioni che operano sulla coppia. Per prestazioni ottimali, evita di utilizzare variabili scalari con numeri in virgola mobile a 16 bit; vettorizza i dati in vettori di due o quattro elementi. Il compilatore di shader potrebbe essere in grado di utilizzare valori scalari nelle operazioni vettoriali. Tuttavia, se ti affidi al compilatore per ottimizzare gli scalari, controlla l'output del compilatore per verificare la vettorializzazione.
La conversione da e verso la rappresentazione in virgola mobile a precisione di 32 e 16 bit ha un costo computazionale. Riduci l'overhead riducendo al minimo le conversioni di precisione nel codice.
Esegui un benchmark delle differenze di rendimento tra le versioni a 16 e 32 bit dei tuoi algoritmi. La precisione dimezzata non sempre comporta un miglioramento delle prestazioni, soprattutto per i calcoli complicati. Gli algoritmi che fanno un uso intensivo di istruzioni di moltiplicazione e addizione fuse (FMA) sui dati vettorizzati sono buoni candidati per un miglioramento del rendimento a metà precisione.
Supporto del formato numerico
Tutti i dispositivi Vulkan su Android supportano numeri a virgola mobile a precisione singola di 32 bit e numeri interi di 32 bit nei calcoli di dati e shader. Non è garantito il supporto di altri formati e, se disponibile, non è garantito per tutti i casi d'uso.
Vulkan supporta due categorie di formati numerici facoltativi: operazioni aritmetiche e archiviazione. Prima di utilizzare un formato specifico, assicurati che un dispositivo lo supporti in entrambe le categorie.
Supporto aritmetico
Un dispositivo Vulkan deve dichiarare il supporto aritmetico per un formato numerico affinché possa essere utilizzato nei programmi shader. I dispositivi Vulkan su Android supportano in genere i seguenti formati per l'aritmetica:
- Numero intero a 32 bit (obbligatorio)
- Virgola mobile a 32 bit (obbligatorio)
- Numero intero a 8 bit (facoltativo)
- Intero a 16 bit (facoltativo)
- Virgola mobile a precisione dimezzata a 16 bit (facoltativo)
Per determinare se un dispositivo Vulkan supporta gli interi a 16 bit per l'aritmetica, recupera le funzionalità del dispositivo chiamando la funzione vkGetPhysicalDeviceFeatures2() e controlla se il campo shaderInt16
nella struttura del risultato VkPhysicalDeviceFeatures2 è true.
Per determinare se un dispositivo Vulkan supporta numeri in virgola mobile a 16 bit o numeri interi a 8 bit, segui questi passaggi:
- Verifica se il dispositivo supporta l'estensione Vulkan VK_KHR_shader_float16_int8. L'estensione è obbligatoria per il supporto di numeri in virgola mobile a 16 bit e interi a 8 bit.
- Se
VK_KHR_shader_float16_int8
è supportato, aggiungi un puntatore alla struttura VkPhysicalDeviceShaderFloat16Int8Features a una catenaVkPhysicalDeviceFeatures2.pNext
. - Controlla i campi
shaderFloat16
eshaderInt8
della struttura del risultatoVkPhysicalDeviceShaderFloat16Int8Features
dopo aver chiamatovkGetPhysicalDeviceFeatures2()
. Se il valore del campo ètrue
, il formato è supportato per l'aritmetica del programma shader.
Sebbene non sia un requisito in Vulkan 1.1 o nel profilo di riferimento Android 2022, il supporto dell'estensione VK_KHR_shader_float16_int8
è molto comune sui dispositivi Android.
Supporto per lo spazio di archiviazione
Un dispositivo Vulkan deve dichiarare il supporto di un formato numerico facoltativo per tipi di archiviazione specifici. L'estensione VK_KHR_16bit_storage dichiara il supporto per i formati interi a 16 bit e a virgola mobile a 16 bit. L'estensione definisce quattro tipi di archiviazione. Un dispositivo può supportare numeri a 16 bit per nessuno, alcuni o tutti i tipi di archiviazione.
I tipi di archiviazione sono:
- Oggetti buffer di archiviazione
- Oggetti buffer uniformi
- Blocchi di push costanti
- Interfacce di input e output degli shader
La maggior parte, ma non tutti, i dispositivi Vulkan 1.1 su Android supportano i formati a 16 bit negli oggetti buffer di archiviazione. Non dare per scontato il supporto in base al modello di GPU. I dispositivi con driver meno recenti per una determinata GPU potrebbero non supportare gli oggetti buffer di archiviazione, mentre quelli con driver più recenti sì.
Il supporto dei formati a 16 bit nei buffer uniformi, nei blocchi di costanti push e nelle interfacce di input/output shader dipende in genere dal produttore della GPU. Su Android, una GPU in genere supporta tutti e tre questi tipi o nessuno.
Una funzione di esempio che verifica il supporto dell'aritmetica e dei formati di archiviazione 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;
}
}
Livello di precisione per i dati
Un numero con virgola mobile a precisione dimezzata può rappresentare un intervallo di valori più piccolo con una precisione inferiore rispetto a un numero con virgola mobile a precisione singola. La precisione dimezzata è spesso una scelta semplice e senza perdita di percezione rispetto alla precisione singola. Tuttavia, la precisione a metà potrebbe non essere praticabile in tutti i casi d'uso. Per alcuni tipi di dati, l'intervallo e la precisione ridotti possono comportare artefatti grafici o un rendering errato.
I tipi di dati che sono buoni candidati per la rappresentazione in virgola mobile con metà precisione includono:
- Posiziona i dati nelle coordinate spaziali locali
- UV delle texture per texture più piccole con wrapping UV limitato che può essere vincolato a un intervallo di coordinate compreso tra -1,0 e 1,0
- Dati normali, tangenti e bitangenti
- Dati sui colori dei vertici
- Dati con requisiti di bassa precisione centrati su 0,0
I tipi di dati non consigliati per la rappresentazione in numeri in virgola mobile a metà precisione includono:
- Posiziona i dati in coordinate geografiche globali
- UV delle texture per casi d'uso ad alta precisione, come le coordinate degli elementi dell'interfaccia utente in un foglio dell'atlante
Precisione nel codice shader
I linguaggi di programmazione shader OpenGL Shading Language (GLSL) e High-level Shader Language (HLSL) supportano la specifica di precisione rilassata o precisione esplicita per i tipi numerici. La precisione rilassata viene trattata come un consiglio per il compilatore di shader. La precisione esplicita è un requisito della precisione specificata. I dispositivi Vulkan su Android in genere utilizzano formati di 16 bit se suggeriti dalla precisione rilassata. Altri dispositivi Vulkan, soprattutto su computer desktop che utilizzano hardware grafico senza supporto per i formati a 16 bit, potrebbero ignorare la precisione rilassata e continuare a utilizzare i formati a 32 bit.
Estensioni di archiviazione in GLSL
Devono essere definite le estensioni GLSL appropriate per abilitare il supporto dei formati numerici di 16 o 8 bit nelle strutture di archiviazione e degli buffer uniformi. Le dichiarazioni di estensione pertinenti sono:
// 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
Queste estensioni sono specifiche per GLSL e non hanno un equivalente in HLSL.
Precisione rilassata in GLSL
Utilizza il qualificatore highp
prima di un tipo a virgola mobile per suggerire un valore float a precisione singola e il qualificatore mediump
per un valore float a metà precisione.
I compilatori GLSL per Vulkan interpretano il qualificatore lowp
precedente come mediump
.
Alcuni esempi di precisione ridotta:
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
Precisione esplicita in GLSL
Includi l'estensione GL_EXT_shader_explicit_arithmetic_types_float16
nel codice GLSL per abilitare l'utilizzo di tipi a virgola mobile a 16 bit:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
Dichiara i tipi scalari, vettori e matrici a virgola mobile a 16 bit in GLSL utilizzando le seguenti parole chiave:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
Dichiara i tipi scalari e vettoriali di numeri interi a 16 bit in GLSL utilizzando le seguenti chiavi di accesso:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
Precisione ridotta in HLSL
HLSL utilizza il termine precisione minima anziché precisione rilassata. Una parola chiave di tipo di precisione minima specifica la precisione minima, ma il compilatore può sostituire una precisione superiore se questa è una scelta migliore per l'hardware di destinazione. La parola chiave min16float
specifica un numero in virgola mobile a 16 bit con precisione minima. Gli interi a 16 bit con precisione minima firmati e non firmati sono
specificati rispettivamente dalle parole chiave min16int
e min16uint
. Altri
esempi di dichiarazioni di precisione minima includono:
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
Precisione esplicita in HLSL
Il numero in virgola mobile a metà precisione è specificato dalle parole chiave half
o float16_t
. Gli interi a 16 bit con segno e senza segno sono specificati rispettivamente dalle parole chiave int16_t
e uint16_t
. Altri esempi di dichiarazioni di precisione esplicita includono:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;
-enable-16bit-types