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

รูปแบบตัวเลขของข้อมูลกราฟิกและการคำนวณ 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 บิตมี ต้นทุนในการคำนวณ ลดค่าใช้จ่ายโดยลด Conversion ที่มีความแม่นยำในโค้ด

ความแตกต่างของประสิทธิภาพการเปรียบเทียบระหว่างอัลกอริทึมเวอร์ชัน 16 บิตกับ 32 บิต ความแม่นยำครึ่งหนึ่งไม่ได้ส่งผลให้ประสิทธิภาพดีขึ้นเสมอไป โดยเฉพาะอย่างยิ่งสำหรับการคำนวณที่ซับซ้อน อัลกอริทึมที่ใช้คำสั่ง fused multiply-add (FMA) อย่างหนักในข้อมูลที่แปลงเป็นเวกเตอร์เป็นตัวเลือกที่ดีสำหรับ ประสิทธิภาพที่ปรับปรุงแล้วที่ความแม่นยำครึ่งหนึ่ง

การรองรับรูปแบบตัวเลข

อุปกรณ์ Vulkan ทั้งหมดใน Android รองรับตัวเลขทศนิยมแบบ 32 บิตที่มีความแม่นยำเดียว และตัวเลขจำนวนเต็มแบบ 32 บิตในการคำนวณข้อมูลและ Shader เราไม่รับประกันว่ารูปแบบอื่นๆ จะพร้อมให้บริการ และหากพร้อมให้บริการ ก็ไม่รับประกัน สำหรับ Use Case ทั้งหมด

Vulkan มีการรองรับรูปแบบตัวเลขที่ไม่บังคับ 2 หมวดหมู่ ได้แก่ การคำนวณ และการจัดเก็บ ก่อนใช้รูปแบบใดรูปแบบหนึ่ง โปรดตรวจสอบว่าอุปกรณ์รองรับรูปแบบนั้นในทั้ง 2 หมวดหมู่

การรองรับเลขคณิต

อุปกรณ์ Vulkan ต้องประกาศการรองรับการคำนวณสำหรับรูปแบบตัวเลขเพื่อให้ใช้ในโปรแกรม Shader ได้ โดยทั่วไปแล้ว อุปกรณ์ Vulkan ใน Android จะรองรับรูปแบบต่อไปนี้สำหรับการคำนวณทางคณิตศาสตร์

  • จำนวนเต็ม 32 บิต (ต้องระบุ)
  • จุดลอยตัว 32 บิต (ต้องระบุ)
  • จำนวนเต็มแบบ 8 บิต (ไม่บังคับ)
  • จำนวนเต็มแบบ 16 บิต (ไม่บังคับ)
  • จุดลอยตัวแบบความแม่นยำครึ่งหนึ่ง 16 บิต (ไม่บังคับ)

หากต้องการตรวจสอบว่าอุปกรณ์ Vulkan รองรับจำนวนเต็ม 16 บิตสำหรับการคำนวณหรือไม่ ให้เรียกใช้ฟังก์ชัน vkGetPhysicalDeviceFeatures2() และตรวจสอบว่าฟิลด์ shaderInt16 ในโครงสร้างผลลัพธ์ VkPhysicalDeviceFeatures2 เป็นจริงหรือไม่

หากต้องการตรวจสอบว่าอุปกรณ์ Vulkan รองรับจำนวนทศนิยม 16 บิตหรือจำนวนเต็ม 8 บิตหรือไม่ ให้ทำตามขั้นตอนต่อไปนี้

  1. ตรวจสอบว่าอุปกรณ์รองรับส่วนขยาย Vulkan VK_KHR_shader_float16_int8 หรือไม่ ส่วนขยายนี้ จำเป็นสำหรับการรองรับจำนวนเต็ม 8 บิตและโฟลต 16 บิต
  2. หากรองรับ VK_KHR_shader_float16_int8 ให้ต่อพอยน์เตอร์โครงสร้าง VkPhysicalDeviceShaderFloat16Int8Features เข้ากับเชน VkPhysicalDeviceFeatures2.pNext
  3. ตรวจสอบฟิลด์ shaderFloat16 และ shaderInt8 ของ VkPhysicalDeviceShaderFloat16Int8Features โครงสร้างผลลัพธ์หลังจากเรียกใช้ vkGetPhysicalDeviceFeatures2() หากค่าฟิลด์เป็น true ระบบจะรองรับรูปแบบ สำหรับการคำนวณโปรแกรม Shader

แม้ว่า Vulkan 1.1 หรือ Android Baseline profile ปี 2022 จะไม่ได้กำหนดให้ต้องรองรับ แต่การรองรับส่วนขยาย VK_KHR_shader_float16_int8 ก็เป็นเรื่องปกติมากในอุปกรณ์ Android

การรองรับพื้นที่เก็บข้อมูล

อุปกรณ์ Vulkan ต้องประกาศการรองรับรูปแบบตัวเลขที่ไม่บังคับสำหรับ ประเภทพื้นที่เก็บข้อมูลที่เฉพาะเจาะจง ส่วนขยาย VK_KHR_16bit_storage ประกาศการรองรับรูปแบบจำนวนเต็ม 16 บิตและรูปแบบจุดลอย 16 บิต ส่วนขยายจะกำหนดประเภทพื้นที่เก็บข้อมูล 4 ประเภท อุปกรณ์สามารถรองรับตัวเลข 16 บิต สำหรับที่เก็บข้อมูลบางประเภทหรือทั้งหมด

ประเภทพื้นที่เก็บข้อมูลมีดังนี้

  • ออบเจ็กต์บัฟเฟอร์พื้นที่เก็บข้อมูล
  • ออบเจ็กต์บัฟเฟอร์แบบสม่ำเสมอ
  • บล็อกค่าคงที่แบบพุช
  • อินเทอร์เฟซอินพุตและเอาต์พุตของ Shader

อุปกรณ์ Vulkan 1.1 ส่วนใหญ่ใน Android รองรับรูปแบบ 16 บิตใน ออบเจ็กต์บัฟเฟอร์พื้นที่เก็บข้อมูล อย่าคิดว่าระบบจะรองรับตามรุ่น GPU อุปกรณ์ ที่มีไดรเวอร์รุ่นเก่าสำหรับ GPU ที่กำหนดอาจไม่รองรับออบเจ็กต์บัฟเฟอร์พื้นที่เก็บข้อมูล ในขณะที่อุปกรณ์ที่มีไดรเวอร์รุ่นใหม่กว่าจะรองรับ

โดยทั่วไปแล้ว การรองรับรูปแบบ 16 บิตในบัฟเฟอร์แบบสม่ำเสมอ บล็อกค่าคงที่แบบพุช และอินเทอร์เฟซอินพุต/เอาต์พุตของ 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 ใน ชีต Atlas

ความแม่นยำในโค้ดเชเดอร์

ภาษาการเขียนโปรแกรม Shader OpenGL Shading Language (GLSL) และ High-level Shader Language (HLSL) รองรับการระบุความแม่นยำที่ผ่อนปรน หรือความแม่นยำที่ชัดเจนสำหรับประเภทตัวเลข ความแม่นยำแบบผ่อนปรนจะถือเป็น คำแนะนำสำหรับคอมไพเลอร์ Shader ความแม่นยำที่ชัดเจนเป็น ข้อกำหนดของความแม่นยำที่ระบุ โดยทั่วไปแล้ว อุปกรณ์ Vulkan ใน Android จะใช้รูปแบบ 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 สำหรับทศนิยมแบบความแม่นยำครึ่ง คอมไพเลอร์ 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 min16int และ min16uint เป็นคีย์เวิร์ดที่ใช้ระบุจำนวนเต็ม 16 บิตที่มีการลงนามและไม่มีการลงนามที่มีความแม่นยำต่ำสุดตามลำดับ ตัวอย่างเพิ่มเติมของการประกาศความแม่นยำขั้นต่ำมีดังนี้

// 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;