Kare hızının düzenli olması, akıcı bir oyun deneyimi sunmak için çok önemlidir. Kare hızı, karelerin düzenli aralıklarla gösterilmesini sağlayarak takılmayı ve giriş gecikmesini en aza indirir. Android Frame Pacing kitaplığı (Swappy) çoğu oyun için önerilen üst düzey çözüm olsa da Vulkan uzantıları aracılığıyla alt düzey kontrol mümkündür.
Kare sunumu üzerinde hassas kontrol sağlamak için aşağıdaki Vulkan kare tempolandırma uzantılarını kullanın:
VK_GOOGLE_display_timing: Çerçevelerin belirli zamanlarda sunulmasını planlamaya ve oluşturma döngüsünü ayarlamak için geçmiş sunum zamanlarını sorgulamaya olanak tanır.VK_EXT_present_timing: Android 17 (API düzeyi 37) ve sonraki sürümlerde kullanıma sunulan, sunum istekleri için kapsamlı zamanlama geri bildirimi sağlayan daha yeni ve standartlaştırılmış bir uzantı
VK_GOOGLE_display_timing uzantısı daha eski bir uzantıdır ve daha geniş bir Android cihaz yelpazesinde desteklenir. Ancak daha fazla özellik ve daha ayrıntılı zamanlama bilgisi sunduğu için daha yeni cihazları hedeflerken VK_EXT_present_timing tercih edilir.
VK_GOOGLE_display_timing
VK_GOOGLE_display_timing uzantısı, uygulamaların şunları yapmasına olanak tanır:
- Bir ekranın yenileme döngüsünün süresini sorgulama
- Her kare için seçilen bir sunum zamanı belirtin
- Geri bildirim döngüsü uygulamak için geçmiş karelerin gerçek sunum sürelerini sorgulama
Bu uzantı, Swappy'yi kullanmak yerine kendi kare hızlandırma algoritmasını uygulayan oyunlar için kullanışlıdır.
Uzantıyı etkinleştirme
VK_GOOGLE_display_timing özelliğini kullanmak için Vulkan cihazını oluştururken bu özelliği etkinleştirin. Uzantıyı etkinleştirmeden önce fiziksel cihaz tarafından desteklendiğini doğrulayın:
// Check for extension support
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, availableExtensions.data());
bool supported = false;
for (const auto& ext : availableExtensions) {
if (strcmp(ext.extensionName, VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME) == 0) {
supported = true;
break;
}
}
if (supported) {
// Add to your enabled extensions list when calling vkCreateDevice
enabledDeviceExtensions.push_back(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
}
Sorgu görüntüleme yenileme süresi
vkGetRefreshCycleDurationGOOGLE kullanarak bir takas zinciriyle ilişkili ekranın yenileme süresini sorgulayabilirsiniz:
VkRefreshCycleDurationGOOGLE refreshCycle;
vkGetRefreshCycleDurationGOOGLE(device, swapchain, &refreshCycle);
// refreshCycle.refreshDuration is the duration in nanoseconds
Çerçeve sunumunu planlama
Bir karenin ne zaman gösterileceğini belirtmek için vkQueuePresentKHR çağrılırken VkPresentInfoKHR pNext zincirine bir VkPresentTimesInfoGOOGLE yapısı ekleyin:
VkPresentTimeGOOGLE presentTime = {};
presentTime.presentID = frameIndex; // Unique ID for this frame
presentTime.desiredPresentTime = targetTimeNs; // Target time in nanoseconds (CLOCK_MONOTONIC)
VkPresentTimesInfoGOOGLE presentTimesInfo = {};
presentTimesInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE;
presentTimesInfo.swapchainCount = 1;
presentTimesInfo.pTimes = &presentTime;
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = &presentTimesInfo;
// ... populate other presentInfo fields ...
vkQueuePresentKHR(queue, &presentInfo);
desiredPresentTime, sistemin tekdüze saatinden (CLOCK_MONOTONIC) alınan bir zaman damgası olmalıdır. Bu hedef süreyi, ekranın yenileme hızına ve geçmiş karelerin gerçek sunum sürelerine göre hesaplayın. Android'de, 0 olan veya 1 saniyeden daha uzun bir gelecekteki desiredPresentTime değeri yoksayılır.
VK_GOOGLE_display_timing, tüm zaman damgalarının aynı saatten kaynaklandığını varsayar.
Android'de hem VK_GOOGLE_display_timing hem de VK_EXT_present_timing ile ilgili tüm zaman damgalarında CLOCK_MONOTONIC kullanılır. (VK_EXT_present_timing birden fazla zaman alanını desteklese de Android'de farklı saatler kullanmak gerekli değildir.)
Geçmiş sunum zamanlarını sorgulama
Kare hızınızı ayarlamak için vkGetPastPresentationTimingGOOGLE işlevini kullanarak geçmiş karelerin ne zaman gösterildiğini sorgulayın:
uint32_t timingCount = 0;
// Query the number of available timings
vkGetPastPresentationTimingGOOGLE(device, swapchain, &timingCount, nullptr);
if (timingCount > 0) {
std::vector<VkPastPresentationTimingGOOGLE> presentationTimings(timingCount);
vkGetPastPresentationTimingGOOGLE(device, swapchain, &timingCount, presentationTimings.data());
for (const auto& timing : presentationTimings) {
// Use timing information to adjust your pacing algorithm
// timing.presentID identifies the frame
// timing.actualPresentTime is when the frame was displayed (nanoseconds)
// timing.earliestPresentTime is the earliest the frame could have been displayed
// timing.presentMargin is the slack time between GPU completion and presentation
}
}
actualPresentTime ile desiredPresentTime değerlerini karşılaştırarak karelerin çok erken mi yoksa çok geç mi geldiğini belirleyebilir ve oluşturma döngünüzü buna göre ayarlayabilirsiniz.
Kod örneği
VK_GOOGLE_display_timing'nın Vulkan oluşturucuya nasıl entegre edileceğine dair eksiksiz bir çalışma örneği için Android Oyun SDK'sı deposundaki küp demosuna bakın:
Vulkan Cube Demo with Display Timing
Bu demoda, uzantının nasıl etkinleştirileceği, hedef sunum sürelerinin nasıl hesaplanacağı ve kararlı bir kare hızı sağlamak için geçmiş sunum zamanlamalarından gelen geri bildirimlerin nasıl işleneceği gösterilmektedir.
VK_EXT_present_timing
Vulkan'da kullanıma sunulan ve Android 17 ile sonraki sürümlerde desteklenen VK_EXT_present_timing uzantısı, kare sunumu hakkında ayrıntılı geri bildirim almanın standartlaştırılmış ve daha sağlam bir yoludur. VK_GOOGLE_display_timing'daki kavramların yerini alır ve bu kavramları genişletir.
VK_EXT_present_timing planının başlıca avantajları şunlardır:
- Standartlaştırılmış API: Resmi Khronos Vulkan uzantı setinin bir parçasıdır.
- Ayrıntılı aşama sorguları: Sunu ardışık düzeninin belirli aşamalarındaki zaman damgalarının sorgulanmasına olanak tanır (örneğin, karenin kuyruktan çıkarıldığı, ilk pikselin ekrana gönderildiği ve ilk pikselin görünür hale geldiği zaman).
- Zaman alanı desteği: Farklı zaman alanlarını (ör. sistem saati, GPU saati) destekler ve bunlar arasında kalibrasyon yapılmasına olanak tanır. Zaman alanı, zaman damgalarını ölçmek için kullanılan belirli bir saat kaynağını veya zaman tabanını ifade eder. Android'de tüm ilgili zaman damgaları
CLOCK_MONOTONICkullanır. Bu nedenle, birden fazla zaman alanı kullanmak gerekli değildir. VK_KHR_present_id2ile entegrasyon: Sunum isteklerini tanımlamak için standartlaştırılmış VkPresentId2KHR kullanılır.
Uzantıyı etkinleştirme
VK_EXT_present_timing özelliğini kullanmak için cihaz oluşturma sırasında bu özelliği ve ön koşulu olan VK_KHR_present_id2'ı etkinleştirmeniz gerekir. Ayrıca, fiziksel cihaz özelliklerinin desteklenip desteklenmediğini de kontrol etmeniz gerekir:
VkPhysicalDevicePresentId2FeaturesKHR presentId2Features = {};
presentId2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_2_FEATURES_KHR;
VkPhysicalDevicePresentTimingFeaturesEXT presentTimingFeatures = {};
presentTimingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT;
presentTimingFeatures.pNext = &presentId2Features;
VkPhysicalDeviceFeatures2 features2 = {};
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features2.pNext = &presentTimingFeatures;
vkGetPhysicalDeviceFeatures2(physicalDevice, &features2);
if (presentTimingFeatures.presentTiming && presentId2Features.presentId2) {
// Enable VK_EXT_present_timing and VK_KHR_present_id2
enabledDeviceExtensions.push_back(VK_EXT_PRESENT_TIMING_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_KHR_PRESENT_ID_2_EXTENSION_NAME);
}
Bu özellik kontrollerinden biri başarısız olursa (presentTiming veya presentId2 yanlışsa) cihaz ya da sürücü VK_EXT_present_timing'ı veya ön koşulunu desteklemez. Bu durumda uygulamanız VK_EXT_present_timing kullanamaz ve VK_GOOGLE_display_timing'ye (destekleniyorsa) geri dönmeli veya Swappy gibi varsayılan kare hızını ayarlama mekanizmalarını kullanmalıdır.
Takas zincirinde sunum zamanlamasını etkinleştirme
Takas zincirini oluştururken VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT işaretini ayarlayarak sunum zamanlamasını açıkça etkinleştirin:
VkSwapchainCreateInfoKHR swapchainCreateInfo = {};
swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainCreateInfo.flags = VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT;
// ... populate other fields ...
vkCreateSwapchainKHR(device, &swapchainCreateInfo, nullptr, &swapchain);
Mevcut kimlikleri ilişkilendirme
Sunum yaparken VkPresentId2KHR (VK_KHR_present_id2 uzantısının bir parçası) kullanarak her kareyle benzersiz bir kimlik ilişkilendirin:
uint64_t presentId = frameIndex; // Unique, monotonically increasing ID
VkPresentId2KHR presentIdInfo = {};
presentIdInfo.sType = VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR;
presentIdInfo.swapchainCount = 1;
presentIdInfo.pPresentIds = &presentId;
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = &presentIdInfo;
// ... populate other presentInfo fields ...
vkQueuePresentKHR(queue, &presentInfo);
Sorgu takas zinciri zamanlama özellikleri
Yenileme süresi gibi takas zinciri zamanlama özelliklerini vkGetSwapchainTimingPropertiesEXT kullanarak sorgulayın:
VkSwapchainTimingPropertiesEXT timingProperties = {};
timingProperties.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_TIMING_PROPERTIES_EXT;
uint64_t propertiesCounter = 0;
vkGetSwapchainTimingPropertiesEXT(device, swapchain, &timingProperties, &propertiesCounter);
// timingProperties.refreshDuration is the duration in nanoseconds
Desteklenen zaman alanlarına sorgu gönderme
]
Daha önce belirtildiği gibi, zaman alanı belirli bir saat kaynağını veya zaman tabanını ifade eder.
VK_EXT_present_timing birden fazla zaman alanını destekleyip bunlar arasında kalibrasyon yapılmasına olanak tanısa da Android'de farklı saatler kullanmak gerekli değildir. Bunun nedeni, ilgili tüm zaman damgalarının CLOCK_MONOTONIC kullanmasıdır. Takas zinciriniz için desteklenen zaman alanlarına sorgu gönderin:
VkSwapchainTimeDomainPropertiesEXT timeDomainProps = {};
timeDomainProps.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_TIME_DOMAIN_PROPERTIES_EXT;
// Query the count first
vkGetSwapchainTimeDomainPropertiesEXT(device, swapchain, &timeDomainProps, nullptr);
std::vector<VkTimeDomainKHR> timeDomains(timeDomainProps.timeDomainCount);
std::vector<uint64_t> timeDomainIds(timeDomainProps.timeDomainCount);
timeDomainProps.pTimeDomains = timeDomains.data();
timeDomainProps.pTimeDomainIds = timeDomainIds.data();
// Populate the data
vkGetSwapchainTimeDomainPropertiesEXT(device, swapchain, &timeDomainProps, nullptr);
İstenen hedef sunum zamanı
Bir karenin belirli bir zamanda sunulmasını istemek için VkPresentTimingInfoEXT yapısını VkPresentInfoKHR öğenize zincirleyin.
VkPresentTimingInfoEXT timingInfo = {};
timingInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT;
timingInfo.flags = VK_PRESENT_TIMING_INFO_PRESENT_AT_RELATIVE_TIME_BIT_EXT; // Or absolute if supported
timingInfo.targetTime = targetTime; // Time value
timingInfo.timeDomainId = timeDomainIds[0]; // Use a supported time domain ID
timingInfo.presentStageQueries = VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT;
VkPresentTimingsInfoEXT presentTimingsInfo = {};
presentTimingsInfo.sType = VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT;
presentTimingsInfo.swapchainCount = 1;
presentTimingsInfo.pTimingInfos = &timingInfo;
// Chain to VkPresentId2KHR
presentIdInfo.pNext = &presentTimingsInfo;
Daha önce de belirtildiği gibi, Android'de 0 değeri veya 1 saniyeden daha uzun bir süre sonraki hedef zaman damgaları targetTime değeri yoksayılır.
Geçmiş sunum zamanlamalarını sorgulama
Geçmiş sunumlarla ilgili ayrıntılı zamanlama bilgilerini sorgulamak için önce vkSetSwapchainPresentTimingQueueSizeEXT kullanarak zamanlama sırası boyutunu yapılandırın, ardından vkGetPastPresentationTimingEXT kullanarak zamanlamaları alın:
// Set the size of the timing queue (do this during initialization)
vkSetSwapchainPresentTimingQueueSizeEXT(device, swapchain, 10); // Keep last 10 frames
// ... later in your frame loop ...
VkPastPresentationTimingInfoEXT pastTimingInfo = {};
pastTimingInfo.sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_INFO_EXT;
pastTimingInfo.swapchain = swapchain;
VkPastPresentationTimingPropertiesEXT pastProperties = {};
pastProperties.sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_PROPERTIES_EXT;
// First query to get the count of available timings
vkGetPastPresentationTimingEXT(device, &pastTimingInfo, &pastProperties);
if (pastProperties.presentationTimingCount > 0) {
std::vector<VkPastPresentationTimingEXT> timings(pastProperties.presentationTimingCount);
pastProperties.pPresentationTimings = timings.data();
// Populate the timings
vkGetPastPresentationTimingEXT(device, &pastTimingInfo, &pastProperties);
for (const auto& timing : timings) {
// timing.presentId identifies the frame (matches the presentId you set)
// timing.targetTime is the requested target time
// If you requested stage queries, you can inspect timing.pPresentStages
}
}
Kod örneği
VK_EXT_present_timing için eksiksiz bir entegrasyon örneği ve uygunluk testleri için Vulkan CTS (Compatibility Test Suite) deposundaki deqp (Draw Elements Quality Program) testlerine bakın:
Vulkan CTS Sunum Zamanlaması Testleri
Bu testler, sunum zamanlaması için takas zincirlerinin nasıl yapılandırılacağını, hedef zamanların nasıl ayarlanacağını ve farklı zaman alanlarında bildirilen sunum zaman damgalarının doğruluğunun nasıl doğrulanacağını gösterir.