無論是圖像資料的數值格式,還是著色器的運算方式,都可能會對遊戲效能產生重大影響。
最佳化格式具有以下特點:
- 提高 GPU 快取使用效率
- 降低記憶體頻寬用量,不僅省電還可提升效能
- 盡可能增加著色器程式中的運算處理量
- 盡可能減少遊戲的 CPU RAM 使用率
浮點格式
現代 3D 圖像中的運算和資料多半都會採用浮點數。Android 上的 Vulkan 可使用大小為 32 或 16 位元的浮點數。32 位元浮點數通稱為單精度或完整精確度浮點數;16 位元浮點數則稱半精度浮點數。
Vulkan 定義了 64 位元浮點類型,但 Android 上的 Vulkan 裝置一般不支援該類型,因此不建議使用。64 位元浮點數通常稱為雙精度浮點數。
整數格式
整數 (不論是否帶正負號) 也可用於資料和運算。標準整數大小為 32 位元。其他位元大小的支援情況則因裝置而異。搭載 Android 的 Vulkan 裝置通常支援 16 位元和 8 位元整數。Vulkan 定義了 64 位元整數類型,但 Android 上的 Vulkan 裝置一般不支援該類型,因此不建議使用。
非理想的半精度行為
新型 GPU 架構會將兩個 16 位元值合成 32 位元組合,並實作在該組合上執行的指令。為獲得最佳效能,請避免使用純量 16 位元浮點變數;而是要將資料向量化,做為二元素或四元素向量。著色器編譯器可能會在向量運算中使用純量值。不過,如果您在將純量最佳化時使用編譯器,請檢查編譯器輸出內容,藉此驗證向量。
在 32 位元和 16 位元精度浮點間轉換,會產生運算開銷。如要減少開銷,請盡可能降低程式碼中的精確度轉換次數。
對於 16 位元和 32 位元版本演算法的效能差異,您可以執行基準測試。半精度有時可能無法提升效能,尤其是在複雜運算時更是如此。如想提升半精度效能,最好的方式是採用演算法,對向量化資料大量使用積和熔加運算 (FMA) 指令。
數值格式支援
只要是搭載 Android 的 Vulkan 裝置,都可在資料和著色器的運算作業中,支援單精度、32 位元浮點數和 32 位元整數。但我們不保證能支援其他格式,即使可支援,也不保證適用於所有用途。
選用數值格式時,Vulkan 支援兩種類別:算術和儲存體。在您使用特定格式前,請先確認裝置在這兩個類別中都支援該格式。
算術支援
Vulkan 裝置必須宣告對數值格式的算術支援,才能在著色器程式中使用該格式。Android 上的 Vulkan 裝置一般支援以下算術格式:
- 32 位元整數 (必要)
- 32 位元浮點 (必要)
- 8 位元整數 (選用)
- 16 位元整數 (選用)
- 16 位元半精度浮點 (選用)
如要判斷 Vulkan 裝置是否支援 16 位元整數來進行算術,
呼叫
vkGetPhysicalDeviceFeatures2() 函式並檢查是否
VkPhysicalDeviceFeatures2 中的 shaderInt16
欄位
結果結構為 true
如要判斷 Vulkan 裝置是否支援 16 位元浮點值或 8 位元整數,請執行以下步驟:
- 檢查裝置是否支援 VK_KHR_shader_float16_int8 Vulkan 擴充功能。如要支援 16 位元浮點值和 8 位元整數,就必須用到這項擴充功能。
- 如果支援
VK_KHR_shader_float16_int8
,請將 VkPhysicalDeviceShaderFloat16Int8Features 結構指標附加至VkPhysicalDeviceFeatures2.pNext
鏈結。 - 呼叫
vkGetPhysicalDeviceFeatures2()
後,請檢查VkPhysicalDeviceShaderFloat16Int8Features
結果結構的shaderFloat16
和shaderInt8
欄位。如果欄位值為true
,代表著色器程式算術支援該格式。
雖然在 Vulkan 1.1 或 2022 年版 Android 基準設定檔中不一定要支援 VK_KHR_shader_float16_int8
擴充功能,但這項支援在 Android 裝置上十分常見。
儲存體支援
Vulkan 裝置必須宣告能夠支援特定儲存體類型的選用數值格式。VK_KHR_16bit_storage 擴充功能會宣告支援 16 位元整數和 16 位元浮點格式。這個擴充功能定義了四種儲存體類型。對於無儲存體、部分儲存體,或所有儲存體類型,裝置都可以支援 16 位元數值。
儲存體類型分為:
- 儲存體緩衝區物件
- 統一緩衝區物件
- 推送常數區塊
- 著色器輸入和輸出介面
大多數 (但非所有) 搭載 Android 的 Vulkan 1.1 裝置都可在儲存體緩衝區物件中支援 16 位元格式。請不要根據 GPU 型號假設能否提供支援。對於特定 GPU,搭載舊版驅動程式的裝置可能不支援儲存體緩衝區物件,而搭載新版驅動程式的裝置則可能支援。
至於能否在統一緩衝區、推送常數區塊和著色器輸入/輸出介面中支援 16 位元格式,一般需視 GPU 製造商而定。通常在 Android 上,GPU 若不是可支援這三種類型,就是完全不支援。
以下範例函式可測試能否支援 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;
}
}
資料的精確程度
與單精度浮點數相比,半精度浮點數能以較低的精確度,代表較小範圍的值。相較於單精度,半精度通常是簡單且明顯的無損選擇。然而,半精度實際上可能不適用於所有用途。對於某類資料縮小範圍和精確度,可能會導致不自然痕跡或算繪出錯。
適合以半精度浮點表示的資料類型包括:
- 以局部空間座標表示的位置資料
- 適用於小型紋理的紋理 UV,UV 裝設限制在 -1.0 至 1.0 的座標範圍之間
- 法向量、切線向量和雙切線向量資料
- 頂點顏色資料
- 以 0.0 為中心的低精度要求資料
「不」建議以半精度浮點值表示的資料類型包括:
- 以全球座標表示的位置資料
- 用於高精度用途的紋理 UV,例如圖集工作表中的 UI 元素座標
著色器程式碼的精確度
OpenGL 著色語言 (GLSL) 和高階著色器語言 (HLSL) 都是著色器程式設計語言,支援的規格包括數值類型的寬鬆精確度/明確精確度。我們建議著色器編譯器採用寬鬆精確度。在已指定精確度的情況下,則必須採用明確精確度。搭載 Android 的 Vulkan 裝置通常會採用寬鬆精確度建議的 16 位元格式。其他 Vulkan 裝置 (特別是使用不支援 16 位元格式圖像硬體的電腦) 可能會忽略寬鬆精確度,仍然使用 32 位元格式。
GLSL 中的儲存體擴充功能
您必須定義適當的 GLSL 擴充功能,以便在儲存體和統一緩衝區結構中支援 16 位元或 8 位元數值格式。相關的擴充功能宣告如下:
// 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
這些是 GLSL 特定的擴充功能,在 HLSL 中沒有對等項目。
GLSL 中的寬鬆精確度
在浮點類型之前使用 highp
限定詞,即可建議單精度浮點值,而 mediump
限定詞則適用於半精度浮點值。Vulkan 的 GLSL 編譯器會將舊版 lowp
限定詞解讀為 mediump
。以下是寬鬆精確度的幾個範例:
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
GLSL 中的明確精確度
在 GLSL 程式碼中加入 GL_EXT_shader_explicit_arithmetic_types_float16
擴充功能,即可啟用 16 位元浮點類型:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
請使用以下關鍵字,在 GLSL 中宣告 16 位元浮點純量、向量和矩陣類型:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
請使用以下關鍵字,在 GLSL 中宣告 16 位元整數純量和向量類型:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
HLSL 中的寬鬆精確度
HLSL 所用術語是「最低精確度」,而非寬鬆精確度。最小精確度類型關鍵字可指定最低精確度,但如果目標硬體更適合採用較高精確度,編譯器可能會替換較高的精確度。關鍵字 min16float
可用於指定最低精確度為 16 位元的浮點值。關鍵字 min16int
和 min16uint
則分別指定最低精確度為帶正負號和不帶正負號的 16 位元整數。以下是關於最低精確度宣告的其他範例:
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
HLSL 中的明確精確度
關鍵字 half
或 float16_t
用於指定半精度浮點。關鍵字 int16_t
和 uint16_t
則分別指定帶正負號和不帶正負號的 16 位元整數。以下是關於明確精確度宣告的其他範例:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;