정밀도를 낮춰 최적화

숫자 형식의 그래픽 데이터와 셰이더 계산은 게임 성능에 상당한 영향을 미칠 수 있습니다.

최적의 형식은 다음을 실행합니다.

  • GPU 캐시 사용 효율성 증가
  • 메모리 대역폭 소비를 줄여 전력 절약 및 성능 개선
  • 셰이더 프로그램의 계산 처리량 극대화
  • 게임의 CPU RAM 사용량 최소화

부동 소수점 형식

최신 3D 그래픽의 계산 및 데이터는 대부분 부동 소수점 숫자를 사용합니다. Android의 Vulkan은 32비트 또는 16비트 크기의 부동 소수점 숫자를 사용합니다. 32비트 부동 소수점 숫자는 일반적으로 단일 정밀도 또는 전체 정밀도라고 합니다. 16비트 부동 소수점 숫자는 절반 정밀도입니다.

Vulkan은 64비트 부동 소수점 유형을 정의하지만 이 유형은 Android의 Vulkan 기기에서 일반적으로 지원되지 않으므로 사용하지 않는 것이 좋습니다. 일반적으로 64비트 부동 소수점 숫자를 배정밀도라고 합니다.

정수 형식

부호 있는 정수 및 부호 없는 정수도 데이터와 계산에 사용됩니다. 표준 정수 크기는 32비트입니다. 다른 비트 크기 지원은 기기에 따라 다릅니다. Android를 실행하는 Vulkan 기기는 일반적으로 16비트 및 8비트 정수를 지원합니다. Vulkan은 64비트 정수 유형을 정의하지만 이 유형은 Android의 Vulkan 기기에서 일반적으로 지원되지 않으므로 사용하지 않는 것이 좋습니다.

최적이 아닌 절반 정밀도 동작

최신 GPU 아키텍처는 16비트 값 두 개를 32비트 쌍으로 결합하고 이 쌍에서 작동하는 명령을 구현합니다. 최적의 성능을 위해 스칼라 16비트 부동 소수점 변수를 사용하지 마세요. 데이터를 2개 또는 4개의 요소 벡터로 벡터화합니다. 셰이더 컴파일러는 벡터 연산에서 스칼라 값을 사용할 수 있습니다. 그러나 컴파일러를 사용하여 스칼라를 최적화하는 경우 컴파일러 출력을 검사하여 벡터화를 확인합니다.

32비트 및 16비트&#ndash;정밀도 부동 소수점 간에 변환하면 계산 비용이 발생합니다. 코드에서 정밀도 변환을 최소화하여 오버헤드를 줄입니다.

알고리즘의 16비트 버전과 32비트 버전 간의 성능 차이를 벤치마킹하세요. 절반 정밀도가 항상 성능이 향상되는 것은 아니며, 특히 복잡한 계산의 경우 더욱 그렇습니다. 벡터화된 데이터에 FMA(Fused Multiply-Add) 명령을 많이 사용하는 알고리즘은 절반 정밀도에서 성능을 개선할 수 있는 좋은 후보입니다.

숫자 형식 지원

Android의 모든 Vulkan 기기는 데이터 및 셰이더 계산에서 단일 정밀도, 32비트 부동 소수점 수 및 32비트 정수를 지원합니다. 다른 형식의 지원이 제공된다고 보장되지는 않으며, 제공되는 경우에도 모든 사용 사례에 지원되는 것은 아닙니다.

Vulkan에는 선택적 숫자 형식에 관한 두 가지 지원 카테고리인 산술, 저장소가 있습니다. 특정 형식을 사용하려면 먼저 기기가 두 카테고리 모두에서 이를 지원하는지 확인해야 합니다.

산술 지원

Vulkan 기기는 셰이더 프로그램에서 사용할 수 있도록 숫자 형식의 산술 지원을 선언해야 습니다. Android의 Vulkan 기기는 일반적으로 다음 산술 형식을 지원합니다.

  • 32비트 정수(필수)
  • 32비트 부동 소수점(필수)
  • 8비트 정수(선택사항)
  • 16비트 정수(선택사항)
  • 16비트 절반 정밀도 부동 소수점(선택사항)

Vulkan 기기가 산술에 16비트 정수를 지원하는지 확인하려면 다음을 실행합니다. 먼저 vkGetPhysicalDeviceFeatures2() 함수를 호출하고 VkPhysicalDeviceFeatures2shaderInt16 필드 true임을 전달합니다.

Vulkan 기기가 16비트 부동 소수점 수나 8비트 정수를 지원하는지 확인하려면 다음 단계를 실행합니다.

  1. 기기에서 VK_KHR_shader_float16_int8 Vulkan 확장 프로그램을 지원하는지 확인합니다. 확장 프로그램은 16비트 부동 소수점 수 및 8비트 정수 지원에 필요합니다.
  2. VK_KHR_shader_float16_int8이 지원되는 경우 VkPhysicalDeviceShaderFloat16Int8Features 구조 포인터를 VkPhysicalDeviceFeatures2.pNext 체인에 추가합니다.
  3. vkGetPhysicalDeviceFeatures2()를 호출한 후 VkPhysicalDeviceShaderFloat16Int8Features 결과 구조의 shaderFloat16shaderInt8 필드를 확인합니다. 필드 값이 true이면 이 형식이 셰이더 프로그램 산술에 지원됩니다.

Vulkan 1.1 또는 2022 Android 기준 프로필의 요구사항은 아니지만 VK_KHR_shader_float16_int8 확장 프로그램 지원은 Android 기기에서 매우 일반적입니다.

저장소 지원

Vulkan 기기는 특정 저장소 유형의 선택적 숫자 형식 지원을 선언해야 합니다. VK_KHR_16bit_storage 확장 프로그램은 16비트 정수 및 16비트 부동 소수점 형식 지원을 선언합니다. 4가지 저장소 유형을 확장 프로그램에서 정의합니다. 기기는 저장소 유형이 없는 경우와 일부 또는 모든 저장소 유형에 관해 16비트 숫자를 지원할 수 있습니다.

저장소 유형은 다음과 같습니다.

  • 저장소 버퍼 객체
  • 균일 버퍼 객체
  • 푸시 상수 블록
  • 셰이더 입력 및 출력 인터페이스

전부는 아니지만 Android의 Vulkan 1.1 기기 대부분은 저장소 버퍼 객체에서 16비트 형식을 지원합니다. GPU 모델을 기반으로 지원을 가정하지 마세요. 특정 GPU를 위한 이전 드라이버가 있는 기기는 저장소 버퍼 객체를 지원하지 않을 수 있지만 최신 드라이버가 있는 기기는 지원합니다.

균일 버퍼, 푸시 상수 블록, 셰이더 입력/출력 인터페이스의 16비트 형식 지원은 일반적으로 GPU 제조업체에 따라 다릅니다. Android에서 GPU는 일반적으로 이러한 세 가지 유형을 모두 지원하거나 아예 지원하지 않습니다.

다음은 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;
  }
}

데이터의 정밀도 수준

절반 정밀도 부동 소수점 수는 단일 정밀도 부동 소수점 수보다 낮은 정밀도로 더 작은 범위의 값을 나타낼 수 있습니다. 절반 정밀도는 단일 정밀도에 비해 간단하고 인식상 무손실 선택인 경우가 많습니다. 그러나 절반 정밀도는 모든 사용 사례에서 실용적이지 않을 수 있습니다. 일부 데이터 유형의 경우 범위와 정밀도가 감소하면 그래픽 아티팩트가 발생하거나 렌더링이 잘못될 수 있습니다.

절반 정밀도 부동 소수점으로 표현하기에 적합한 데이터 유형은 다음과 같습니다.

  • 로컬 공간 좌표의 위치 데이터
  • -1.0~1.0 좌표 범위로 제한될 수 있는 UV 래핑이 제한된 작은 텍스처의 텍스처 UV
  • 일반, 탄젠트, 바이탄젠트 데이터
  • 꼭짓점 색상 데이터
  • 0.0을 중심으로 정밀도 요구사항이 낮은 데이터

절반 정밀도 부동 소수점 수로 표현하는 데 권장되지 않는 데이터 유형은 다음과 같습니다.

  • 전역 세계 좌표의 위치 데이터
  • 아틀라스 시트의 UI 요소 좌표와 같은 높은 정밀도 사용 사례를 위한 텍스처 UV

셰이더 코드의 정밀도

OpenGL Shading Language(GLSL)HLSL(높은 수준 셰이더 언어) 셰이더 프로그래밍 언어는 숫자 유형의 완화된 정밀도 또는 명시적 정밀도 사양을 지원합니다. 완화된 정밀도는 셰이더 컴파일러의 권장사항으로 간주됩니다. 명시적 정밀도는 지정된 정밀도의 요구사항입니다. Android의 Vulkan 기기는 일반적으로 완화된 정밀도에서 추천 시 16비트 형식을 사용합니다. 특히 16비트 형식을 지원하지 않는 그래픽 하드웨어를 사용하는 데스크톱 컴퓨터의 다른 Vulkan 기기는 완화된 정밀도를 무시하고 여전히 32비트 형식을 사용할 수 있습니다.

GLSL의 저장소 확장 프로그램

저장소 및 균일 버퍼 구조에서 16비트 또는 8비트 숫자 형식을 지원할 수 있도록 적절한 GLSL 확장 프로그램을 정의해야 합니다. 관련 확장 프로그램 선언은 다음과 같습니다.

// 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 한정자를 절반 정밀도 부동 소수점 수에 사용하세요. Vulkan용 GLSL 컴파일러는 레거시 lowp 한정자를 mediump로 해석합니다. 완화된 정밀도의 몇 가지 예는 다음과 같습니다.

mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix;   // Suggest 32-bit single precision

GLSL의 명시적 정밀도

16비트 부동 소수점 유형을 사용할 수 있도록 GLSL 코드에 GL_EXT_shader_explicit_arithmetic_types_float16 확장 프로그램을 포함합니다.

#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require

다음 키워드를 사용하여 GLSL에서 16비트 부동 소수점 스칼라, 벡터, 행렬 유형을 선언합니다.

float16_t   f16vec2     f16vec3    f16vec4
f16mat2     f16mat3     f16mat4
f16mat2x2   f16mat2x3   f16mat2x4
f16mat3x2   f16mat3x3   f16mat3x4
f16mat4x2   f16mat4x3   f16mat4x4

다음 키워드를 사용하여 GLSL에서 16비트 정수 스칼라 및 벡터 유형을 선언합니다.

int16_t     i16vec2     i16vec3    i16vec4
uint16_t    u16vec2     u16vec3    u16vec4

HLSL의 완화된 정밀도

HLSL은 완화된 정밀도 대신 최소 정밀도라는 용어를 사용합니다. 최소 정밀도 유형 키워드는 최소 정밀도를 지정하지만, 높은 정밀도가 타겟 하드웨어에 더 나은 선택인 경우 컴파일러는 더 높은 정밀도로 대체할 수 있습니다. 최소 정밀도 16비트 부동 소수점 수는 min16float 키워드로 지정됩니다. 최소 정밀도 부호 있는 16비트 정수와 부호 없는 16비트 정수는 각각 min16intmin16uint 키워드로 지정됩니다. 최소 정밀도 선언의 추가 예는 다음과 같습니다.

// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;

HLSL의 명시적 정밀도

절반 정밀도 부동 소수점은 half 또는 float16_t 키워드로 지정됩니다. 부호 있는 16비트 정수와 부호 없는 16비트 정수는 각각 int16_tuint16_t 키워드로 지정됩니다. 명시적 정밀도 선언의 추가 예는 다음과 같습니다.

// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;