เพิ่มประสิทธิภาพโดยลดความแม่นยำ

รูปแบบตัวเลขของข้อมูลกราฟิกและการคำนวณ 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 บิตหรือไม่ ให้ทำตามขั้นตอนต่อไปนี้

  1. ตรวจสอบว่าอุปกรณ์รองรับส่วนขยาย VK_KHR_shader_float16_int8 ของ Vulkan หรือไม่ ส่วนขยายนี้จำเป็นสำหรับการรองรับจุดลอยตัว 16 บิตและจำนวนเต็ม 8 บิต
  2. หากรองรับ VK_KHR_shader_float16_int8 ให้ต่อท้ายตัวชี้โครงสร้าง VkPhysicalDeviceShaderFloat16Int8Features ไปยังเชน VkPhysicalDeviceFeatures2.pNext
  3. ตรวจสอบฟิลด์ 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;