@@ -33,18 +33,29 @@ Vulkan GPU query pool implementation.
3333#include < Methane/Instrumentation.h>
3434#include < Methane/Checks.hpp>
3535
36+ #include < algorithm>
37+ #include < array>
3638#include < chrono>
3739#include < limits>
40+ #include < span>
41+ #include < string_view>
42+ #include < vector>
43+ #include < fmt/ranges.h>
3844#include < magic_enum/magic_enum.hpp>
3945
40- static const vk::TimeDomainEXT g_vk_cpu_time_domain =
46+ // CPU time domains which can be used to calibrate GPU timestamps, in the order of preference.
47+ // The selected domain must return timestamps in the units which Data::GetQpcToNSecMultiplier()
48+ // converts to nanoseconds: QueryPerformanceCounter ticks on Windows and nanoseconds of the POSIX
49+ // monotonic clocks on all other platforms (where that multiplier is 1).
50+ static constexpr std::array g_vk_cpu_time_domains{
4151#if defined(_WIN32)
42- vk::TimeDomainEXT::eQueryPerformanceCounter;
43- #elif defined(__linux__) && defined CLOCK_MONOTONIC_RAW
44- vk::TimeDomainEXT::eClockMonotonicRaw;
52+ vk::TimeDomainEXT::eQueryPerformanceCounter
4553#else
46- static_cast <vk::TimeDomainEXT>(-1 );
54+ // MoltenVK exposes CLOCK_MONOTONIC_RAW only, while other Vulkan drivers may expose either of these.
55+ vk::TimeDomainEXT::eClockMonotonicRaw,
56+ vk::TimeDomainEXT::eClockMonotonic
4757#endif
58+ };
4859
4960namespace Methane ::Graphics::Vulkan
5061{
@@ -70,6 +81,33 @@ static Data::Size GetMaxTimestampsCount(const Rhi::IContext& context, uint32_t m
7081 return frames_count * max_timestamps_per_frame;
7182}
7283
84+ // Used only to build the description of the failed check below, which is compiled out with METHANE_CHECKS_ENABLED=OFF.
85+ [[maybe_unused]] static std::vector<std::string_view> GetTimeDomainNames (std::span<const vk::TimeDomainEXT> time_domains)
86+ {
87+ META_FUNCTION_TASK ();
88+ std::vector<std::string_view> time_domain_names;
89+ time_domain_names.reserve (time_domains.size ());
90+ std::ranges::transform (time_domains, std::back_inserter (time_domain_names),
91+ [](vk::TimeDomainEXT time_domain) { return magic_enum::enum_name (time_domain); });
92+ return time_domain_names;
93+ }
94+
95+ static vk::TimeDomainEXT GetCalibrateableCpuTimeDomain (const vk::PhysicalDevice& vk_physical_device)
96+ {
97+ META_FUNCTION_TASK ();
98+ // Not every driver exposes every CPU time domain: MoltenVK, for example, exposes CLOCK_MONOTONIC_RAW only,
99+ // so the first domain supported by the device is taken from the platform preference list instead of
100+ // requiring one hard-coded domain to be present.
101+ const std::vector<vk::TimeDomainEXT> calibrateable_time_domains = vk_physical_device.getCalibrateableTimeDomainsEXT ();
102+ const auto cpu_time_domain_it = std::ranges::find_first_of (g_vk_cpu_time_domains, calibrateable_time_domains);
103+ const bool is_cpu_time_domain_calibrateable = cpu_time_domain_it != g_vk_cpu_time_domains.end ();
104+ META_CHECK_TRUE_DESCR (is_cpu_time_domain_calibrateable,
105+ " Vulkan does not support calibration of any CPU time domain used on this platform ({}), device supports only ({})" ,
106+ fmt::join (GetTimeDomainNames (g_vk_cpu_time_domains), " , " ),
107+ fmt::join (GetTimeDomainNames (calibrateable_time_domains), " , " ));
108+ return is_cpu_time_domain_calibrateable ? *cpu_time_domain_it : g_vk_cpu_time_domains.front ();
109+ }
110+
73111Query::Query (Base::QueryPool& buffer, Base::CommandList& command_list, Index index, Range data_range)
74112 : Base::Query(buffer, command_list, index, data_range)
75113 , m_vk_device(GetVulkanQueryPool().GetVulkanContext().GetVulkanDevice().GetNativeDevice())
@@ -131,9 +169,14 @@ CommandQueue& QueryPool::GetVulkanCommandQueue() noexcept
131169}
132170
133171TimestampQueryPool::TimestampQueryPool (CommandQueue& command_queue, uint32_t max_timestamps_per_frame)
134- : QueryPool(command_queue, Type::Timestamp, 1U << 15U , 1U ,
172+ // Query pool is created with exactly as many queries as its data buffer can hold timestamps:
173+ // over-allocating queries makes MoltenVK fail to create the backing MTLCounterSampleBuffer
174+ // (limited to 32768 bytes, i.e. 4096 timestamps) and silently fall back to emulated timestamps.
175+ : QueryPool(command_queue, Type::Timestamp,
176+ GetMaxTimestampsCount (command_queue.GetContext(), max_timestamps_per_frame), 1U,
135177 GetMaxTimestampsCount(command_queue.GetContext(), max_timestamps_per_frame) * sizeof(Timestamp),
136178 sizeof(Timestamp))
179+ , m_vk_cpu_time_domain(GetCalibrateableCpuTimeDomain(command_queue.GetVulkanDevice().GetNativePhysicalDevice()))
137180{
138181 META_FUNCTION_TASK ();
139182
@@ -144,15 +187,8 @@ TimestampQueryPool::TimestampQueryPool(CommandQueue& command_queue, uint32_t max
144187 const float gpu_timestamp_period = vk_physical_device.getProperties ().limits .timestampPeriod ;
145188 SetGpuFrequency (static_cast <Frequency>(gpu_timestamp_period * std::chrono::nanoseconds (1s).count ()));
146189
147- // Check if Vulkan supports CPU time domains calibration
148- const auto calibrateable_time_domains = vk_physical_device.getCalibrateableTimeDomainsEXT ();
149- bool is_cpu_time_domain_calibrateable = std::ranges::find (calibrateable_time_domains, g_vk_cpu_time_domain) != calibrateable_time_domains.end ();
150- META_CHECK_TRUE_DESCR (is_cpu_time_domain_calibrateable,
151- " Vulkan does not support calibration of the CPU time domain {}" ,
152- magic_enum::enum_name (g_vk_cpu_time_domain));
153-
154190 // Calculate the desired CPU-GPU timestamps deviation
155- const std::array<vk::CalibratedTimestampInfoEXT, 2 > timestamp_infos = {{ { vk::TimeDomainEXT::eDevice }, { g_vk_cpu_time_domain }, }};
191+ const std::array<vk::CalibratedTimestampInfoEXT, 2 > timestamp_infos = {{ { vk::TimeDomainEXT::eDevice }, { m_vk_cpu_time_domain }, }};
156192 std::array<uint64_t , 2 > timestamps{{}};
157193 std::array<uint64_t , 32 > probe_deviations{{}};
158194 for (uint64_t & deviation : probe_deviations)
@@ -189,7 +225,7 @@ Rhi::ITimestampQueryPool::CalibratedTimestamps TimestampQueryPool::Calibrate()
189225 constexpr uint32_t max_calibration_attempts = 16U ;
190226
191227 const vk::Device& vk_device = GetVulkanCommandQueue ().GetVulkanDevice ().GetNativeDevice ();
192- const std::array<vk::CalibratedTimestampInfoEXT, 2 > timestamp_infos = {{ { vk::TimeDomainEXT::eDevice }, { g_vk_cpu_time_domain }, }};
228+ const std::array<vk::CalibratedTimestampInfoEXT, 2 > timestamp_infos = {{ { vk::TimeDomainEXT::eDevice }, { m_vk_cpu_time_domain }, }};
193229 std::array<uint64_t , 2 > timestamps{{}};
194230 std::array<uint64_t , 2 > best_timestamps{{}};
195231 uint64_t deviation = 0U ;
0 commit comments