VK_EXT_present_timing ile Vulkan'da ekran gecikmesini ölçme

“GPU bitti” (vkGetFenceStatus) “ekranda pikseller” demek değildir. Bitmiş bir render ile gerçek fotonlar arasında present engine kuyruğu, kompozitör ve scanout durur — çoğu zaman 1–3 refresh döngüsü gizli gecikme. VK_EXT_present_timing nihayet bu boşluğu ölçmenizi ve hatta present’leri hedef bir zaman için zamanlamanızı sağlar.

Size ne verir

timeline.txt
 submit → fence (GPU bitti) → [present kuyruğu] → scanout → görünür
         ^________________ ölçüldü mü? hayır   ^__________ bu kısım!

Uzantıyla, her vkQueuePresentKHR pNext‘inde bir VkPresentTimingsInfoEXT taşır ve sürücü her present için şunları içeren bir VkPastPresentationTimingEXT rapor eder:

  • presentId — VK_KHR_present_id sonuçlarıyla korele
  • targetTime — hedeflediğiniz zaman
  • pPresentStages — {stage, time}‘lı bir VkPresentStageTimeEXT[]: abone olduğunuz her presentation aşaması için bir zaman damgası (istek gönderildi, present edildi, ilk piksel çıktı, …)
  • timeDomain/timeDomainId — bu zamanların hangi saatte yaşadığı
  • reportComplete — istenen tüm aşamaların gelip gelmediği

Kurulum

setup.cpp
// Cihaz: VK_EXT_present_timing + feature struct'ını açın.
// Üç biti var — ihtiyacınız olanları isteyin:
vk::PhysicalDevicePresentTimingFeaturesEXT feat{};
feat.presentTiming          = VK_TRUE;
feat.presentAtAbsoluteTime  = VK_TRUE; // mutlak targetTime
feat.presentAtRelativeTime  = VK_TRUE; // "şu andan N ns sonra"
//   → vkCreateDevice pNext'e zincirle

// Bu surface'in neyi desteklediğini yoklayın — zincirleme yeri:
// vkGetPhysicalDeviceSurfaceCapabilities2KHR'ın çıktısı:
vk::PresentTimingSurfaceCapabilitiesEXT caps{};
vk::PhysicalDeviceSurfaceInfo2KHR sinfo{surf};
vk::SurfaceCapabilities2KHR sc2{};
sc2.pNext = ∩︀
dev.getSurfaceCapabilities2KHR(sinfo, &sc2);
//   caps.presentTimingSupported / .presentAtAbsoluteTimeSupported /
//   .presentStageQueries   (hangi aşamalar zaman damgalanabilir)

// Swapchain: creation flag ile katılın — pNext gerekmez:
vk::SwapchainCreateInfoKHR sci{};
sci.flags = vk::SwapchainCreateFlagBitsKHR::ePresentTimingEXT;
// … her zamanki gibi vkCreateSwapchainKHR

// Opsiyonel: geçmiş-presentation sonuçlarını buffer'layan kuyruğu büyütün
dev.setSwapchainPresentTimingQueueSizeEXT(swapchain, 64);

Gerçek present zamanlarını geri okuma

query.cpp
// Çağıranın ayırdığı sonuç dizisi + aşama dizisi:
vk::PresentStageTimeEXT     stages[8];
vk::PastPresentationTimingEXT results[4]{};
for (auto& r : results) {
    r.pPresentStages    = stages;      // (gerçek kod: sonuç başına dizi)
    r.presentStageCount = 8;
}
vk::PastPresentationTimingPropertiesEXT props{};
props.presentationTimingCount = 4;
props.pPresentationTimings    = results;

vk::PastPresentationTimingInfoEXT in{};
in.flags     = vk::PastPresentationTimingFlagBitsEXT
              ::eAllowPartialResults; // gecikenlerde bloklamayın
in.swapchain = swapchain;
dev.getPastPresentationTimingEXT(in, &props);

for (uint32_t i = 0; i < props.presentationTimingCount; ++i) {
    auto& r = results[i];
    for (uint32_t s = 0; s < r.presentStageCount; ++s)
        logLatency(r.pPresentStages[s].stage, r.pPresentStages[s].time);
}

vkGetSwapchainTimingPropertiesEXT swapchain için refreshDuration ve refreshInterval verir; vkGetSwapchainTimeDomainPropertiesEXT timeDomainId‘leri VkTimeDomainKHR saatlerine (device, CLOCK_MONOTONIC, …) eşler, böylece sayılar vkGetCalibratedTimestampsKHR + VkSwapchainCalibratedTimestampInfoEXT yoluyla CPU zaman damgalarıyla karşılaştırılabilir olur.

Hedef zaman için present etme

Daha güçlü özellik: her present’e bir targetTime iliştirmek — sürücü image’ı o anın/sonrasının refresh’ine kadar tutar. Bu, “istediğinde render et, bir sonraki vsync’e oturmasını dua et"i deterministik frame pacing’e çevirir:

target.cpp
vk::PresentTimingInfoEXT ti{};
ti.targetTime   = nextVsyncEstimate;   // bir swapchain zaman domain'inde
ti.timeDomainId = domainId;            // getSwapchainTimeDomainPropertiesEXT'ten
ti.presentStageQueries = vk::PresentStageFlagBitsEXT::ePresented;

vk::PresentTimingsInfoEXT ptis{};
ptis.swapchainCount = 1;
ptis.pTimingInfos   = &ti;
// vkQueuePresentKHR'nin VkPresentInfoKHR::pNext'ine zincirle

flags modu seçer: ePresentAtRelativeTime (targetTime = “şu andan itibaren”) veya ePresentAtNearestRefreshCycle (sonraki-yerine en yakın refresh’e oturt).

Zamanlama-sonuçları kuyruğu sonludur: vkQueuePresentKHR VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT döndürebilir — vkGetPastPresentationTimingEXT ile boşaltın (veya vkSetSwapchainPresentTimingQueueSizeEXT ile boyutu artırın).

Gecikme bütçesi muhasebesi

CPU’nun N frame ileriye koşmasını durdurmak için VK_KHR_present_wait (vkWaitForPresentKHR) ile eşleştirin: önceki frame present edilene kadar bekleyip sonra bir sonrakine başlayın — input sampling mümkün olan en geç anda olur:

budget.txt
waitForPresent(prev) → input örnekle → render → targetTime ile present

Pratik kurallar

  • Zamanlama swapchain başına opt-in’dir (ePresentTimingEXT flag’i) — düz swapchain’ler hiçbir şey rapor etmez.
  • timeDomainId‘leri init’te bir kez VkTimeDomainKHR‘a çevirin; vkGetCalibratedTimestampsKHR olmadan sayılar opak bir saattedir.
  • Wayland’de kompozitör takvimi sahiplenir — zamanlama verisi kompozitör presentation’ını yansıtır, ham scanout’u değil.
  • En düşük gecikme için MAILBOX ile birleştirin; zamanlama o zaman ayarlanacak gerçek uçtan-uca sayıyı söyler.
  • Bu, eski VK_GOOGLE_display_timing‘in (earliestPresentTime/presentMargin vardı) yerini alır — iki API’yi karıştırmayın.

Check out similar posts by category: Vulkan C++