“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
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_idsonuçlarıyla koreletargetTime— hedeflediğiniz zamanpPresentStages—{stage, time}‘lı birVkPresentStageTimeEXT[]: 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
// 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
// Ç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:
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:
waitForPresent(prev) → input örnekle → render → targetTime ile presentPratik kurallar
- Zamanlama swapchain başına opt-in’dir (
ePresentTimingEXTflag’i) — düz swapchain’ler hiçbir şey rapor etmez. timeDomainId‘leri init’te bir kezVkTimeDomainKHR‘a çevirin;vkGetCalibratedTimestampsKHRolmadan 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
MAILBOXile birleştirin; zamanlama o zaman ayarlanacak gerçek uçtan-uca sayıyı söyler. - Bu, eski
VK_GOOGLE_display_timing‘in (earliestPresentTime/presentMarginvardı) yerini alır — iki API’yi karıştırmayın.