Vulkan bellek modeli açıklaması: availability, visibility, scope ve OpMemoryBarrier

Vulkan’ın bellek modeli (VK_KHR_vulkan_memory_model ile formalize edilmiş, 1.2’de çekirdek) “invocation A’nın yazması invocation B’ye ne zaman görünür olur” sorusunu yanıtlar. C++‘ın bellek modelinden farklı olarak bölünmüştür: availability, visibility ve scope üç ayrı şeydir ve varsayılan hiçbirinin gerçekleşmemesidir.

Üç bileşen

model.txt
availability   — yazma fiziksel olarak cache'den çıktı ve görülebilir
visibility     — okuyucu gerçekten onu gözlemler (cache invalidation)
scope          — sıralamanın *kime* uygulandığı:
                 Subgroup / Workgroup / QueueFamily / Device / Instance

Available yapılmayan bir yazma, yazıcının cache’inden asla çıkmaz. Visibility scope’unda olmayan bir okuma, eski cache kopyasına çarpabilir. Doğru scope‘u olmayan bir atomik, kendi subgroup’u dışında hiçbir şeyi sıralayamaz.

SPIR-V’de görünümü

spirv.txt
OpMemoryBarrier  %scope  %semantics
OpAtomicStore    %ptr %scope %semantics %value
OpAtomicIAdd     %ptr %scope %semantics %val

scope:     Workgroup | QueueFamily | Device | Invocation
semantics: Acquire | Release | AcquireRelease |
           UniformMemory | SubgroupMemory | WorkgroupMemory |
           ImageMemory | OutputMemoryKHR | MakeAvailable |
           MakeVisible | SequentiallyConsistent

MakeAvailable/MakeVisible Vulkan’a özel flag’lerdir — C++ benzeri acquire/release’i, Vulkan’ın tüm cihaz boyunca ihtiyaç duyduğu cache-flush semantiğiyle genişletirler.

atomic.glsl
// GLSL — derleyici SPIR-V'yi seçer ama scope+semantics'i
// bellek niteleyicileri ve barrier'larla kontrol edersiniz:
layout(set=0,binding=0) coherent buffer Counters { uint c[]; } g_cnt;

void bump() {
    memoryBarrierBuffer();          // yazmalarımı görünür kıl
    atomicAdd(g_cnt.c[i], 1);       // relaxed atomik RMW
}

Buffer’daki coherent büyük anahtardır — okuma/yazmaları bellek alt sisteminden geçmeye zorlar (hâlâ device-scope, barrier’sız hâlâ sıralı tutarlı değil).

API tarafında flag’lerin anlamı

api.cpp
vk::PhysicalDeviceVulkanMemoryModelFeatures f{};
f.vulkanMemoryModel                            = VK_TRUE; // temel model
f.vulkanMemoryModelDeviceScope                 = VK_TRUE; // cihaz boyunca scope
f.vulkanMemoryModelAvailabilityVisibilityChains= VK_TRUE; // zincir geçişliliği

vulkanMemoryModel etkin değilse eski 1.2 öncesi kurallar geçerlidir: barrier’lar sadece bir queue submission içinde sıralar ve çoğu çapraz-shader iletişimi teknik olarak tanımsızdır. Onu etkinleştirmek “shader SSBO’ya yazar → başka shader acquire barrier ile görür"ü iyi tanımlı yapar.

Her zaman işe yarayan tarif

recipe.txt
üretici:   veri yaz → OpMemoryBarrier Device|UniformMemory|Release
                           |MakeAvailable
tüketici:  OpMemoryBarrier Device|UniformMemory|Acquire|MakeVisible
           → veri oku

queue seviyesi: vkQueueSubmit + fence/semaphore zaten availability+
                visibility içerir — shader seviyesi barrier'lar sadece
                *aynı submission* sıralaması için önemlidir.

Pratik kurallar

  • coherent ≠ senkronize — okuma/yazmalar L2’yi atlar ama sıralama hâlâ acquire/release ister; barrier olmadan okuyucu flag’i veriden önce görebilir.
  • GLSL atomiklerinde varsayılan scope Device’tir — güvenli ama yavaş; sayaç yerelse Workgroup‘a daraltın.
  • SequentiallyConsistent var ama neredeyse hiç ihtiyacınız olan şey değil — acquire/release gerçek vakaları maliyetin çok küçük bir kısmıyla kapsar.
  • Queue seviyesi senkron (semaphore, event, fence beklenen submit’ler) zaten availability+visibility sağlar — çapraz command-buffer sıralaması için shader barrier eklemeyin, o queue primitiflerinin işidir.
  • Çapraz-queue erişim için vkQueueFamilyOwnershipTransfer veya timeline semaphore kullanın — bellek modeli queue senkronizasyonunun yerini almaz, queue-içi görünürlüğü formalize eder.

Check out similar posts by category: Vulkan C++