Ottimizza con precisione ridotta

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
  • Riduzione del consumo di larghezza di banda della memoria, risparmio energetico e aumento delle 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 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 di mezza precisione non ottimale

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 ombreggiato può 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 comporta sempre 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 prevede due categorie di supporto per i formati numerici facoltativi: aritmetica 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)
  • (Facoltativo) Virgola in virgola mobile a mezza precisione a 16 bit

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:

  1. 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.
  2. Se VK_KHR_shader_float16_int8 è supportato, aggiungi un puntatore alla struttura VkPhysicalDeviceShaderFloat16Int8Features a una catena VkPhysicalDeviceFeatures2.pNext.
  3. Controlla i campi shaderFloat16 e shaderInt8 della struttura del risultato VkPhysicalDeviceShaderFloat16Int8Features dopo aver chiamato vkGetPhysicalDeviceFeatures2(). 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 Android Baseline del 2022, il supporto per l'estensione VK_KHR_shader_float16_int8 è molto comune sui dispositivi Android.

Supporto dello 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 dei dispositivi Vulkan 1.1 su Android, ma non tutti, supporta 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 oggetti buffer di archiviazione, a differenza dei dispositivi con driver più recenti.

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 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 bitangent
  • Dati colore Vertex
  • 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 texture per casi d'uso ad alta precisione, come le coordinate degli elementi UI in un foglio 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 consentire l'utilizzo dei tipi in 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