init
This commit is contained in:
@@ -0,0 +1,504 @@
|
||||
/* Copyright (c) 2020-2025, Broadcom Inc. and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "stats/vulkan_stats_provider.h"
|
||||
#include "core/command_buffer.h"
|
||||
#include "core/device.h"
|
||||
#include "rendering/render_context.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
VulkanStatsProvider::VulkanStatsProvider(std::set<StatIndex> &requested_stats,
|
||||
const CounterSamplingConfig &sampling_config,
|
||||
RenderContext &render_context) :
|
||||
render_context(render_context)
|
||||
{
|
||||
// Check all the Vulkan capabilities we require are present
|
||||
if (!is_supported(sampling_config))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vkb::core::DeviceC &device = render_context.get_device();
|
||||
const PhysicalDevice &gpu = device.get_gpu();
|
||||
|
||||
has_timestamps = gpu.get_properties().limits.timestampComputeAndGraphics;
|
||||
timestamp_period = gpu.get_properties().limits.timestampPeriod;
|
||||
|
||||
// Interrogate device for supported stats
|
||||
uint32_t queue_family_index = vkb::get_queue_family_index(gpu.get_queue_family_properties(), VK_QUEUE_GRAPHICS_BIT);
|
||||
|
||||
// Query number of available counters
|
||||
uint32_t count = 0;
|
||||
gpu.enumerate_queue_family_performance_query_counters(queue_family_index, &count,
|
||||
nullptr, nullptr);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return; // No counters available
|
||||
}
|
||||
|
||||
std::vector<VkPerformanceCounterKHR> counters(count);
|
||||
std::vector<VkPerformanceCounterDescriptionKHR> descs(count);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++)
|
||||
{
|
||||
counters[i].sType = VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_KHR;
|
||||
counters[i].pNext = nullptr;
|
||||
descs[i].sType = VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_DESCRIPTION_KHR;
|
||||
descs[i].pNext = nullptr;
|
||||
}
|
||||
|
||||
// Now get the list of counters and their descriptions
|
||||
gpu.enumerate_queue_family_performance_query_counters(queue_family_index, &count,
|
||||
counters.data(), descs.data());
|
||||
|
||||
// Every vendor has a different set of performance counters each
|
||||
// with different names. Match them to the stats we want, where available.
|
||||
if (!fill_vendor_data())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool performance_impact = false;
|
||||
|
||||
// Now build stat_data by matching vendor_data to Vulkan counter data
|
||||
for (auto &s : vendor_data)
|
||||
{
|
||||
StatIndex index = s.first;
|
||||
|
||||
if (requested_stats.find(index) == requested_stats.end())
|
||||
{
|
||||
continue; // We weren't asked for this stat
|
||||
}
|
||||
|
||||
VendorStat &init = s.second;
|
||||
bool found_ctr = false;
|
||||
bool found_div = (init.divisor_name == "");
|
||||
uint32_t ctr_idx, div_idx;
|
||||
|
||||
std::regex name_regex(init.name);
|
||||
std::regex div_regex(init.divisor_name);
|
||||
|
||||
for (uint32_t i = 0; !(found_ctr && found_div) && i < descs.size(); i++)
|
||||
{
|
||||
if (!found_ctr && std::regex_match(descs[i].name, name_regex))
|
||||
{
|
||||
ctr_idx = i;
|
||||
found_ctr = true;
|
||||
}
|
||||
if (!found_div && std::regex_match(descs[i].name, div_regex))
|
||||
{
|
||||
div_idx = i;
|
||||
found_div = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_ctr && found_div)
|
||||
{
|
||||
if ((descs[ctr_idx].flags & VK_PERFORMANCE_COUNTER_DESCRIPTION_PERFORMANCE_IMPACTING_KHR) ||
|
||||
(init.divisor_name != "" && descs[div_idx].flags != VK_PERFORMANCE_COUNTER_DESCRIPTION_PERFORMANCE_IMPACTING_KHR))
|
||||
{
|
||||
performance_impact = true;
|
||||
}
|
||||
|
||||
// Record the counter data
|
||||
counter_indices.emplace_back(ctr_idx);
|
||||
if (init.divisor_name == "")
|
||||
{
|
||||
stat_data[index] = StatData(ctr_idx, counters[ctr_idx].storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
counter_indices.emplace_back(div_idx);
|
||||
stat_data[index] = StatData(ctr_idx, counters[ctr_idx].storage, init.scaling,
|
||||
div_idx, counters[div_idx].storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (performance_impact)
|
||||
LOGW("The collection of performance counters may impact performance");
|
||||
|
||||
if (counter_indices.size() == 0)
|
||||
{
|
||||
return; // No stats available
|
||||
}
|
||||
|
||||
// Acquire the profiling lock, without which we can't collect stats
|
||||
VkAcquireProfilingLockInfoKHR info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_ACQUIRE_PROFILING_LOCK_INFO_KHR;
|
||||
info.timeout = 2000000000; // 2 seconds (in ns)
|
||||
|
||||
if (vkAcquireProfilingLockKHR(device.get_handle(), &info) != VK_SUCCESS)
|
||||
{
|
||||
stat_data.clear();
|
||||
counter_indices.clear();
|
||||
LOGW("Profiling lock acquisition timed-out");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we know the counters and that we can collect them, make a query pool for the results.
|
||||
if (!create_query_pools(queue_family_index))
|
||||
{
|
||||
stat_data.clear();
|
||||
counter_indices.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// These stats are fully supported by this provider and in a single pass, so remove
|
||||
// from the requested set.
|
||||
// Subsequent providers will then only look for things that aren't already supported.
|
||||
for (const auto &s : stat_data)
|
||||
{
|
||||
requested_stats.erase(s.first);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanStatsProvider::~VulkanStatsProvider()
|
||||
{
|
||||
if (stat_data.size() > 0)
|
||||
{
|
||||
// Release profiling lock
|
||||
vkReleaseProfilingLockKHR(render_context.get_device().get_handle());
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanStatsProvider::fill_vendor_data()
|
||||
{
|
||||
const auto &pd_props = render_context.get_device().get_gpu().get_properties();
|
||||
if (pd_props.vendorID == 0x14E4) // Broadcom devices
|
||||
{
|
||||
LOGI("Using Vulkan performance counters from Broadcom device");
|
||||
|
||||
// NOTE: The names here are actually regular-expressions.
|
||||
// Counter names can change between hardware variants for the same vendor,
|
||||
// so regular expression names mean that multiple h/w variants can be easily supported.
|
||||
// clang-format off
|
||||
vendor_data = {
|
||||
{StatIndex::gpu_cycles, {"cycle_count"}},
|
||||
{StatIndex::gpu_vertex_cycles, {"gpu_vertex_cycles"}},
|
||||
{StatIndex::gpu_fragment_cycles, {"gpu_fragment_cycles"}},
|
||||
{StatIndex::gpu_fragment_jobs, {"render_jobs_completed"}},
|
||||
{StatIndex::gpu_ext_reads, {"gpu_mem_reads"}},
|
||||
{StatIndex::gpu_ext_writes, {"gpu_mem_writes"}},
|
||||
{StatIndex::gpu_ext_read_bytes, {"gpu_bytes_read"}},
|
||||
{StatIndex::gpu_ext_write_bytes, {"gpu_bytes_written"}},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Override vendor-specific graph data
|
||||
vendor_data.at(StatIndex::gpu_vertex_cycles).set_vendor_graph_data({"Vertex/Coord/User Cycles", "{:4.1f} M/s", static_cast<float>(1e-6)});
|
||||
vendor_data.at(StatIndex::gpu_fragment_jobs).set_vendor_graph_data({"Render Jobs", "{:4.0f}/s"});
|
||||
|
||||
return true;
|
||||
}
|
||||
#if 0
|
||||
else if (pd_props.vendorID == xxxx) // Other vendor's devices
|
||||
{
|
||||
// Fill vendor_data for other vendor
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
{
|
||||
// Unsupported vendor
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanStatsProvider::create_query_pools(uint32_t queue_family_index)
|
||||
{
|
||||
vkb::core::DeviceC &device = render_context.get_device();
|
||||
const PhysicalDevice &gpu = device.get_gpu();
|
||||
uint32_t num_framebuffers = static_cast<uint32_t>(render_context.get_render_frames().size());
|
||||
|
||||
// Now we know the available counters, we can build a query pool that will collect them.
|
||||
// We will check that the counters can be collected in a single pass. Multi-pass would
|
||||
// be a big performance hit so for these samples, we don't want to use it.
|
||||
VkQueryPoolPerformanceCreateInfoKHR perf_create_info{};
|
||||
perf_create_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_PERFORMANCE_CREATE_INFO_KHR;
|
||||
perf_create_info.queueFamilyIndex = queue_family_index;
|
||||
perf_create_info.counterIndexCount = static_cast<uint32_t>(counter_indices.size());
|
||||
perf_create_info.pCounterIndices = counter_indices.data();
|
||||
|
||||
uint32_t passes_needed = gpu.get_queue_family_performance_query_passes(&perf_create_info);
|
||||
if (passes_needed != 1)
|
||||
{
|
||||
// Needs more than one pass, remove all our supported stats
|
||||
LOGW("Requested Vulkan stats require multiple passes, we won't collect them");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We will need a query pool to report the stats back to us
|
||||
VkQueryPoolCreateInfo pool_create_info{};
|
||||
pool_create_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
||||
pool_create_info.pNext = &perf_create_info;
|
||||
pool_create_info.queryType = VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR;
|
||||
pool_create_info.queryCount = num_framebuffers;
|
||||
|
||||
query_pool = std::make_unique<QueryPool>(device, pool_create_info);
|
||||
|
||||
if (!query_pool)
|
||||
{
|
||||
LOGW("Failed to create performance query pool");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset the query pool before first use. We cannot do these in the command buffer
|
||||
// as that is invalid usage for performance queries due to the potential for multiple
|
||||
// passes being required.
|
||||
query_pool->host_reset(0, num_framebuffers);
|
||||
|
||||
if (has_timestamps)
|
||||
{
|
||||
// If we support timestamp queries we will use those to more accurately measure
|
||||
// the time spent executing a command buffer than just a frame-to-frame timer
|
||||
// in software.
|
||||
VkQueryPoolCreateInfo timestamp_pool_create_info{};
|
||||
timestamp_pool_create_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
||||
timestamp_pool_create_info.queryType = VK_QUERY_TYPE_TIMESTAMP;
|
||||
timestamp_pool_create_info.queryCount = num_framebuffers * 2; // 2 timestamps per frame (start & end)
|
||||
|
||||
timestamp_pool = std::make_unique<QueryPool>(device, timestamp_pool_create_info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanStatsProvider::is_supported(const CounterSamplingConfig &sampling_config) const
|
||||
{
|
||||
// Continuous sampling mode cannot be supported by VK_KHR_performance_query
|
||||
if (sampling_config.mode == CounterSamplingMode::Continuous)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
vkb::core::DeviceC &device = render_context.get_device();
|
||||
|
||||
// The VK_KHR_performance_query must be available and enabled
|
||||
if (!(device.is_extension_enabled("VK_KHR_performance_query") && device.is_extension_enabled("VK_EXT_host_query_reset")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the performance query features flag.
|
||||
// Note: VK_KHR_get_physical_device_properties2 is a pre-requisite of VK_KHR_performance_query
|
||||
// so must be present.
|
||||
VkPhysicalDevicePerformanceQueryFeaturesKHR perf_query_features{};
|
||||
perf_query_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR;
|
||||
|
||||
VkPhysicalDeviceFeatures2KHR device_features{};
|
||||
device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
|
||||
device_features.pNext = &perf_query_features;
|
||||
|
||||
vkGetPhysicalDeviceFeatures2KHR(device.get_gpu().get_handle(), &device_features);
|
||||
if (!perf_query_features.performanceCounterQueryPools)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanStatsProvider::is_available(StatIndex index) const
|
||||
{
|
||||
return stat_data.find(index) != stat_data.end();
|
||||
}
|
||||
|
||||
const StatGraphData &VulkanStatsProvider::get_graph_data(StatIndex index) const
|
||||
{
|
||||
assert(is_available(index) && "VulkanStatsProvider::get_graph_data() called with invalid StatIndex");
|
||||
|
||||
const auto &data = vendor_data.find(index)->second;
|
||||
if (data.has_vendor_graph_data)
|
||||
{
|
||||
return data.graph_data;
|
||||
}
|
||||
|
||||
return default_graph_map[index];
|
||||
}
|
||||
|
||||
void VulkanStatsProvider::begin_sampling(vkb::core::CommandBufferC &cb)
|
||||
{
|
||||
uint32_t active_frame_idx = render_context.get_active_frame_index();
|
||||
if (timestamp_pool)
|
||||
{
|
||||
// We use TimestampQueries when available to provide a more accurate delta_time.
|
||||
// This counters are from a single command buffer execution, but the passed
|
||||
// delta time is a frame-to-frame s/w measure. A timestamp query in the the cmd
|
||||
// buffer gives the actual elapsed time where the counters were measured.
|
||||
cb.reset_query_pool(*timestamp_pool, active_frame_idx * 2, 1);
|
||||
cb.write_timestamp(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, *timestamp_pool,
|
||||
active_frame_idx * 2);
|
||||
}
|
||||
|
||||
if (query_pool)
|
||||
{
|
||||
cb.begin_query(*query_pool, active_frame_idx, static_cast<VkQueryControlFlags>(0));
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanStatsProvider::end_sampling(vkb::core::CommandBufferC &cb)
|
||||
{
|
||||
uint32_t active_frame_idx = render_context.get_active_frame_index();
|
||||
|
||||
if (query_pool)
|
||||
{
|
||||
// Perform a barrier to ensure all previous commands complete before ending the query
|
||||
// This does not block later commands from executing as we use BOTTOM_OF_PIPE in the
|
||||
// dst stage mask
|
||||
vkCmdPipelineBarrier(cb.get_handle(),
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
0, 0, nullptr, 0, nullptr, 0, nullptr);
|
||||
cb.end_query(*query_pool, active_frame_idx);
|
||||
|
||||
++queries_ready;
|
||||
}
|
||||
|
||||
if (timestamp_pool)
|
||||
{
|
||||
cb.reset_query_pool(*timestamp_pool, active_frame_idx * 2 + 1, 1);
|
||||
cb.write_timestamp(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, *timestamp_pool,
|
||||
active_frame_idx * 2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static double get_counter_value(const VkPerformanceCounterResultKHR &result,
|
||||
VkPerformanceCounterStorageKHR storage)
|
||||
{
|
||||
switch (storage)
|
||||
{
|
||||
case VK_PERFORMANCE_COUNTER_STORAGE_INT32_KHR:
|
||||
return static_cast<double>(result.int32);
|
||||
case VK_PERFORMANCE_COUNTER_STORAGE_INT64_KHR:
|
||||
return static_cast<double>(result.int64);
|
||||
case VK_PERFORMANCE_COUNTER_STORAGE_UINT32_KHR:
|
||||
return static_cast<double>(result.uint32);
|
||||
case VK_PERFORMANCE_COUNTER_STORAGE_UINT64_KHR:
|
||||
return static_cast<double>(result.uint64);
|
||||
case VK_PERFORMANCE_COUNTER_STORAGE_FLOAT32_KHR:
|
||||
return static_cast<double>(result.float32);
|
||||
case VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR:
|
||||
return (result.float64);
|
||||
default:
|
||||
assert(0);
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
float VulkanStatsProvider::get_best_delta_time(float sw_delta_time) const
|
||||
{
|
||||
if (!timestamp_pool)
|
||||
{
|
||||
return sw_delta_time;
|
||||
}
|
||||
|
||||
float delta_time = sw_delta_time;
|
||||
|
||||
// Query the timestamps to get an accurate delta time
|
||||
std::array<uint64_t, 2> timestamps;
|
||||
|
||||
uint32_t active_frame_idx = render_context.get_active_frame_index();
|
||||
|
||||
VkResult r = timestamp_pool->get_results(active_frame_idx * 2, 2,
|
||||
timestamps.size() * sizeof(uint64_t),
|
||||
timestamps.data(), sizeof(uint64_t),
|
||||
VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_64_BIT);
|
||||
if (r == VK_SUCCESS)
|
||||
{
|
||||
float elapsed_ns = timestamp_period * static_cast<float>(timestamps[1] - timestamps[0]);
|
||||
delta_time = elapsed_ns * 0.000000001f;
|
||||
}
|
||||
|
||||
return delta_time;
|
||||
}
|
||||
|
||||
StatsProvider::Counters VulkanStatsProvider::sample(float delta_time)
|
||||
{
|
||||
Counters out;
|
||||
if (!query_pool || queries_ready == 0)
|
||||
{
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t active_frame_idx = render_context.get_active_frame_index();
|
||||
|
||||
VkDeviceSize stride = sizeof(VkPerformanceCounterResultKHR) * counter_indices.size();
|
||||
|
||||
std::vector<VkPerformanceCounterResultKHR> results(counter_indices.size());
|
||||
|
||||
VkResult r = query_pool->get_results(active_frame_idx, 1,
|
||||
results.size() * sizeof(VkPerformanceCounterResultKHR),
|
||||
results.data(), stride, VK_QUERY_RESULT_WAIT_BIT);
|
||||
if (r != VK_SUCCESS)
|
||||
{
|
||||
return out;
|
||||
}
|
||||
|
||||
// Use timestamps to get a more accurate delta if available
|
||||
delta_time = get_best_delta_time(delta_time);
|
||||
|
||||
// Parse the results - they are in the order we gave in counter_indices
|
||||
for (const auto &s : stat_data)
|
||||
{
|
||||
StatIndex si = s.first;
|
||||
|
||||
bool need_divisor = (stat_data[si].scaling == StatScaling::ByCounter);
|
||||
double divisor_value = 1.0;
|
||||
double value = 0.0;
|
||||
bool found_ctr = false, found_div = !need_divisor;
|
||||
|
||||
for (uint32_t i = 0; !(found_ctr && found_div) && i < counter_indices.size(); i++)
|
||||
{
|
||||
if (s.second.counter_index == counter_indices[i])
|
||||
{
|
||||
value = get_counter_value(results[i], stat_data[si].storage);
|
||||
found_ctr = true;
|
||||
}
|
||||
if (need_divisor && s.second.divisor_counter_index == counter_indices[i])
|
||||
{
|
||||
divisor_value = get_counter_value(results[i], stat_data[si].divisor_storage);
|
||||
found_div = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_ctr && found_div)
|
||||
{
|
||||
if (stat_data[si].scaling == StatScaling::ByDeltaTime && delta_time != 0.0)
|
||||
{
|
||||
value /= delta_time;
|
||||
}
|
||||
else if (stat_data[si].scaling == StatScaling::ByCounter && divisor_value != 0.0)
|
||||
{
|
||||
value /= divisor_value;
|
||||
}
|
||||
out[si].result = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Now reset the query we just fetched the results from
|
||||
query_pool->host_reset(active_frame_idx, 1);
|
||||
|
||||
--queries_ready;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
Reference in New Issue
Block a user