Format numerik data grafis dan penghitungan shader dapat berdampak signifikan pada performa game.
Format yang optimal dapat melakukan hal berikut:
- Meningkatkan efisiensi penggunaan cache GPU
- Mengurangi penggunaan bandwidth memori, menghemat daya, dan meningkatkan performa
- Memaksimalkan throughput komputasi dalam program shader
- Meminimalkan penggunaan RAM CPU untuk game
Format floating point
Sebagian besar penghitungan dan data dalam grafis 3D modern menggunakan bilangan floating point. Vulkan di Android menggunakan bilangan floating point yang berukuran 32 atau 16 bit. Bilangan floating point 32 bit biasanya disebut sebagai presisi tunggal atau presisi penuh; bilangan floating point 16-bit disebut presisi setengah.
Vulkan menentukan jenis floating point 64-bit, tetapi jenis ini biasanya tidak didukung oleh perangkat Vulkan di Android, dan penggunaannya tidak direkomendasikan. Bilangan floating point 64-bit biasanya disebut sebagai presisi ganda.
Format bilangan bulat
Bilangan bulat yang ditandatangani dan tidak ditandatangani juga digunakan untuk data dan penghitungan. Ukuran bilangan bulat standar adalah 32 bit. Dukungan untuk ukuran bit lain bergantung pada perangkat. Perangkat Vulkan yang menjalankan Android biasanya mendukung bilangan bulat 16-bit dan 8-bit. Vulkan menentukan jenis bilangan bulat 64-bit, tetapi jenis ini biasanya tidak didukung oleh perangkat Vulkan di Android, dan penggunaannya tidak direkomendasikan.
Perilaku presisi setengah yang kurang optimal
Arsitektur GPU modern menggabungkan dua nilai 16-bit bersama-sama dalam pasangan 32-bit dan menerapkan petunjuk yang beroperasi pada pasangan tersebut. Untuk performa yang optimal, hindari penggunaan variabel float 16-bit skalar; vektorisasi data menjadi vektor dua atau empat elemen. Compiler shader mungkin dapat menggunakan nilai skalar dalam operasi vektor. Namun, jika Anda mengandalkan compiler untuk mengoptimalkan skalar, periksa output compiler untuk memverifikasi vektorisasi.
Konversi ke dan dari floating point presisi 32-bit dan 16-bit memiliki biaya komputasi. Kurangi overhead dengan meminimalkan konversi presisi dalam kode Anda.
Tentukan tolok ukur perbedaan performa antara algoritma versi 16-bit dan 32-bit Anda. Presisi setengah tidak selalu menghasilkan peningkatan performa, terutama untuk penghitungan yang rumit. Algoritma yang banyak menggunakan petunjuk fused multiply-add (FMA) pada data vektor adalah kandidat yang baik untuk meningkatkan performa pada presisi setengah.
Dukungan format numerik
Semua perangkat Vulkan di Android mendukung bilangan floating point 32-bit presisi tunggal dan angka bilangan bulat 32-bit dalam penghitungan data dan shader. Dukungan untuk format lain tidak dijamin akan tersedia, dan jika tersedia, tidak dijamin untuk semua kasus penggunaan.
Vulkan memiliki dua kategori dukungan untuk format numerik opsional: aritmetika dan penyimpanan. Sebelum menggunakan format tertentu, pastikan perangkat mendukungnya dalam kedua kategori tersebut.
Dukungan aritmetika
Perangkat Vulkan harus mendeklarasikan dukungan aritmetika untuk format numerik agar dapat digunakan dalam program shader. Perangkat Vulkan di Android biasanya mendukung format berikut untuk aritmetika:
- Bilangan bulat 32-bit (wajib)
- Floating point 32 bit (wajib)
- Bilangan bulat 8-bit (opsional)
- Bilangan bulat 16-bit (opsional)
- Floating point presisi setengah 16-bit (opsional)
Untuk menentukan apakah perangkat Vulkan mendukung bilangan bulat 16-bit untuk aritmetika,
ambil fitur perangkat dengan memanggil fungsi
vkGetPhysicalDeviceFeatures2() dan memeriksa apakah kolom
shaderInt16
di struktur hasil VkPhysicalDeviceFeatures2
bernilai benar (true).
Untuk menentukan apakah perangkat Vulkan mendukung float 16-bit atau bilangan bulat 8-bit, lakukan langkah-langkah berikut:
- Periksa apakah perangkat mendukung ekstensi Vulkan VK_KHR_shader_float16_int8. Ekstensi ini diperlukan untuk dukungan float 16-bit dan bilangan bulat 8-bit.
- Jika
VK_KHR_shader_float16_int8
didukung, tambahkan pointer struktur VkPhysicalDeviceShaderFloat16Int8Features ke rantaiVkPhysicalDeviceFeatures2.pNext
. - Periksa kolom
shaderFloat16
danshaderInt8
dari struktur hasilVkPhysicalDeviceShaderFloat16Int8Features
setelah memanggilvkGetPhysicalDeviceFeatures2()
. Jika nilai kolom adalahtrue
, format ini didukung untuk aritmetika program shader.
Meskipun bukan persyaratan di Vulkan 1.1 atau
profil Dasar Pengukuran Android 2022, dukungan untuk ekstensi VK_KHR_shader_float16_int8
biasanya ditemukan di perangkat Android.
Dukungan penyimpanan
Perangkat Vulkan harus mendeklarasikan dukungan terhadap format numerik opsional untuk jenis penyimpanan tertentu. Ekstensi VK_KHR_16bit_storage mendeklarasikan dukungan untuk format bilangan bulat 16-bit dan floating-point 16-bit. Empat jenis penyimpanan ditentukan oleh ekstensi ini. Perangkat dapat mendukung bilangan 16-bit untuk tidak satu pun, beberapa, atau semua jenis penyimpanan.
Jenis penyimpanannya adalah:
- Objek buffer penyimpanan
- Objek buffer seragam
- Blok konstanta push
- Antarmuka input dan output shader
Sebagian besar, tetapi tidak semua, perangkat Vulkan 1.1 di Android mendukung format 16-bit dalam objek buffer penyimpanan. Jangan mengasumsikan dukungan berdasarkan model GPU. Perangkat dengan driver yang lebih lama untuk GPU tertentu mungkin tidak mendukung objek buffer penyimpanan, sedangkan perangkat dengan driver yang lebih baru mendukungnya.
Dukungan untuk format 16-bit dalam buffer seragam, blok konstanta push, dan antarmuka input/output shader umumnya bergantung pada produsen GPU. Di Android, GPU biasanya mendukung ketiga jenis ini atau tidak mendukung ketiganya.
Contoh fungsi yang menguji dukungan format penyimpanan dan aritmetika 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;
}
}
Tingkat presisi data
Bilangan floating point presisi setengah dapat mewakili rentang nilai yang lebih kecil dengan presisi yang lebih rendah daripada bilangan floating point presisi tunggal. Presisi setengah sering kali merupakan pilihan yang mudah dan tanpa penurunan secara persepsi dibandingkan presisi tunggal. Namun, presisi setengah mungkin tidak praktis untuk semua kasus penggunaan. Untuk beberapa jenis data, rentang dan presisi yang lebih rendah dapat menyebabkan artefak grafis atau rendering yang salah.
Jenis data yang merupakan kandidat baik untuk representasi dalam floating point presisi setengah meliputi:
- Data posisi dalam koordinat ruang lokal
- UV tekstur untuk tekstur yang lebih kecil dengan gabungan UV terbatas yang dapat dibatasi pada rentang koordinat -1,0 sampai 1,0
- Data normal, tangen, dan bitangen
- Data warna vertex
- Data dengan persyaratan presisi rendah yang berpusat pada 0,0
Jenis data yang tidak direkomendasikan untuk representasi dalam float presisi setengah meliputi:
- Data posisi dalam koordinat dunia global
- UV tekstur untuk kasus penggunaan presisi tinggi seperti koordinat elemen UI dalam sheet atlas
Presisi dalam kode shader
Bahasa pemrograman shader OpenGL Shading Language (GLSL) dan High-level Shader Language (HLSL) mendukung spesifikasi presisi longgar atau presisi eksplisit untuk jenis numerik. Presisi longgar diperlakukan sebagai rekomendasi untuk compiler shader. Presisi eksplisit adalah persyaratan dari presisi yang ditentukan. Perangkat Vulkan di Android umumnya menggunakan format 16-bit jika disarankan oleh presisi longgar. Perangkat Vulkan lainnya, terutama di komputer desktop yang menggunakan hardware grafis yang tidak mendukung format 16-bit, mungkin mengabaikan presisi longgar dan tetap menggunakan format 32-bit.
Ekstensi penyimpanan dalam GLSL
Ekstensi GLSL yang sesuai harus ditentukan guna mengaktifkan dukungan untuk format numerik 16-bit atau 8-bit dalam struktur buffer seragam dan penyimpanan. Deklarasi ekstensi yang relevan adalah:
// 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
Ekstensi ini khusus untuk GLSL dan tidak memiliki padanan di HLSL.
Presisi longgar dalam GLSL
Gunakan penentu highp
sebelum jenis floating point untuk menyarankan
float presisi tunggal dan penentu mediump
untuk float presisi setengah.
Compiler GLSL untuk Vulkan menafsirkan penentu lowp
lama sebagai mediump
.
Beberapa contoh presisi longgar:
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
Presisi eksplisit dalam GLSL
Sertakan ekstensi GL_EXT_shader_explicit_arithmetic_types_float16
dalam
kode GLSL Anda untuk mengaktifkan penggunaan jenis floating point 16 bit:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
Deklarasikan jenis skalar, vektor, dan matriks floating point 16-bit dalam GLSL menggunakan kata kunci berikut:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
Deklarasikan jenis skalar dan vektor bilangan bulat 16-bit dalam GLSL menggunakan kata kunci berikut:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
Presisi longgar dalam HLSL
HLSL menggunakan istilah presisi minimal, bukan presisi longgar. Kata kunci
jenis presisi minimal menentukan presisi minimum, tetapi compiler dapat
mengganti dengan presisi yang lebih tinggi jika presisi yang lebih tinggi adalah pilihan yang lebih baik untuk
hardware target. Float 16-bit presisi minimal ditentukan oleh
kata kunci min16float
. Bilangan bulat 16-bit presisi minimal yang ditandatangani dan tidak ditandatangani, masing-masing ditentukan oleh kata kunci min16int
dan min16uint
. Contoh
tambahan dari deklarasi presisi minimal mencakup hal berikut:
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
Presisi eksplisit dalam HLSL
Floating point presisi setengah ditentukan oleh kata kunci half
atau
float16_t
. Bilangan bulat 16-bit yang ditandatangani dan tidak ditandatangani, masing-masing ditentukan oleh kata kunci int16_t
dan uint16_t
. Contoh tambahan dari deklarasi presisi
eksplisit mencakup hal berikut:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;