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

รูปแบบตัวเลขของข้อมูลกราฟิกและการคำนวณเชดเดอร์อาจส่งผลต่อประสิทธิภาพของเกมได้อย่างมาก

รูปแบบที่เหมาะสมจะมีลักษณะดังนี้

  • เพิ่มประสิทธิภาพการใช้แคช 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 บิตในการคำนวณข้อมูลและการคำนวณเฉดสี เราไม่รับประกันว่าจะมีการสนับสนุนรูปแบบอื่นๆ และหากมี ก็ไม่รับประกันว่าจะใช้ได้กับ Use Case ทั้งหมด

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 แต่การรองรับส่วนขยาย VK_KHR_shader_float16_int8 นั้นพบได้ทั่วไปในอุปกรณ์ Android

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

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

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

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

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

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

ภาษาโปรแกรม 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 ใช้คำว่าความแม่นยำขั้นต่ำแทนความแม่นยำแบบผ่อนปรน คีย์เวิร์ดประเภทความแม่นยำขั้นต่ำจะระบุความแม่นยำขั้นต่ำ แต่คอมไพเลอร์อาจแทนที่ด้วยความแม่นยำที่สูงกว่าหากความแม่นยำที่สูงกว่าเป็นตัวเลือกที่ดีกว่าสำหรับฮาร์ดแวร์เป้าหมาย คีย์เวิร์ด min16float จะระบุค่าลอยทศนิยม 16 บิตที่มีความแม่นยำขั้นต่ำ ระบุจำนวนเต็ม 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;