Vulkan specialization sabitleri açıklaması: yeniden derlemeden shader varyantları

#define QUALITY 2‘li bir shader gömülüdür — sabiti değiştirin ve SPIR-V’yi yeniden derlersiniz. Bir UBO’dan quality okuyan shader esnektir ama fragment başına bir load + branch öder. Specialization sabitleri orta yoldur: değer pipeline creation zamanında enjekte edilir, sürücünün shader derleyicisi onu literal sabit olarak görür — ölü dallar katlanır, döngüler açılır — ama SPIR-V’ye hiç dokunmadınız.

Mekanizma

shader.comp
// GLSL tarafı — varsayılanlı isimli sabit:
layout(constant_id = 0) const int QUALITY = 2;
layout(constant_id = 1) const bool USE_FILTER = true;

void main() {
    if (USE_FILTER) { /* … */ }  // USE_FILTER=false iken katlanıp gider
    for (int i = 0; i < QUALITY; ++i) { /* … */ }  // açılır
}
pipeline.cpp
// Host tarafı — pipeline create'te varsayılanları ez:
struct { int32_t quality; uint32_t useFilter; } spec{4, 1};

vk::SpecializationMapEntry entries[2];
entries[0].setConstantID(0).setOffset(offsetof(decltype(spec), quality))
          .setSize(sizeof(spec.quality));
entries[1].setConstantID(1).setOffset(offsetof(decltype(spec), useFilter))
          .setSize(sizeof(spec.useFilter));

vk::SpecializationInfo specInfo{};
specInfo.setMapEntries(entries).setDataSize(sizeof(spec)).setPData(&spec);

vk::PipelineShaderStageCreateInfo stage{};
stage.setPModule(shaderModule).setPName("main").setPSpecializationInfo(&specInfo);
// … pipelineCreateInfo.setStages(stage) …

Sürücünün JIT’i, pipeline creation’da SPIR-V’yi verilen değerler katlanmış olarak derler — QUALITY=4 ISA’da bir literal olur.

Neden sadece UBO değil

YaklaşımShader’ın gördüğüMaliyetYeniden derleme?
#definederleme-zamanı literalbedavaevet — yeni SPIR-V
Spec sabitipipeline build’de literalbedavahayır — sadece yeni VkPipeline
UBO / push sabitibellekten loadkullanım başına loadhayır

Kazanç: dal katlama ve döngü açma. USE_FILTER‘ın bir UBO değeri olduğu if (USE_FILTER)‘lı bir shader her iki tarafın register tahsisini değerlendirmek zorundadır; spec sabitiyle ölü dal tamamen kaybolur — daha az register, daha iyi occupancy.

Parladığı yer

  • Kalite merdivenleri: QUALITY=1..4 bir örnekleme döngüsünü açar — tek SPIR-V’den düşük/orta/yüksek pipeline’lar.
  • Özellik anahtarları: HAS_NORMALMAP, USE_SRGB — tek shader kaynağı, sürücü her varyantı özelleştirir.
  • Workgroup boyutları: layout(local_size_x_id = 0) — GLSL’i düzenlemeden aynı compute kernel’ini 64 vs 256 thread için özelleştirin.
  • Codec/format varyantları: bir YUV→RGB dönüşüm shader’ı 420 vs 422 vs 444 alt örnekleme için özelleşir — stride matematiği immediate’lere katlanır.

p67 bağlantısı

GPU PNG encoder’ı W, H ve bytesPerRow için spec sabitleri kullanır — aynı shader her image boyutu için sabit-boyutlu bir filter kernel’ine derlenir. Dispatch sayısı değerlere bağlıdır, bu yüzden CPU onları geri okur ve dispatch’ten önce ikinci bir sabit seti iter.

Example: VulkanExamples/p67_pngenc

Pratik kurallar

  • constant_id değerleri aşama-başına isim alanındadır — vertex’teki id 0 ile fragment’teki bağımsızdır.
  • Bool’lar spec verisinde uint32_t‘dir — VkBool32 genişliği 1 değil.
  • Spec sabitleri draw başına değil pipeline creation’da özelleşir — değeri değiştirmek yeni bir VkPipeline yaratmak demektir (cache’leyen sürücülerde ucuz; pipeline cache yine tekilleştirir).
  • Varsayılan değerler (GLSL’deki = 2) uygulama ezmezse uygulanır — “sağlıklı fallback” için kullanışlı.
  • Tüm optimizasyonlar hayatta kalmaz: bir sürücü nadiren-kullanılan bir spec-const dalını katlamayı atlayabilir — ama literali her zaman alır.

Check out similar posts by category: Vulkan C++