การเว้นระยะเฟรมเป็นสิ่งสำคัญในการมอบประสบการณ์การเล่นเกมที่ราบรื่น การเว้นระยะเฟรมช่วยให้มั่นใจได้ว่า เฟรมจะแสดงในช่วงเวลาปกติ ซึ่งจะช่วยลดอาการกระตุกและเวลาในการตอบสนองต่ออินพุต แม้ว่าไลบรารีการเว้นระยะเฟรมของ Android (Swappy) จะเป็นโซลูชันระดับสูงที่แนะนำสำหรับเกมส่วนใหญ่ แต่การควบคุมระดับต่ำก็พร้อมใช้งานผ่านส่วนขยาย Vulkan
ใช้ส่วนขยายการเว้นระยะเฟรม Vulkan ต่อไปนี้เพื่อ ควบคุมการนำเสนอเฟรมได้อย่างแม่นยำ
VK_GOOGLE_display_timing: อนุญาตให้กำหนดเวลา เฟรมที่จะนำเสนอในเวลาที่เฉพาะเจาะจง และค้นหาเวลาการนำเสนอที่ผ่านมา เพื่อปรับลูปการแสดงผลVK_EXT_present_timing: ส่วนขยายใหม่ที่ได้มาตรฐาน ซึ่งให้ความคิดเห็นด้านเวลาที่ครอบคลุมสำหรับคำขอการนำเสนอ เปิดตัวใน Android 17 (ระดับ API 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);
}
ระยะเวลาการรีเฟรชการแสดงผลของคำค้นหา
คุณสามารถค้นหาระยะเวลารีเฟรชของจอแสดงผลที่เชื่อมโยงกับ Swapchain ได้โดยใช้ vkGetRefreshCycleDurationGOOGLE
VkRefreshCycleDurationGOOGLE refreshCycle;
vkGetRefreshCycleDurationGOOGLE(device, swapchain, &refreshCycle);
// refreshCycle.refreshDuration is the duration in nanoseconds
กำหนดเวลาการนำเสนอเฟรม
หากต้องการระบุเวลาที่ควรแสดงเฟรม ให้แนบโครงสร้าง VkPresentTimesInfoGOOGLE ไปยังเชน pNext ของ VkPresentInfoKHR
เมื่อเรียกใช้ vkQueuePresentKHR
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 ระบบจะละเว้นdesiredPresentTime 0 หรือการประทับเวลาใดๆ ที่มากกว่า 1 วินาทีในอนาคต
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 พร้อมการกำหนดเวลาการแสดงผล
การสาธิตนี้แสดงวิธีเปิดใช้ส่วนขยาย คำนวณเวลาเป้าหมายในการนำเสนอ และประมวลผลความคิดเห็นจากเวลาในการนำเสนอที่ผ่านมาเพื่อรักษาอัตราเฟรมที่เสถียร
VK_EXT_present_timing
เปิดตัวใน Vulkan และรองรับใน Android 17 ขึ้นไป ส่วนขยาย
VK_EXT_present_timing เป็นวิธีที่ได้มาตรฐานและมีประสิทธิภาพมากขึ้นในการรับ
ความคิดเห็นโดยละเอียดเกี่ยวกับการนำเสนอเฟรม โดยจะมาแทนที่และขยายแนวคิดใน VK_GOOGLE_display_timing
ข้อดีหลักๆ ของ VK_EXT_present_timing มีดังนี้
- API มาตรฐาน: ส่วนหนึ่งของชุดส่วนขยาย Vulkan อย่างเป็นทางการของ Khronos
- การค้นหาระดับละเอียด: อนุญาตให้ค้นหาการประทับเวลาในขั้นตอนที่เฉพาะเจาะจง ของไปป์ไลน์การนำเสนอ (เช่น เมื่อมีการนำเฟรมออกจากคิว เมื่อ ส่งพิกเซลแรกไปยังจอแสดงผล และเมื่อพิกเซลแรกปรากฏ ให้เห็น)
- รองรับโดเมนเวลา: รองรับโดเมนเวลาต่างๆ (เช่น เวลาของระบบ เวลาของ GPU) และอนุญาตให้ปรับเทียบระหว่างโดเมนเวลาเหล่านั้น โดเมนเวลา
แสดงถึงแหล่งที่มาของนาฬิกาหรือฐานเวลาที่เฉพาะเจาะจงซึ่งใช้ในการวัดการประทับเวลา
ใน 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
เปิดใช้การกำหนดเวลาการนำเสนอใน Swapchain
เมื่อสร้าง Swapchain ให้เปิดใช้เวลาในการนำเสนออย่างชัดเจนโดย
ตั้งค่าแฟล็ก 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);
สอบถามพร็อพเพอร์ตี้การกำหนดเวลาของ Swapchain
สอบถามพร็อพเพอร์ตี้การกำหนดเวลาของ Swapchain เช่น ระยะเวลาการรีเฟรช โดยใช้ 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 ค้นหาโดเมนเวลาที่รองรับสำหรับ Swapchain ของคุณ
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);
ขอเวลาเป้าหมายในการนำเสนอ
หากต้องการขอให้แสดงเฟรมในเวลาที่เฉพาะเจาะจง ให้เชื่อมต่อโครงสร้าง a
VkPresentTimingInfoEXT กับ VkPresentInfoKHR
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 ระบบจะไม่สนใจ targetTime ที่มีค่าเป็น 0 หรือการประทับเวลาเป้าหมายใดๆ ที่อยู่ล่วงหน้ามากกว่า 1 วินาที
ค้นหาเวลาที่นำเสนอที่ผ่านมา
หากต้องการค้นหาข้อมูลช่วงเวลาโดยละเอียดสำหรับการนำเสนอที่ผ่านมา ให้กำหนดค่าขนาดคิวช่วงเวลาโดยใช้ 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ได้ที่การทดสอบ deqp (โปรแกรมคุณภาพขององค์ประกอบการวาด)
ในที่เก็บ Vulkan CTS (ชุดเครื่องมือทดสอบความเข้ากันได้)
การทดสอบเวลาในการนำเสนอของ CTS สำหรับ Vulkan
การทดสอบเหล่านี้แสดงให้เห็นวิธีกำหนดค่า Swapchain สำหรับเวลาปัจจุบัน ตั้งค่าเวลาเป้าหมาย และยืนยันความถูกต้องของไทม์สแตมป์การนำเสนอที่รายงานในโดเมนเวลาต่างๆ