รูปแบบตัวเลขของข้อมูลกราฟิกและการคำนวณ Shader อาจส่งผลกระทบอย่างมากต่อประสิทธิภาพของเกม
รูปแบบที่เหมาะสมมีลักษณะดังนี้
- เพิ่มประสิทธิภาพการใช้แคชของ GPU
- ลดการใช้แบนด์วิดท์ของหน่วยความจำ ซึ่งช่วยประหยัดพลังงานและเพิ่มประสิทธิภาพ
- เพิ่มปริมาณงานการคำนวณสูงสุดในโปรแกรม Shader
- ลดการใช้ RAM ของ CPU ในเกม
รูปแบบจุดลอยตัว
การคำนวณและข้อมูลส่วนใหญ่ในกราฟิก 3 มิติสมัยใหม่ใช้ตัวเลขจุดลอยตัว Vulkan ใน Android ใช้ตัวเลขจุดลอยตัวที่มีขนาด 32 หรือ 16 บิต โดยทั่วไปแล้วตัวเลขจุดลอยตัว 32 บิตจะเรียกว่าความแม่นยำเดี่ยวหรือความแม่นยำเต็ม ส่วนตัวเลขจุดลอยตัว 16 บิตจะเรียกว่าความแม่นยำครึ่งหนึ่ง
Vulkan กำหนดประเภทจุดลอยตัว 64 บิต แต่โดยทั่วไปแล้วอุปกรณ์ Vulkan ใน Android จะไม่รองรับประเภทนี้ และเราไม่แนะนำให้ใช้ โดยทั่วไปแล้วตัวเลขจุดลอยตัว 64 บิตจะเรียกว่าความแม่นยำสองเท่า
รูปแบบจำนวนเต็ม
นอกจากนี้ยังใช้ตัวเลขจำนวนเต็มที่มีเครื่องหมายและไม่มีเครื่องหมายสำหรับข้อมูลและการคำนวณด้วย ขนาดจำนวนเต็มมาตรฐานคือ 32 บิต การรองรับขนาดบิตอื่นๆ ขึ้นอยู่กับอุปกรณ์ โดยทั่วไปแล้วอุปกรณ์ Vulkan ที่ใช้ Android จะรองรับจำนวนเต็ม 16 บิตและ 8 บิต Vulkan กำหนดประเภทจำนวนเต็ม 64 บิต แต่โดยทั่วไปแล้วอุปกรณ์ Vulkan ใน Android จะไม่รองรับประเภทนี้ และเราไม่แนะนำให้ใช้
ลักษณะการทำงานของความแม่นยำครึ่งหนึ่งที่ไม่เหมาะสม
สถาปัตยกรรม GPU สมัยใหม่จะรวมค่า 16 บิต 2 ค่าเข้าด้วยกันเป็นคู่ 32 บิต และใช้คำสั่งที่ทำงานกับคู่ดังกล่าว หลีกเลี่ยงการใช้ตัวแปรจุดลอยตัวสเกลาร์ 16 บิตเพื่อประสิทธิภาพสูงสุด ให้แปลงข้อมูลเป็นเวกเตอร์ 2 หรือ 4 องค์ประกอบ คอมไพเลอร์ Shader อาจใช้ค่าสเกลาร์ในการดำเนินการเวกเตอร์ได้ อย่างไรก็ตาม หากคุณใช้คอมไพเลอร์เพื่อเพิ่มประสิทธิภาพสเกลาร์ ให้ตรวจสอบเอาต์พุตของคอมไพเลอร์เพื่อยืนยันการแปลงเป็นเวกเตอร์
การแปลงไปเป็นและจากจุดลอยตัวที่มีความแม่นยำ 32 บิตและ 16 บิตมีค่าใช้จ่ายในการคำนวณ ลดค่าใช้จ่ายโดยการลดการแปลงความแม่นยำในโค้ด
เปรียบเทียบประสิทธิภาพการทำงานระหว่างอัลกอริทึมเวอร์ชัน 16 บิตและ 32 บิต ความแม่นยำครึ่งหนึ่งไม่ได้ส่งผลให้ประสิทธิภาพดีขึ้นเสมอไป โดยเฉพาะอย่างยิ่งสำหรับการคำนวณที่ซับซ้อน อัลกอริทึมที่ใช้คำสั่ง Fused Multiply-Add (FMA) กับข้อมูลที่แปลงเป็นเวกเตอร์อย่างหนักเป็นตัวเลือกที่ดีสำหรับการปรับปรุงประสิทธิภาพที่ความแม่นยำครึ่งหนึ่ง
การรองรับรูปแบบตัวเลข
อุปกรณ์ Vulkan ทั้งหมดใน Android รองรับตัวเลขจุดลอยตัวที่มีความแม่นยำเดี่ยว 32 บิต และตัวเลขจำนวนเต็ม 32 บิตในการคำนวณข้อมูลและ Shader เราไม่รับประกันว่ารูปแบบอื่นๆ จะพร้อมใช้งาน และหากพร้อมใช้งานก็ไม่รับประกันว่าจะใช้ได้กับกรณีการใช้งานทั้งหมด
Vulkan มีการรองรับรูปแบบตัวเลขที่ไม่บังคับ 2 หมวดหมู่ ได้แก่ การคำนวณและการจัดเก็บ ก่อนใช้รูปแบบใดรูปแบบหนึ่ง ให้ตรวจสอบว่าอุปกรณ์รองรับรูปแบบนั้นในทั้ง 2 หมวดหมู่
การรองรับการคำนวณ
อุปกรณ์ Vulkan ต้องประกาศการรองรับการคำนวณสำหรับรูปแบบตัวเลขเพื่อให้ใช้งานได้ในโปรแกรม Shader โดยทั่วไปแล้วอุปกรณ์ Vulkan ใน Android จะรองรับรูปแบบต่อไปนี้สำหรับการคำนวณ
- จำนวนเต็ม 32 บิต (บังคับ)
- จุดลอยตัว 32 บิต (บังคับ)
- จำนวนเต็ม 8 บิต (ไม่บังคับ)
- จำนวนเต็ม 16 บิต (ไม่บังคับ)
- จุดลอยตัวที่มีความแม่นยำครึ่งหนึ่ง 16 บิต (ไม่บังคับ)
หากต้องการตรวจสอบว่าอุปกรณ์ Vulkan รองรับจำนวนเต็ม 16 บิตสำหรับการคำนวณหรือไม่
ให้เรียกข้อมูลฟีเจอร์ของอุปกรณ์โดยเรียกใช้ฟังก์ชัน
vkGetPhysicalDeviceFeatures2() และตรวจสอบว่า
ฟิลด์ shaderInt16 ในโครงสร้างผลลัพธ์ VkPhysicalDeviceFeatures2
เป็นจริงหรือไม่
หากต้องการตรวจสอบว่าอุปกรณ์ Vulkan รองรับจุดลอยตัว 16 บิตหรือจำนวนเต็ม 8 บิตหรือไม่ ให้ทำตามขั้นตอนต่อไปนี้
- ตรวจสอบว่าอุปกรณ์รองรับส่วนขยาย VK_KHR_shader_float16_int8 ของ Vulkan หรือไม่ ส่วนขยายนี้จำเป็นสำหรับการรองรับจุดลอยตัว 16 บิตและจำนวนเต็ม 8 บิต
- หากรองรับ
VK_KHR_shader_float16_int8ให้ต่อท้ายตัวชี้โครงสร้าง VkPhysicalDeviceShaderFloat16Int8Features ไปยังเชนVkPhysicalDeviceFeatures2.pNext - ตรวจสอบฟิลด์
shaderFloat16และshaderInt8ของโครงสร้างผลลัพธ์VkPhysicalDeviceShaderFloat16Int8Featuresหลังจากเรียกใช้vkGetPhysicalDeviceFeatures2()หากค่าฟิลด์เป็นtrueระบบจะรองรับรูปแบบสำหรับการคำนวณโปรแกรม Shader
แม้ว่า Vulkan 1.1 หรือโปรไฟล์พื้นฐานของ Android ปี 2022
จะไม่ได้กำหนดไว้ แต่โดยทั่วไปแล้วอุปกรณ์ Android จะรองรับVK_KHR_shader_float16_int8
ส่วนขยาย
การรองรับการจัดเก็บ
อุปกรณ์ Vulkan ต้องประกาศการรองรับรูปแบบตัวเลขที่ไม่บังคับสำหรับประเภทการจัดเก็บข้อมูลที่เฉพาะเจาะจง ส่วนขยาย VK_KHR_16bit_storage ประกาศการรองรับรูปแบบจำนวนเต็ม 16 บิตและจุดลอยตัว 16 บิต ส่วนขยายนี้กำหนดประเภทการจัดเก็บข้อมูลไว้ 4 ประเภท อุปกรณ์สามารถรองรับตัวเลข 16 บิตสำหรับประเภทการจัดเก็บข้อมูลบางประเภทหรือทุกประเภท
ประเภทการจัดเก็บข้อมูลมีดังนี้
- ออบเจ็กต์บัฟเฟอร์การจัดเก็บข้อมูล
- ออบเจ็กต์บัฟเฟอร์แบบ Uniform
- บล็อกค่าคงที่แบบ Push
- อินเทอร์เฟซอินพุตและเอาต์พุตของ Shader
อุปกรณ์ Vulkan 1.1 ส่วนใหญ่ใน Android รองรับรูปแบบ 16 บิตในออบเจ็กต์บัฟเฟอร์การจัดเก็บข้อมูล อย่าสันนิษฐานการรองรับตามรุ่น GPU อุปกรณ์ที่มีไดรเวอร์ GPU เวอร์ชันเก่าอาจไม่รองรับออบเจ็กต์บัฟเฟอร์การจัดเก็บข้อมูล ในขณะที่อุปกรณ์ที่มีไดรเวอร์เวอร์ชันใหม่รองรับ
โดยทั่วไปแล้วการรองรับรูปแบบ 16 บิตในบัฟเฟอร์แบบ Uniform, บล็อกค่าคงที่แบบ Push และอินเทอร์เฟซอินพุต/เอาต์พุตของ Shader จะขึ้นอยู่กับผู้ผลิต GPU ใน Android โดยทั่วไปแล้ว GPU จะรองรับประเภทเหล่านี้ทั้งหมด 3 ประเภทหรือไม่รองรับเลย
ฟังก์ชันตัวอย่างที่ทดสอบการรองรับรูปแบบการคำนวณและการจัดเก็บข้อมูลของ 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 ในแอตลาสชีต
ความแม่นยำในโค้ด Shader
ภาษาโปรแกรม Shader ของ OpenGL Shading Language (GLSL) และ High-level Shader Language (HLSL) รองรับการระบุความแม่นยำที่ลดลงหรือความแม่นยำที่ชัดเจนสำหรับประเภทตัวเลข ระบบจะถือว่าความแม่นยำที่ลดลงเป็นคำแนะนำสำหรับคอมไพเลอร์ Shader ความแม่นยำที่ชัดเจนเป็นข้อกำหนดของความแม่นยำที่ระบุ โดยทั่วไปแล้วอุปกรณ์ Vulkan ใน Android จะใช้รูปแบบ 16 บิตเมื่อความแม่นยำที่ลดลงแนะนำ อุปกรณ์ Vulkan อื่นๆ โดยเฉพาะอย่างยิ่งในคอมพิวเตอร์เดสก์ท็อปที่ใช้ฮาร์ดแวร์กราฟิกที่ไม่รองรับรูปแบบ 16 บิต อาจละเว้นความแม่นยำที่ลดลงและยังคงใช้รูปแบบ 32 บิต
ส่วนขยายการจัดเก็บข้อมูลใน GLSL
ต้องกำหนดส่วนขยาย GLSL ที่เหมาะสมเพื่อเปิดใช้การรองรับรูปแบบตัวเลข 16 บิตหรือ 8 บิตในโครงสร้างบัฟเฟอร์การจัดเก็บข้อมูลและแบบ Uniform การประกาศส่วนขยายที่เกี่ยวข้องมีดังนี้
// 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 สำหรับจุดลอยตัวที่มีความแม่นยำครึ่งหนึ่ง
คอมไพเลอร์ GLSL สำหรับ Vulkan จะตีความตัวระบุ lowp แบบเดิมเป็น mediump
ตัวอย่างความแม่นยำที่ลดลงมีดังนี้
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
ความแม่นยำที่ชัดเจนใน GLSL
ใส่ส่วนขยาย GL_EXT_shader_explicit_arithmetic_types_float16 ในโค้ด GLSL เพื่อเปิดใช้การใช้ประเภทจุดลอยตัว 16 บิต
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
ประกาศประเภทสเกลาร์ เวกเตอร์ และเมทริกซ์จุดลอยตัว 16 บิตใน GLSL โดยใช้คีย์เวิร์ดต่อไปนี้
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
ประกาศประเภทสเกลาร์และเวกเตอร์จำนวนเต็ม 16 บิตใน GLSL โดยใช้คีย์เวิร์ดต่อไปนี้
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
ความแม่นยำที่ลดลงใน HLSL
HLSL ใช้คำว่า ความแม่นยำต่ำสุด แทนความแม่นยำที่ลดลง คีย์เวิร์ดประเภทความแม่นยำต่ำสุดจะระบุความแม่นยำต่ำสุด แต่คอมไพเลอร์อาจแทนที่ด้วยความแม่นยำที่สูงกว่าหากความแม่นยำที่สูงกว่าเป็นตัวเลือกที่ดีกว่าสำหรับฮาร์ดแวร์เป้าหมาย จุดลอยตัว 16 บิตที่มีความแม่นยำต่ำสุดจะระบุโดยคีย์เวิร์ด min16float จำนวนเต็ม 16 บิตที่มีเครื่องหมายและไม่มีเครื่องหมายที่มีความแม่นยำต่ำสุดจะระบุโดยคีย์เวิร์ด min16int และ min16uint ตามลำดับ ตัวอย่างเพิ่มเติมของการประกาศความแม่นยำต่ำสุดมีดังนี้
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
ความแม่นยำที่ชัดเจนใน HLSL
จุดลอยตัวที่มีความแม่นยำครึ่งหนึ่งจะระบุโดยคีย์เวิร์ด half หรือ float16_t จำนวนเต็ม 16 บิตที่มีเครื่องหมายและไม่มีเครื่องหมายจะระบุโดยคีย์เวิร์ด int16_t
และ uint16_t ตามลำดับ ตัวอย่างเพิ่มเติมของการประกาศความแม่นยำที่ชัดเจนมีดังนี้
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;