프레임 속도는 원활한 게임 환경을 제공하는 데 매우 중요합니다. 프레임 속도는 프레임이 일정한 간격으로 표시되도록 하여 끊김 현상과 입력 지연 시간을 최소화합니다. Android Frame Pacing 라이브러리 (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);
}
디스플레이 새로고침 기간 쿼리
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과 비교하면 프레임이 너무 일찍 또는 너무 늦게 도착하는지 확인하고 그에 따라 렌더링 루프를 조정할 수 있습니다.
코드 예
Vulkan 렌더러에 VK_GOOGLE_display_timing을 통합하는 방법에 관한 완전한 작업 예는 Android 게임 SDK 저장소의 큐브 데모 를 참고하세요.
이 데모는 확장 프로그램을 사용 설정하고, 타겟 프레젠테이션 시간을 계산하고, 이전 프레젠테이션 타이밍의 의견을 처리하여 안정적인 프레임 속도를 유지하는 방법을 보여줍니다.
VK_EXT_present_timing
Vulkan에 도입되고 Android 17 이상에서 지원되는 VK_EXT_present_timing 확장 프로그램은 프레임 프레젠테이션에 관한 자세한 의견을 얻는 표준화되고 더 강력한 방법입니다. VK_GOOGLE_display_timing의 개념을 대체하고 확장합니다.
VK_EXT_present_timing의 주요 이점은 다음과 같습니다.
- 표준화된 API: 공식 Khronos Vulkan 확장 프로그램 세트의 일부입니다.
- 세부 스테이지 쿼리: 프레젠테이션 파이프라인의 특정 스테이지에서 타임스탬프를 쿼리할 수 있습니다 (예: 프레임이 대기열에서 삭제된 시점, 첫 번째 픽셀이 디스플레이로 전송된 시점, 첫 번째 픽셀이 표시된 시점).
- 시간 도메인 지원: 다양한 시간 도메인 (예:
시스템 시간, 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가 false임) 기기 또는 드라이버가 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);
현재 ID 연결
프레젠테이션할 때 VK_KHR_present_id2 확장 프로그램의 일부인 VkPresentId2KHR을 사용하여 각 프레임에 고유 ID를 연결합니다.
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를 사용하여 새로고침 기간과 같은 스왑체인 타이밍 속성을 쿼리합니다. 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);
타겟 프레젠테이션 시간 요청
프레임이 특정 시간에 표시되도록 요청하려면
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의 완전한 통합 예와 적합성 테스트는 Vulkan CTS (호환성 테스트 모음) 저장소의 deqp (Draw Elements Quality Program) 테스트를 참고하세요.
이러한 테스트는 현재 타이밍을 위해 스왑체인을 구성하고, 타겟 시간을 설정하고, 다양한 시간 도메인에서 보고된 프레젠테이션 타임스탬프의 정확성을 확인하는 방법을 보여줍니다.