गेमिंग का शानदार अनुभव देने के लिए, फ़्रेम पेसिंग बहुत ज़रूरी है. फ़्रेम पेसिंग से यह पक्का किया जाता है कि फ़्रेम नियमित अंतराल पर दिखें. इससे गेम के अटकने और इनपुट में लगने वाले समय को कम किया जा सकता है. ज़्यादातर गेम के लिए, Android फ़्रेम पेसिंग लाइब्रेरी (Swappy) का इस्तेमाल करने का सुझाव दिया जाता है. हालांकि, Vulkan एक्सटेंशन के ज़रिए लो-लेवल कंट्रोल उपलब्ध होता है.
फ़्रेम प्रज़ेंटेशन को सटीक तरीके से कंट्रोल करने के लिए, Vulkan के इन फ़्रेम पेसिंग एक्सटेंशन का इस्तेमाल करें:
VK_GOOGLE_display_timing: इसकी मदद से, फ़्रेम को किसी खास समय पर दिखाने के लिए शेड्यूल किया जा सकता है. साथ ही, रेंडरिंग लूप को अडजस्ट करने के लिए, पिछली बार फ़्रेम दिखाए जाने के समय के बारे में क्वेरी की जा सकती हैVK_EXT_present_timing: यह एक नया और स्टैंडर्ड एक्सटेंशन है. यह प्रज़ेंटेशन के अनुरोधों के लिए, समय से जुड़ी पूरी जानकारी देता है. इसे Android 17 (एपीआई लेवल 37) और इसके बाद के वर्शन में पेश किया गया था
VK_GOOGLE_display_timing एक्सटेंशन पुराना है और यह ज़्यादातर Android डिवाइसों पर काम करता है. हालांकि, नए डिवाइसों को टारगेट करने के लिए VK_EXT_present_timing का इस्तेमाल करना बेहतर होता है. ऐसा इसलिए, क्योंकि इसमें ज़्यादा सुविधाएं मिलती हैं. साथ ही, समय के बारे में ज़्यादा जानकारी मिलती है.
VK_GOOGLE_display_timing
VK_GOOGLE_display_timing एक्सटेंशन की मदद से, ऐप्लिकेशन ये काम कर सकते हैं:
- डिसप्ले के रीफ़्रेश साइकल की अवधि के बारे में क्वेरी करना
- हर फ़्रेम के लिए, प्रज़ेंटेशन का समय तय करें
- फ़ीडबैक लूप लागू करने के लिए, पिछले फ़्रेम के असल प्रज़ेंटेशन समय के बारे में क्वेरी करना
यह एक्सटेंशन उन गेम के लिए काम का है जो Swappy का इस्तेमाल करने के बजाय, फ़्रेम पेसिंग के लिए अपने एल्गोरिदम का इस्तेमाल करते हैं.
एक्सटेंशन चालू करना
VK_GOOGLE_display_timing का इस्तेमाल करने के लिए, Vulkan डिवाइस बनाते समय इसे चालू करें. एक्सटेंशन चालू करने से पहले, पुष्टि करें कि यह फ़िज़िकल डिवाइस के साथ काम करता है:
// 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);
}
क्वेरी के डिसप्ले को रीफ़्रेश करने की अवधि
vkGetRefreshCycleDurationGOOGLE का इस्तेमाल करके, स्वैपचेन से जुड़े डिसप्ले के रीफ़्रेश होने की अवधि के बारे में क्वेरी की जा सकती है:
VkRefreshCycleDurationGOOGLE refreshCycle;
vkGetRefreshCycleDurationGOOGLE(device, swapchain, &refreshCycle);
// refreshCycle.refreshDuration is the duration in nanoseconds
फ़्रेम प्रज़ेंटेशन शेड्यूल करना
यह तय करने के लिए कि फ़्रेम कब दिखना चाहिए, vkQueuePresentKHR को कॉल करते समय, VkPresentInfoKHR की pNext चेन में VkPresentTimesInfoGOOGLE स्ट्रक्चर अटैच करें:
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, सिस्टम के मोनोटोनीक क्लॉक (CLOCK_MONOTONIC) का टाइमस्टैंप होना चाहिए. इस टारगेट टाइम का हिसाब, डिसप्ले के रीफ़्रेश रेट और पिछले फ़्रेम के असल प्रज़ेंटेशन टाइम के आधार पर लगाया जाता है. Android पर, 0 का desiredPresentTime या आने वाले समय में एक सेकंड से ज़्यादा का कोई भी टाइमस्टैंप अनदेखा कर दिया जाता है.
VK_GOOGLE_display_timing यह मानता है कि सभी टाइमस्टैंप एक ही घड़ी से लिए गए हैं.
Android पर, VK_GOOGLE_display_timing और VK_EXT_present_timing, दोनों से जुड़े सभी टाइमस्टैंप के लिए CLOCK_MONOTONIC का इस्तेमाल किया जाता है. (VK_EXT_present_timing में एक से ज़्यादा टाइम डोमेन इस्तेमाल किए जा सकते हैं. हालांकि, Android पर अलग-अलग घड़ियों का इस्तेमाल करना ज़रूरी नहीं है).
पिछली बार प्रज़ेंटेशन कब किया गया था, इस बारे में क्वेरी करना
अपने फ़्रेम पेसिंग लूप को अडजस्ट करने के लिए, vkGetPastPresentationTimingGOOGLE का इस्तेमाल करें. इससे यह पता चलेगा कि पिछले फ़्रेम कब दिखाए गए थे:
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 की तुलना desiredPresentTime से करके, यह पता लगाया जा सकता है कि फ़्रेम बहुत जल्दी या बहुत देर से तो नहीं आ रहे हैं. इसके बाद, रेंडरिंग लूप को उसी के हिसाब से अडजस्ट किया जा सकता है.
कोड का उदाहरण
VK_GOOGLE_display_timing को Vulkan रेंडरर में इंटिग्रेट करने के तरीके का पूरा उदाहरण देखने के लिए, Android Game SDK के डेटा स्टोर करने की जगह में मौजूद क्यूब डेमो देखें:
डिसप्ले टाइमिंग के साथ Vulkan Cube Demo
इस डेमो में बताया गया है कि एक्सटेंशन को कैसे चालू करें, टारगेट प्रज़ेंटेशन टाइम का हिसाब कैसे लगाएं, और फ़्रेम रेट को स्थिर रखने के लिए, पिछली प्रज़ेंटेशन टाइमिंग से मिले सुझावों को कैसे प्रोसेस करें.
VK_EXT_present_timing
Vulkan में पेश किया गया और Android 17 और इसके बाद के वर्शन में काम करने वाला VK_EXT_present_timing एक्सटेंशन, फ़्रेम प्रज़ेंटेशन के बारे में ज़्यादा जानकारी वाला फ़ीडबैक पाने का एक स्टैंडर्ड और बेहतर तरीका है. यह VK_GOOGLE_display_timing में मौजूद कॉन्सेप्ट की जगह लेता है और उन्हें बेहतर तरीके से समझाता है.
VK_EXT_present_timing के मुख्य फ़ायदे:
- स्टैंडर्ड एपीआई: Khronos Vulkan एक्सटेंशन सेट का हिस्सा
- स्टेज के बारे में ज़्यादा जानकारी वाली क्वेरी: इससे, प्रज़ेंटेशन पाइपलाइन के किसी खास स्टेज पर टाइमस्टैंप के बारे में क्वेरी की जा सकती है. उदाहरण के लिए, फ़्रेम को कब हटाया गया, पहले पिक्सल को डिसप्ले पर कब भेजा गया, और पहला पिक्सल कब दिखने लगा
- टाइम डोमेन के साथ काम करता है: अलग-अलग टाइम डोमेन के साथ काम करता है. जैसे, सिस्टम टाइम, जीपीयू टाइम. साथ ही, इनके बीच कैलिब्रेट करने की सुविधा देता है. टाइम डोमेन, किसी खास क्लॉक सोर्स या टाइम बेस को दिखाता है. इसका इस्तेमाल टाइमस्टैंप को मेज़र करने के लिए किया जाता है.
Android पर, सभी काम के टाइमस्टैंप
CLOCK_MONOTONICका इस्तेमाल करते हैं. इसलिए, एक से ज़्यादा टाइम डोमेन का इस्तेमाल करना ज़रूरी नहीं है. VK_KHR_present_id2के साथ इंटिग्रेशन: यह स्टैंडर्ड किए गए VkPresentId2KHR का इस्तेमाल करके, प्रज़ेंटेशन के अनुरोधों की पहचान करता है
एक्सटेंशन चालू करना
VK_EXT_present_timing का इस्तेमाल करने के लिए, आपको डिवाइस बनाते समय इसे और इसकी ज़रूरी शर्त VK_KHR_present_id2 को चालू करना होगा. आपको यह भी देखना चाहिए कि डिवाइस की सुविधाओं के लिए, फ़िज़िकल डिवाइस का इस्तेमाल किया जा सकता है या नहीं:
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);
}
अगर इनमें से कोई भी सुविधा काम नहीं करती है (presentTiming या presentId2 गलत है), तो इसका मतलब है कि डिवाइस या ड्राइवर, VK_EXT_present_timing या इसकी ज़रूरी शर्तों के साथ काम नहीं करता. इस मामले में, आपका ऐप्लिकेशन VK_EXT_present_timing का इस्तेमाल नहीं कर सकता. साथ ही, इसे VK_GOOGLE_display_timing (अगर यह सुविधा काम करती है) पर वापस आ जाना चाहिए या फ़्रेम पेसिंग के डिफ़ॉल्ट तरीकों, जैसे कि Swappy पर निर्भर रहना चाहिए.
स्वैपचेन पर मौजूदा टाइमिंग की सुविधा चालू करें
स्वैपचेन बनाते समय, VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT फ़्लैग सेट करके, प्रेजेंट टाइमिंग को साफ़ तौर पर चालू करें:
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);
मौजूदा असोसिएट आईडी
प्रज़ेंट करते समय, VkPresentId2KHR (VK_KHR_present_id2 एक्सटेंशन का हिस्सा) का इस्तेमाल करके, हर फ़्रेम के साथ एक यूनीक आईडी जोड़ें:
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);
स्वैपचेन के समय से जुड़ी प्रॉपर्टी के बारे में क्वेरी करना
vkGetSwapchainTimingPropertiesEXT का इस्तेमाल करके, स्वैपचेन की टाइमिंग प्रॉपर्टी के बारे में क्वेरी करें. जैसे, रीफ़्रेश होने की अवधि:
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
क्वेरी के लिए, समय के साथ काम करने वाले डोमेन
]
जैसा कि पहले बताया गया है, टाइम डोमेन किसी खास क्लॉक सोर्स या टाइम बेस को दिखाता है.
VK_EXT_present_timing कई टाइम डोमेन के साथ काम करता है और इनके बीच कैलिब्रेट करने की अनुमति देता है. हालांकि, Android पर अलग-अलग घड़ियों का इस्तेमाल करना ज़रूरी नहीं है, क्योंकि सभी काम के टाइमस्टैंप CLOCK_MONOTONIC का इस्तेमाल करते हैं. अपनी स्वैपचेन के लिए, काम करने वाले टाइम डोमेन के बारे में क्वेरी करें:
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);
प्रज़ेंटेशन के लिए अनुरोध किया गया टारगेट समय
किसी फ़्रेम को किसी खास समय पर प्रज़ेंट करने का अनुरोध करने के लिए, अपने VkPresentInfoKHR में VkPresentTimingInfoEXT स्ट्रक्चर को जोड़ें.
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;
जैसा कि पहले बताया गया है, Android पर 0 का targetTime या आने वाले समय में एक सेकंड से ज़्यादा का कोई भी टारगेट टाइमस्टैंप अनदेखा कर दिया जाता है.
पिछले प्रज़ेंटेशन के समय के बारे में क्वेरी करना
पिछले प्रज़ेंटेशन के लिए, समय की ज़्यादा जानकारी वाली क्वेरी करने के लिए, सबसे पहले vkSetSwapchainPresentTimingQueueSizeEXT का इस्तेमाल करके, टाइमिंग क्यू का साइज़ कॉन्फ़िगर करें. इसके बाद, vkGetPastPresentationTimingEXT का इस्तेमाल करके, टाइमिंग वापस पाएं:
// 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
}
}
कोड का उदाहरण
VK_EXT_present_timing के इंटिग्रेशन का पूरा उदाहरण और कन्फ़ॉर्मेंस टेस्ट देखने के लिए, Vulkan CTS (Compatibility Test Suite) रिपॉज़िटरी में deqp (Draw Elements Quality Program) टेस्ट देखें:
Vulkan CTS Present Timing Tests
इन टेस्ट से पता चलता है कि प्रेजेंट टाइमिंग के लिए स्वैपचेन को कैसे कॉन्फ़िगर किया जाता है, टारगेट टाइम कैसे सेट किए जाते हैं, और अलग-अलग टाइम डोमेन में रिपोर्ट किए गए प्रज़ेंटेशन टाइमस्टैंप की सटीकता की पुष्टि कैसे की जाती है.