init
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/* Copyright (c) 2020, 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 "frame_time_stats_provider.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
FrameTimeStatsProvider::FrameTimeStatsProvider(std::set<StatIndex> &requested_stats)
|
||||
{
|
||||
// We always, and only, support StatIndex::frame_times since it's handled directly by us.
|
||||
// Remove from requested set to stop other providers looking for it.
|
||||
requested_stats.erase(StatIndex::frame_times);
|
||||
}
|
||||
|
||||
bool FrameTimeStatsProvider::is_available(StatIndex index) const
|
||||
{
|
||||
// We only support StatIndex::frame_times
|
||||
return index == StatIndex::frame_times;
|
||||
}
|
||||
|
||||
StatsProvider::Counters FrameTimeStatsProvider::sample(float delta_time)
|
||||
{
|
||||
Counters res;
|
||||
// frame_times comes directly from delta_time
|
||||
res[StatIndex::frame_times].result = delta_time;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,46 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stats_provider.h"
|
||||
#include <set>
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class FrameTimeStatsProvider : public StatsProvider
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a FrameTimeStatsProvider
|
||||
* @param requested_stats Set of stats to be collected. Supported stats will be removed from the set.
|
||||
*/
|
||||
FrameTimeStatsProvider(std::set<StatIndex> &requested_stats);
|
||||
/**
|
||||
* @brief Checks if this provider can supply the given enabled stat
|
||||
* @param index The stat index
|
||||
* @return True if the stat is available, false otherwise
|
||||
*/
|
||||
bool is_available(StatIndex index) const override;
|
||||
|
||||
/**
|
||||
* @brief Retrieve a new sample set
|
||||
* @param delta_time Time since last sample
|
||||
*/
|
||||
Counters sample(float delta_time) override;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stats/stats.h>
|
||||
|
||||
#include <rendering/hpp_render_context.h>
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
namespace stats
|
||||
{
|
||||
/**
|
||||
* @brief facade class around vkb::Stats, providing a vulkan.hpp-based interface
|
||||
*
|
||||
* See vkb::Stats for documentation
|
||||
*/
|
||||
class HPPStats : private vkb::Stats
|
||||
{
|
||||
public:
|
||||
using vkb::Stats::get_data;
|
||||
using vkb::Stats::get_graph_data;
|
||||
using vkb::Stats::get_requested_stats;
|
||||
using vkb::Stats::is_available;
|
||||
using vkb::Stats::request_stats;
|
||||
using vkb::Stats::resize;
|
||||
using vkb::Stats::update;
|
||||
|
||||
explicit HPPStats(vkb::rendering::HPPRenderContext &render_context, size_t buffer_size = 16) :
|
||||
vkb::Stats(reinterpret_cast<vkb::RenderContext &>(render_context), buffer_size)
|
||||
{}
|
||||
|
||||
void begin_sampling(vkb::core::CommandBufferCpp &cb)
|
||||
{
|
||||
vkb::Stats::begin_sampling(reinterpret_cast<vkb::core::CommandBufferC &>(cb));
|
||||
}
|
||||
|
||||
void end_sampling(vkb::core::CommandBufferCpp &cb)
|
||||
{
|
||||
vkb::Stats::end_sampling(reinterpret_cast<vkb::core::CommandBufferC &>(cb));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace stats
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,274 @@
|
||||
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2020-2025, Broadcom Inc.
|
||||
*
|
||||
* 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 "hwcpipe_stats_provider.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const char *get_product_family_name(hwcpipe::device::gpu_family f)
|
||||
{
|
||||
using gpu_family = hwcpipe::device::gpu_family;
|
||||
|
||||
switch (f)
|
||||
{
|
||||
case gpu_family::bifrost:
|
||||
return "Bifrost";
|
||||
case gpu_family::midgard:
|
||||
return "Midgard";
|
||||
case gpu_family::valhall:
|
||||
return "Valhall";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
HWCPipeStatsProvider::HWCPipeStatsProvider(std::set<StatIndex> &requested_stats)
|
||||
{
|
||||
// Mapping of stats to their hwcpipe availability
|
||||
// clang-format off
|
||||
StatDataMap hwcpipe_stats = {
|
||||
{StatIndex::gpu_cycles, {hwcpipe_counter::MaliGPUActiveCy}},
|
||||
{StatIndex::gpu_vertex_cycles, {hwcpipe_counter::MaliNonFragQueueActiveCy, {MaliNonFragActiveCy, MaliBinningQueueActiveCy} }},
|
||||
{StatIndex::gpu_load_store_cycles, {hwcpipe_counter::MaliLSIssueCy}},
|
||||
{StatIndex::gpu_tiles, {hwcpipe_counter::MaliFragTile}},
|
||||
{StatIndex::gpu_killed_tiles, {hwcpipe_counter::MaliFragTileKill}},
|
||||
{StatIndex::gpu_fragment_cycles, {hwcpipe_counter::MaliFragQueueActiveCy, {MaliFragActiveCy, MaliMainQueueActiveCy}}},
|
||||
{StatIndex::gpu_fragment_jobs, {hwcpipe_counter::MaliFragQueueJob, {MaliMainQueueJob}}},
|
||||
{StatIndex::gpu_ext_reads, {hwcpipe_counter::MaliExtBusRdBt}},
|
||||
{StatIndex::gpu_ext_writes, {hwcpipe_counter::MaliExtBusWrBt}},
|
||||
{StatIndex::gpu_ext_read_stalls, {hwcpipe_counter::MaliExtBusRdStallCy}},
|
||||
{StatIndex::gpu_ext_write_stalls, {hwcpipe_counter::MaliExtBusWrStallCy}},
|
||||
{StatIndex::gpu_ext_read_bytes, {hwcpipe_counter::MaliExtBusRdBy}},
|
||||
{StatIndex::gpu_ext_write_bytes, {hwcpipe_counter::MaliExtBusWrBy}},
|
||||
{StatIndex::gpu_tex_cycles, {hwcpipe_counter::MaliTexIssueCy}}};
|
||||
// clang-format on
|
||||
|
||||
// Detect all GPUs & print some info
|
||||
for (const auto &gpu : hwcpipe::find_gpus())
|
||||
{
|
||||
LOGI("HWCPipe: ------------------------------------------------------------");
|
||||
LOGI("HWCPipe: GPU Device {}:", gpu.get_device_number());
|
||||
LOGI("HWCPipe: ------------------------------------------------------------");
|
||||
LOGI("HWCPipe: Product Family: {}", get_product_family_name(gpu.get_gpu_family()));
|
||||
LOGI("HWCPipe: Number of Cores: {}", gpu.num_shader_cores());
|
||||
LOGI("HWCPipe: Bus Width: {}", gpu.bus_width());
|
||||
}
|
||||
|
||||
// Probe device 0 (i.e. /dev/mali0)
|
||||
auto gpu = hwcpipe::gpu(0);
|
||||
if (!gpu)
|
||||
{
|
||||
LOGE("HWCPipe: Mali GPU device 0 is missing");
|
||||
}
|
||||
|
||||
auto config = hwcpipe::sampler_config(gpu);
|
||||
auto counter_db = hwcpipe::counter_database{};
|
||||
|
||||
std::error_code ec;
|
||||
for (const auto &stat : requested_stats)
|
||||
{
|
||||
auto it = hwcpipe_stats.find(stat);
|
||||
if (it != hwcpipe_stats.end())
|
||||
{
|
||||
hwcpipe::counter_metadata meta;
|
||||
|
||||
auto ec = counter_db.describe_counter(it->second.counter, meta);
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: unknown counter");
|
||||
}
|
||||
|
||||
ec = config.add_counter(it->second.counter);
|
||||
if (ec)
|
||||
{
|
||||
// Some counters have changed in recent devices
|
||||
auto &fallback_list = it->second.fallback_list;
|
||||
if (!fallback_list.empty())
|
||||
{
|
||||
for (const auto &fallback : fallback_list)
|
||||
{
|
||||
ec = counter_db.describe_counter(fallback, meta);
|
||||
ec = config.add_counter(fallback);
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
// Replace counter with available alternative
|
||||
it->second.counter = fallback;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: '{}' counter not supported by this GPU.", meta.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
stat_data[stat] = hwcpipe_stats[stat];
|
||||
|
||||
LOGI("HWCPipe: enabled '{}' counter", meta.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any supported stats from the requested set.
|
||||
// Subsequent providers will then only look for things that aren't already supported.
|
||||
for (const auto &iter : stat_data)
|
||||
{
|
||||
requested_stats.erase(iter.first);
|
||||
}
|
||||
|
||||
stat_data_count = stat_data.size();
|
||||
|
||||
sampler = std::make_unique<hwcpipe::sampler<>>(config);
|
||||
|
||||
if (stat_data_count > 0)
|
||||
{
|
||||
ec = sampler->start_sampling();
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: {}", ec.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HWCPipeStatsProvider::~HWCPipeStatsProvider()
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
if (stat_data_count > 0)
|
||||
{
|
||||
ec = sampler->stop_sampling();
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: {}", ec.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HWCPipeStatsProvider::is_available(StatIndex index) const
|
||||
{
|
||||
return stat_data.find(index) != stat_data.end();
|
||||
}
|
||||
|
||||
const StatGraphData &HWCPipeStatsProvider::get_graph_data(StatIndex index) const
|
||||
{
|
||||
assert(is_available(index) && "HWCPipeStatsProvider::get_graph_data() called with invalid StatIndex");
|
||||
|
||||
static StatGraphData vertex_compute_cycles{"Vertex Compute Cycles", "{:4.1f} M/s", static_cast<float>(1e-6)};
|
||||
|
||||
// HWCPipe reports combined vertex/compute cycles (which is Arm specific)
|
||||
// Ensure we report graph with the correct name when asked for vertex cycles
|
||||
if (index == StatIndex::gpu_vertex_cycles)
|
||||
{
|
||||
return vertex_compute_cycles;
|
||||
}
|
||||
|
||||
return default_graph_map[index];
|
||||
}
|
||||
|
||||
static double get_gpu_counter_value(const hwcpipe::counter_sample &sample)
|
||||
{
|
||||
switch (sample.type)
|
||||
{
|
||||
case hwcpipe::counter_sample::type::uint64:
|
||||
return sample.value.uint64;
|
||||
case hwcpipe::counter_sample::type::float64:
|
||||
return sample.value.float64;
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
StatsProvider::Counters HWCPipeStatsProvider::sample(float delta_time)
|
||||
{
|
||||
Counters res;
|
||||
|
||||
if (stat_data_count < 1)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
ec = sampler->sample_now();
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: {}", ec.message());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Map from hwcpipe measurement to our sample result for each counter
|
||||
for (auto iter : stat_data)
|
||||
{
|
||||
StatIndex index = iter.first;
|
||||
const StatData &data = iter.second;
|
||||
|
||||
hwcpipe::counter_sample sample;
|
||||
|
||||
ec = sampler->get_counter_value(data.counter, sample);
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: {}", ec.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto d = get_gpu_counter_value(sample);
|
||||
|
||||
if (data.scaling == StatScaling::ByDeltaTime && delta_time != 0.0f)
|
||||
{
|
||||
d /= delta_time;
|
||||
}
|
||||
else if (data.scaling == StatScaling::ByCounter)
|
||||
{
|
||||
ec = sampler->get_counter_value(data.divisor, sample);
|
||||
if (ec)
|
||||
{
|
||||
LOGE("HWCPipe: {}", ec.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
double divisor = get_gpu_counter_value(sample);
|
||||
if (divisor != 0.0)
|
||||
{
|
||||
d /= divisor;
|
||||
}
|
||||
else
|
||||
{
|
||||
d = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
res[index].result = d;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
StatsProvider::Counters HWCPipeStatsProvider::continuous_sample(float delta_time)
|
||||
{
|
||||
return sample(delta_time);
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,105 @@
|
||||
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2020-2025, Broadcom Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/vk_common.h"
|
||||
|
||||
#include "stats_provider.h"
|
||||
|
||||
VKBP_DISABLE_WARNINGS()
|
||||
#include <device/product_id.hpp>
|
||||
#include <hwcpipe/counter_database.hpp>
|
||||
#include <hwcpipe/gpu.hpp>
|
||||
#include <hwcpipe/sampler.hpp>
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include <unistd.h>
|
||||
VKBP_ENABLE_WARNINGS()
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class HWCPipeStatsProvider : public StatsProvider
|
||||
{
|
||||
private:
|
||||
struct StatData
|
||||
{
|
||||
hwcpipe_counter counter;
|
||||
std::vector<hwcpipe_counter> fallback_list;
|
||||
StatScaling scaling;
|
||||
hwcpipe_counter divisor;
|
||||
|
||||
StatData(hwcpipe_counter _counter = {}, std::vector<hwcpipe_counter> _fb_list = {}, StatScaling _sc = {}, hwcpipe_counter div = {}) :
|
||||
counter(_counter), fallback_list(std::move(_fb_list)), scaling(_sc), divisor(div)
|
||||
{}
|
||||
};
|
||||
|
||||
using StatDataMap = std::unordered_map<StatIndex, StatData, StatIndexHash>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a HWCPipeStateProvider
|
||||
* @param requested_stats Set of stats to be collected. Supported stats will be removed from the set.
|
||||
*/
|
||||
HWCPipeStatsProvider(std::set<StatIndex> &requested_stats);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~HWCPipeStatsProvider();
|
||||
|
||||
/**
|
||||
* @brief Checks if this provider can supply the given enabled stat
|
||||
* @param index The stat index
|
||||
* @return True if the stat is available, false otherwise
|
||||
*/
|
||||
bool is_available(StatIndex index) const override;
|
||||
|
||||
/**
|
||||
* @brief Retrieve graphing data for the given enabled stat
|
||||
* @param index The stat index
|
||||
*/
|
||||
const StatGraphData &get_graph_data(StatIndex index) const override;
|
||||
|
||||
/**
|
||||
* @brief Retrieve a new sample set from polled sampling
|
||||
* @param delta_time Time since last sample
|
||||
*/
|
||||
Counters sample(float delta_time) override;
|
||||
|
||||
/**
|
||||
* @brief Retrieve a new sample set from continuous sampling
|
||||
* @param delta_time Time since last sample
|
||||
*/
|
||||
Counters continuous_sample(float delta_time) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<hwcpipe::sampler<>> sampler;
|
||||
|
||||
size_t stat_data_count{0};
|
||||
|
||||
// Only stats which are available and were requested end up in stat_data
|
||||
StatDataMap stat_data;
|
||||
|
||||
// Counter sampling configuration
|
||||
CounterSamplingConfig sampling_config;
|
||||
};
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,476 @@
|
||||
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2020-2025, Broadcom Inc.
|
||||
*
|
||||
* 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/stats.h"
|
||||
|
||||
#include <core/util/profiling.hpp>
|
||||
#include <vk_mem_alloc.h>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "core/device.h"
|
||||
#include "frame_time_stats_provider.h"
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
# include "hwcpipe_stats_provider.h"
|
||||
#endif
|
||||
#include "core/allocated.h"
|
||||
#include "rendering/render_context.h"
|
||||
#include "vulkan_stats_provider.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
Stats::Stats(RenderContext &render_context, size_t buffer_size) :
|
||||
render_context(render_context),
|
||||
buffer_size(buffer_size)
|
||||
{
|
||||
assert(buffer_size >= 2 && "Buffers size should be greater than 2");
|
||||
}
|
||||
|
||||
Stats::~Stats()
|
||||
{
|
||||
if (stop_worker)
|
||||
{
|
||||
stop_worker->set_value();
|
||||
}
|
||||
|
||||
if (worker_thread.joinable())
|
||||
{
|
||||
worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void Stats::request_stats(const std::set<StatIndex> &wanted_stats,
|
||||
CounterSamplingConfig config)
|
||||
{
|
||||
if (providers.size() != 0)
|
||||
{
|
||||
throw std::runtime_error("Stats must only be requested once");
|
||||
}
|
||||
|
||||
requested_stats = wanted_stats;
|
||||
sampling_config = config;
|
||||
|
||||
// Copy the requested stats, so they can be changed by the providers below
|
||||
std::set<StatIndex> stats = requested_stats;
|
||||
|
||||
// Initialize our list of providers (in priority order)
|
||||
// All supported stats will be removed from the given 'stats' set by the provider's constructor
|
||||
// so subsequent providers only see requests for stats that aren't already supported.
|
||||
providers.emplace_back(std::make_unique<FrameTimeStatsProvider>(stats));
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
providers.emplace_back(std::make_unique<HWCPipeStatsProvider>(stats));
|
||||
#endif
|
||||
providers.emplace_back(std::make_unique<VulkanStatsProvider>(stats, sampling_config, render_context));
|
||||
|
||||
// In continuous sampling mode we still need to update the frame times as if we are polling
|
||||
// Store the frame time provider here so we can easily access it later.
|
||||
frame_time_provider = providers[0].get();
|
||||
|
||||
for (const auto &stat : requested_stats)
|
||||
{
|
||||
counters[stat] = std::vector<float>(buffer_size, 0);
|
||||
}
|
||||
|
||||
if (sampling_config.mode == CounterSamplingMode::Continuous)
|
||||
{
|
||||
// Start a thread for continuous sample capture
|
||||
stop_worker = std::make_unique<std::promise<void>>();
|
||||
|
||||
worker_thread = std::thread([this] {
|
||||
continuous_sampling_worker(stop_worker->get_future());
|
||||
});
|
||||
|
||||
// Reduce smoothing for continuous sampling
|
||||
alpha_smoothing = 0.6f;
|
||||
}
|
||||
|
||||
for (const auto &stat_index : requested_stats)
|
||||
{
|
||||
if (!is_available(stat_index))
|
||||
{
|
||||
LOGW(vkb::StatsProvider::default_graph_data(stat_index).name + " : not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stats::resize(const size_t width)
|
||||
{
|
||||
// The circular buffer size will be 1/16th of the width of the screen
|
||||
// which means every sixteen pixels represent one graph value
|
||||
buffer_size = width >> 4;
|
||||
|
||||
for (auto &counter : counters)
|
||||
{
|
||||
counter.second.resize(buffer_size);
|
||||
counter.second.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
bool Stats::is_available(const StatIndex index) const
|
||||
{
|
||||
for (const auto &p : providers)
|
||||
{
|
||||
if (p->is_available(index))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void add_smoothed_value(std::vector<float> &values, float value, float alpha)
|
||||
{
|
||||
assert(values.size() >= 2 && "Buffers size should be greater than 2");
|
||||
|
||||
if (values.size() == values.capacity())
|
||||
{
|
||||
// Shift values to the left to make space at the end and update counters
|
||||
std::rotate(values.begin(), values.begin() + 1, values.end());
|
||||
}
|
||||
|
||||
// Use an exponential moving average to smooth values
|
||||
values.back() = value * alpha + *(values.end() - 2) * (1.0f - alpha);
|
||||
}
|
||||
|
||||
void Stats::update(float delta_time)
|
||||
{
|
||||
switch (sampling_config.mode)
|
||||
{
|
||||
case CounterSamplingMode::Polling:
|
||||
{
|
||||
StatsProvider::Counters sample;
|
||||
|
||||
for (auto &p : providers)
|
||||
{
|
||||
auto s = p->sample(delta_time);
|
||||
sample.insert(s.begin(), s.end());
|
||||
}
|
||||
push_sample(sample);
|
||||
break;
|
||||
}
|
||||
case CounterSamplingMode::Continuous:
|
||||
{
|
||||
// Check that we have no pending samples to be shown
|
||||
if (pending_samples.size() == 0)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(continuous_sampling_mutex);
|
||||
if (!should_add_to_continuous_samples)
|
||||
{
|
||||
// If we have no pending samples, we let the worker thread
|
||||
// capture samples for the next frame
|
||||
should_add_to_continuous_samples = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The worker thread has captured a frame, so we stop it
|
||||
// and read the samples
|
||||
should_add_to_continuous_samples = false;
|
||||
pending_samples.clear();
|
||||
std::swap(pending_samples, continuous_samples);
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_samples.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the number of pending samples is capped at a reasonable value
|
||||
if (pending_samples.size() > 100)
|
||||
{
|
||||
// Prefer later samples over new samples.
|
||||
std::move(pending_samples.end() - 100, pending_samples.end(), pending_samples.begin());
|
||||
pending_samples.erase(pending_samples.begin() + 100, pending_samples.end());
|
||||
|
||||
// If we get to this point, we're not reading samples fast enough, nudge a little ahead.
|
||||
fractional_pending_samples += 1.0f;
|
||||
}
|
||||
|
||||
// Compute the number of samples to show this frame
|
||||
float floating_sample_count = sampling_config.speed * delta_time * static_cast<float>(buffer_size) + fractional_pending_samples;
|
||||
|
||||
// Keep track of the fractional value to avoid speeding up or slowing down too much due to rounding errors.
|
||||
// Generally we push very few samples per frame, so this matters.
|
||||
fractional_pending_samples = floating_sample_count - std::floor(floating_sample_count);
|
||||
|
||||
auto sample_count = static_cast<size_t>(floating_sample_count);
|
||||
|
||||
// Clamp the number of samples
|
||||
sample_count = std::max<size_t>(1, std::min<size_t>(sample_count, pending_samples.size()));
|
||||
|
||||
// Get the frame time stats (not a continuous stat)
|
||||
StatsProvider::Counters frame_time_sample = frame_time_provider->sample(delta_time);
|
||||
|
||||
// Push the samples to circular buffers
|
||||
std::for_each(pending_samples.begin(), pending_samples.begin() + sample_count, [this, frame_time_sample](auto &s) {
|
||||
// Write the correct frame time into the continuous stats
|
||||
s.insert(frame_time_sample.begin(), frame_time_sample.end());
|
||||
// Then push the sample to the counters list
|
||||
this->push_sample(s);
|
||||
});
|
||||
pending_samples.erase(pending_samples.begin(), pending_samples.begin() + sample_count);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
profile_counters();
|
||||
}
|
||||
|
||||
void Stats::continuous_sampling_worker(std::future<void> should_terminate)
|
||||
{
|
||||
worker_timer.tick();
|
||||
|
||||
for (auto &p : providers)
|
||||
{
|
||||
p->continuous_sample(0.0f);
|
||||
}
|
||||
|
||||
while (should_terminate.wait_for(std::chrono::seconds(0)) != std::future_status::ready)
|
||||
{
|
||||
auto delta_time = static_cast<float>(worker_timer.tick());
|
||||
auto interval = std::chrono::duration_cast<std::chrono::duration<float>>(sampling_config.interval).count();
|
||||
|
||||
// Ensure we wait for the interval specified in config
|
||||
if (delta_time < interval)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::duration<float>(interval - delta_time));
|
||||
delta_time += static_cast<float>(worker_timer.tick());
|
||||
}
|
||||
|
||||
// Sample counters
|
||||
StatsProvider::Counters sample;
|
||||
for (auto &p : providers)
|
||||
{
|
||||
StatsProvider::Counters s = p->continuous_sample(delta_time);
|
||||
sample.insert(s.begin(), s.end());
|
||||
}
|
||||
|
||||
// Add the new sample to the vector of continuous samples
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(continuous_sampling_mutex);
|
||||
if (should_add_to_continuous_samples)
|
||||
{
|
||||
continuous_samples.push_back(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stats::push_sample(const StatsProvider::Counters &sample)
|
||||
{
|
||||
for (auto &c : counters)
|
||||
{
|
||||
StatIndex idx = c.first;
|
||||
std::vector<float> &values = c.second;
|
||||
|
||||
// Find the counter matching this StatIndex in the Sample
|
||||
const auto &smp = sample.find(idx);
|
||||
if (smp == sample.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float measurement = static_cast<float>(smp->second.result);
|
||||
|
||||
add_smoothed_value(values, measurement, alpha_smoothing);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// For now names are taken from the stats_provider.cpp file
|
||||
const char *to_string(StatIndex index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case StatIndex::frame_times:
|
||||
return "Frame Times (ms)";
|
||||
case StatIndex::cpu_cycles:
|
||||
return "CPU Cycles (M/s)";
|
||||
case StatIndex::cpu_instructions:
|
||||
return "CPU Instructions (M/s)";
|
||||
case StatIndex::cpu_cache_miss_ratio:
|
||||
return "Cache Miss Ratio (%)";
|
||||
case StatIndex::cpu_branch_miss_ratio:
|
||||
return "Branch Miss Ratio (%)";
|
||||
case StatIndex::cpu_l1_accesses:
|
||||
return "CPU L1 Accesses (M/s)";
|
||||
case StatIndex::cpu_instr_retired:
|
||||
return "CPU Instructions Retired (M/s)";
|
||||
case StatIndex::cpu_l2_accesses:
|
||||
return "CPU L2 Accesses (M/s)";
|
||||
case StatIndex::cpu_l3_accesses:
|
||||
return "CPU L3 Accesses (M/s)";
|
||||
case StatIndex::cpu_bus_reads:
|
||||
return "CPU Bus Read Beats (M/s)";
|
||||
case StatIndex::cpu_bus_writes:
|
||||
return "CPU Bus Write Beats (M/s)";
|
||||
case StatIndex::cpu_mem_reads:
|
||||
return "CPU Memory Read Instructions (M/s)";
|
||||
case StatIndex::cpu_mem_writes:
|
||||
return "CPU Memory Write Instructions (M/s)";
|
||||
case StatIndex::cpu_ase_spec:
|
||||
return "CPU Speculatively Exec. SIMD Instructions (M/s)";
|
||||
case StatIndex::cpu_vfp_spec:
|
||||
return "CPU Speculatively Exec. FP Instructions (M/s)";
|
||||
case StatIndex::cpu_crypto_spec:
|
||||
return "CPU Speculatively Exec. Crypto Instructions (M/s)";
|
||||
case StatIndex::gpu_cycles:
|
||||
return "GPU Cycles (M/s)";
|
||||
case StatIndex::gpu_vertex_cycles:
|
||||
return "Vertex Cycles (M/s)";
|
||||
case StatIndex::gpu_load_store_cycles:
|
||||
return "Load Store Cycles (k/s)";
|
||||
case StatIndex::gpu_tiles:
|
||||
return "Tiles (k/s)";
|
||||
case StatIndex::gpu_killed_tiles:
|
||||
return "Tiles killed by CRC match (k/s)";
|
||||
case StatIndex::gpu_fragment_jobs:
|
||||
return "Fragment Jobs (s)";
|
||||
case StatIndex::gpu_fragment_cycles:
|
||||
return "Fragment Cycles (M/s)";
|
||||
case StatIndex::gpu_tex_cycles:
|
||||
return "Shader Texture Cycles (k/s)";
|
||||
case StatIndex::gpu_ext_reads:
|
||||
return "External Reads (M/s)";
|
||||
case StatIndex::gpu_ext_writes:
|
||||
return "External Writes (M/s)";
|
||||
case StatIndex::gpu_ext_read_stalls:
|
||||
return "External Read Stalls (M/s)";
|
||||
case StatIndex::gpu_ext_write_stalls:
|
||||
return "External Write Stalls (M/s)";
|
||||
case StatIndex::gpu_ext_read_bytes:
|
||||
return "External Read Bytes (MiB/s)";
|
||||
case StatIndex::gpu_ext_write_bytes:
|
||||
return "External Write Bytes (MiB/s)";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Stats::profile_counters() const
|
||||
{
|
||||
#if VKB_PROFILING
|
||||
static std::chrono::high_resolution_clock::time_point last_time = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (now - last_time < std::chrono::milliseconds(100))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
last_time = now;
|
||||
|
||||
for (auto &c : counters)
|
||||
{
|
||||
StatIndex idx = c.first;
|
||||
auto &graph_data = get_graph_data(idx);
|
||||
|
||||
if (c.second.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float average = 0.0f;
|
||||
for (auto &v : c.second)
|
||||
{
|
||||
average += v;
|
||||
}
|
||||
average /= c.second.size();
|
||||
|
||||
if (auto *index_name = to_string(idx))
|
||||
{
|
||||
Plot<float>::plot(index_name, average * graph_data.scale_factor);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::string> labels;
|
||||
|
||||
auto &device = render_context.get_device();
|
||||
VmaAllocator allocator = allocated::get_memory_allocator();
|
||||
|
||||
VmaBudget heap_budgets[VK_MAX_MEMORY_HEAPS];
|
||||
vmaGetHeapBudgets(allocator, heap_budgets);
|
||||
|
||||
// We know that we will only ever have one device in the system, so we can cache the labels
|
||||
if (labels.size() == 0)
|
||||
{
|
||||
VkPhysicalDeviceMemoryProperties memory_properties;
|
||||
vkGetPhysicalDeviceMemoryProperties(device.get_gpu().get_handle(), &memory_properties);
|
||||
|
||||
labels.reserve(memory_properties.memoryHeapCount);
|
||||
|
||||
for (size_t heap = 0; heap < memory_properties.memoryHeapCount; heap++)
|
||||
{
|
||||
VkMemoryPropertyFlags flags = memory_properties.memoryHeaps[heap].flags;
|
||||
labels.push_back("Heap " + std::to_string(heap) + " " + vk::to_string(vk::MemoryPropertyFlags{flags}));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t heap = 0; heap < labels.size(); heap++)
|
||||
{
|
||||
Plot<float, PlotType::Memory>::plot(labels[heap].c_str(), heap_budgets[heap].usage / (1024.0f * 1024.0f));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Stats::begin_sampling(vkb::core::CommandBufferC &cb)
|
||||
{
|
||||
// Inform the providers
|
||||
for (auto &p : providers)
|
||||
{
|
||||
p->begin_sampling(cb);
|
||||
}
|
||||
}
|
||||
|
||||
void Stats::end_sampling(vkb::core::CommandBufferC &cb)
|
||||
{
|
||||
// Inform the providers
|
||||
for (auto &p : providers)
|
||||
{
|
||||
p->end_sampling(cb);
|
||||
}
|
||||
}
|
||||
|
||||
const StatGraphData &Stats::get_graph_data(StatIndex index) const
|
||||
{
|
||||
for (auto &p : providers)
|
||||
{
|
||||
if (p->is_available(index))
|
||||
{
|
||||
return p->get_graph_data(index);
|
||||
}
|
||||
}
|
||||
return StatsProvider::default_graph_data(index);
|
||||
}
|
||||
|
||||
StatGraphData::StatGraphData(const std::string &name,
|
||||
const std::string &graph_label_format,
|
||||
float scale_factor,
|
||||
bool has_fixed_max,
|
||||
float max_value) :
|
||||
name(name),
|
||||
format{graph_label_format},
|
||||
scale_factor{scale_factor},
|
||||
has_fixed_max{has_fixed_max},
|
||||
max_value{max_value}
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,204 @@
|
||||
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2020-2025, Broadcom Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "stats_common.h"
|
||||
#include "stats_provider.h"
|
||||
#include "timer.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class RenderContext;
|
||||
|
||||
namespace core
|
||||
{
|
||||
template <vkb::BindingType bindingType>
|
||||
class CommandBuffer;
|
||||
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
|
||||
} // namespace core
|
||||
|
||||
/*
|
||||
* @brief Helper class for querying statistics about the CPU and the GPU
|
||||
*/
|
||||
class Stats
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Stats object
|
||||
* @param render_context The RenderContext for this sample
|
||||
* @param buffer_size Size of the circular buffers
|
||||
*/
|
||||
explicit Stats(RenderContext &render_context, size_t buffer_size = 16);
|
||||
|
||||
/**
|
||||
* @brief Destroys the Stats object
|
||||
*/
|
||||
~Stats();
|
||||
|
||||
/**
|
||||
* @brief Request specific set of stats to be collected
|
||||
* @param requested_stats Set of stats to be collected if available
|
||||
* @param sampling_config Sampling mode configuration (polling or continuous)
|
||||
*/
|
||||
void request_stats(const std::set<StatIndex> &requested_stats,
|
||||
CounterSamplingConfig sampling_config = {CounterSamplingMode::Polling});
|
||||
|
||||
/**
|
||||
* @brief Resizes the stats buffers according to the width of the screen
|
||||
* @param width The width of the screen
|
||||
*/
|
||||
void resize(size_t width);
|
||||
|
||||
/**
|
||||
* @brief Checks if an enabled stat is available in the current platform
|
||||
* @param index The stat index
|
||||
* @return True if the stat is available, false otherwise
|
||||
*/
|
||||
bool is_available(StatIndex index) const;
|
||||
|
||||
/**
|
||||
* @brief Returns data relevant for graphing a specific statistic
|
||||
* @param index The stat index of the data requested
|
||||
* @return The data of the specified stat
|
||||
*/
|
||||
const StatGraphData &get_graph_data(StatIndex index) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the collected data for a specific statistic
|
||||
* @param index The stat index of the data requested
|
||||
* @return The data of the specified stat
|
||||
*/
|
||||
const std::vector<float> &get_data(StatIndex index) const
|
||||
{
|
||||
return counters.at(index);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return The requested stats
|
||||
*/
|
||||
const std::set<StatIndex> &get_requested_stats() const
|
||||
{
|
||||
return requested_stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update statistics, must be called after every frame
|
||||
* @param delta_time Time since last update
|
||||
*/
|
||||
void update(float delta_time);
|
||||
|
||||
/**
|
||||
* @brief A command buffer that we want to collect stats about has just begun
|
||||
*
|
||||
* Some stats providers (like the Vulkan extension one) can only collect stats
|
||||
* about the execution of a specific command buffer. In those cases we need to
|
||||
* know when a command buffer has begun and when it's about to end so that we
|
||||
* can inject some extra commands into the command buffer to control the stats
|
||||
* collection. This method tells the stats provider that a command buffer has
|
||||
* begun so that can happen. The command buffer must be in a recording state
|
||||
* when this method is called.
|
||||
* @param cb The command buffer
|
||||
*/
|
||||
void begin_sampling(vkb::core::CommandBufferC &cb);
|
||||
|
||||
/**
|
||||
* @brief A command buffer that we want to collect stats about is about to be ended
|
||||
*
|
||||
* Some stats providers (like the Vulkan extension one) can only collect stats
|
||||
* about the execution of a specific command buffer. In those cases we need to
|
||||
* know when a command buffer has begun and when it's about to end so that we
|
||||
* can inject some extra commands into the command buffer to control the stats
|
||||
* collection. This method tells the stats provider that a command buffer is
|
||||
* about to be ended so that can happen. The command buffer must be in a recording
|
||||
* state when this method is called.
|
||||
* @param cb The command buffer
|
||||
*/
|
||||
void end_sampling(vkb::core::CommandBufferC &cb);
|
||||
|
||||
private:
|
||||
/// The render context
|
||||
RenderContext &render_context;
|
||||
|
||||
/// Stats that were requested - they may not all be available
|
||||
std::set<StatIndex> requested_stats;
|
||||
|
||||
/// Provider that tracks frame times
|
||||
StatsProvider *frame_time_provider;
|
||||
|
||||
/// A list of stats providers to use in priority order
|
||||
std::vector<std::unique_ptr<StatsProvider>> providers;
|
||||
|
||||
/// Counter sampling configuration
|
||||
CounterSamplingConfig sampling_config;
|
||||
|
||||
/// Size of the circular buffers
|
||||
size_t buffer_size;
|
||||
|
||||
/// Timer used in the main thread to compute delta time
|
||||
Timer main_timer;
|
||||
|
||||
/// Timer used by the worker thread to throttle counter sampling
|
||||
Timer worker_timer;
|
||||
|
||||
/// Alpha smoothing for running average
|
||||
float alpha_smoothing{0.2f};
|
||||
|
||||
/// Circular buffers for counter data
|
||||
std::map<StatIndex, std::vector<float>> counters{};
|
||||
|
||||
/// Worker thread for continuous sampling
|
||||
std::thread worker_thread;
|
||||
|
||||
/// Promise to stop the worker thread
|
||||
std::unique_ptr<std::promise<void>> stop_worker;
|
||||
|
||||
/// A mutex for accessing measurements during continuous sampling
|
||||
std::mutex continuous_sampling_mutex;
|
||||
|
||||
/// The samples read during continuous sampling
|
||||
std::vector<StatsProvider::Counters> continuous_samples;
|
||||
|
||||
/// A flag specifying if the worker thread should add entries to continuous_samples
|
||||
bool should_add_to_continuous_samples{false};
|
||||
|
||||
/// The samples waiting to be displayed
|
||||
std::vector<StatsProvider::Counters> pending_samples;
|
||||
|
||||
/// A value which helps keep a steady pace of continuous samples output.
|
||||
float fractional_pending_samples{0.0f};
|
||||
|
||||
/// The worker thread function for continuous sampling;
|
||||
/// it adds a new entry to continuous_samples at every interval
|
||||
void continuous_sampling_worker(std::future<void> should_terminate);
|
||||
|
||||
/// Updates circular buffers for CPU and GPU counters
|
||||
void push_sample(const StatsProvider::Counters &sample);
|
||||
|
||||
// Push counters to external profilers
|
||||
void profile_counters() const;
|
||||
};
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,137 @@
|
||||
/* Copyright (c) 2018-2022, Arm Limited and Contributors
|
||||
* Copyright (c) 2020-2022, Broadcom Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
# undef None
|
||||
#endif
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
/**
|
||||
* @brief Handles of stats to be optionally enabled in @ref vkb::Stats
|
||||
*/
|
||||
enum class StatIndex
|
||||
{
|
||||
frame_times,
|
||||
cpu_cycles,
|
||||
cpu_instructions,
|
||||
cpu_cache_miss_ratio,
|
||||
cpu_branch_miss_ratio,
|
||||
cpu_l1_accesses,
|
||||
cpu_instr_retired,
|
||||
cpu_l2_accesses,
|
||||
cpu_l3_accesses,
|
||||
cpu_bus_reads,
|
||||
cpu_bus_writes,
|
||||
cpu_mem_reads,
|
||||
cpu_mem_writes,
|
||||
cpu_ase_spec,
|
||||
cpu_vfp_spec,
|
||||
cpu_crypto_spec,
|
||||
|
||||
gpu_cycles,
|
||||
gpu_vertex_cycles,
|
||||
gpu_load_store_cycles,
|
||||
gpu_tiles,
|
||||
gpu_killed_tiles,
|
||||
gpu_fragment_jobs,
|
||||
gpu_fragment_cycles,
|
||||
gpu_ext_reads,
|
||||
gpu_ext_writes,
|
||||
gpu_ext_read_stalls,
|
||||
gpu_ext_write_stalls,
|
||||
gpu_ext_read_bytes,
|
||||
gpu_ext_write_bytes,
|
||||
gpu_tex_cycles,
|
||||
};
|
||||
|
||||
struct StatIndexHash
|
||||
{
|
||||
template <typename T>
|
||||
std::size_t operator()(T t) const
|
||||
{
|
||||
return static_cast<std::size_t>(t);
|
||||
}
|
||||
};
|
||||
|
||||
enum class StatScaling
|
||||
{
|
||||
// The stat is not scaled
|
||||
None,
|
||||
|
||||
// The stat is scaled by delta time, useful for per-second values
|
||||
ByDeltaTime,
|
||||
|
||||
// The stat is scaled by another counter, useful for ratios
|
||||
ByCounter
|
||||
};
|
||||
|
||||
enum class CounterSamplingMode
|
||||
{
|
||||
/// Sample counters only when calling update()
|
||||
Polling,
|
||||
/// Sample counters continuously, update circular buffers when calling update()
|
||||
Continuous
|
||||
};
|
||||
|
||||
struct CounterSamplingConfig
|
||||
{
|
||||
/// Sampling mode (polling or continuous)
|
||||
CounterSamplingMode mode;
|
||||
|
||||
/// Sampling interval in continuous mode
|
||||
std::chrono::milliseconds interval{1};
|
||||
|
||||
/// Speed of circular buffer updates in continuous mode;
|
||||
/// at speed = 1.0f a new sample is displayed over 1 second.
|
||||
float speed{0.5f};
|
||||
};
|
||||
|
||||
// Per-statistic graph data
|
||||
class StatGraphData
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs data for the graph
|
||||
* @param name Name of the Stat
|
||||
* @param format Format of the label
|
||||
* @param scale_factor Any scaling to apply to the data
|
||||
* @param has_fixed_max Whether the data should have a fixed max value
|
||||
* @param max_value The maximum value to use
|
||||
*/
|
||||
StatGraphData(const std::string &name,
|
||||
const std::string &format,
|
||||
float scale_factor = 1.0f,
|
||||
bool has_fixed_max = false,
|
||||
float max_value = 0.0f);
|
||||
|
||||
StatGraphData() = default;
|
||||
|
||||
std::string name;
|
||||
std::string format;
|
||||
float scale_factor;
|
||||
bool has_fixed_max;
|
||||
float max_value;
|
||||
};
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2020-2023, 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_provider.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
// Default graphing values for stats. May be overridden by individual providers.
|
||||
std::map<StatIndex, StatGraphData> StatsProvider::default_graph_map{
|
||||
// clang-format off
|
||||
// StatIndex Name shown in graph Format Scale Fixed_max Max_value
|
||||
{StatIndex::frame_times, {"Frame Times", "{:3.1f} ms", 1000.0f}},
|
||||
{StatIndex::cpu_cycles, {"CPU Cycles", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_instructions, {"CPU Instructions", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_cache_miss_ratio, {"Cache Miss Ratio", "{:3.1f}%", 100.0f, true, 100.0f}},
|
||||
{StatIndex::cpu_branch_miss_ratio, {"Branch Miss Ratio", "{:3.1f}%", 100.0f, true, 100.0f}},
|
||||
{StatIndex::cpu_l1_accesses, {"CPU L1 Accesses", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_instr_retired, {"CPU Instructions Retired", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_l2_accesses, {"CPU L2 Accesses", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_l3_accesses, {"CPU L3 Accesses", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_bus_reads, {"CPU Bus Read Beats", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_bus_writes, {"CPU Bus Write Beats", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_mem_reads, {"CPU Memory Read Instructions", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_mem_writes, {"CPU Memory Write Instructions", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_ase_spec, {"CPU Speculatively Exec. SIMD Instructions", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_vfp_spec, {"CPU Speculatively Exec. FP Instructions", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::cpu_crypto_spec, {"CPU Speculatively Exec. Crypto Instructions", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
|
||||
{StatIndex::gpu_cycles, {"GPU Cycles", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_vertex_cycles, {"Vertex Cycles", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_load_store_cycles, {"Load Store Cycles", "{:4.0f} k/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_tiles, {"Tiles", "{:4.1f} k/s", static_cast<float>(1e-3)}},
|
||||
{StatIndex::gpu_killed_tiles, {"Tiles killed by CRC match", "{:4.1f} k/s", static_cast<float>(1e-3)}},
|
||||
{StatIndex::gpu_fragment_jobs, {"Fragment Jobs", "{:4.0f}/s"}},
|
||||
{StatIndex::gpu_fragment_cycles, {"Fragment Cycles", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_tex_cycles, {"Shader Texture Cycles", "{:4.0f} k/s", static_cast<float>(1e-3)}},
|
||||
{StatIndex::gpu_ext_reads, {"External Reads", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_ext_writes, {"External Writes", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_ext_read_stalls, {"External Read Stalls", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_ext_write_stalls, {"External Write Stalls", "{:4.1f} M/s", static_cast<float>(1e-6)}},
|
||||
{StatIndex::gpu_ext_read_bytes, {"External Read Bytes", "{:4.1f} MiB/s", 1.0f / (1024.0f * 1024.0f)}},
|
||||
{StatIndex::gpu_ext_write_bytes, {"External Write Bytes", "{:4.1f} MiB/s", 1.0f / (1024.0f * 1024.0f)}},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// Static
|
||||
const StatGraphData &StatsProvider::default_graph_data(StatIndex index)
|
||||
{
|
||||
return default_graph_map.at(index);
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,105 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "stats_common.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
template <vkb::BindingType bindingType>
|
||||
class CommandBuffer;
|
||||
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
|
||||
} // namespace core
|
||||
|
||||
/**
|
||||
* @brief Abstract interface for all StatsProvider classes
|
||||
*/
|
||||
class StatsProvider
|
||||
{
|
||||
public:
|
||||
struct Counter
|
||||
{
|
||||
double result;
|
||||
};
|
||||
|
||||
using Counters = std::unordered_map<StatIndex, Counter, StatIndexHash>;
|
||||
|
||||
/**
|
||||
* @brief Virtual Destructor
|
||||
*/
|
||||
virtual ~StatsProvider()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Checks if this provider can supply the given enabled stat
|
||||
* @param index The stat index
|
||||
* @return True if the stat is available, false otherwise
|
||||
*/
|
||||
virtual bool is_available(StatIndex index) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Retrieve graphing data for the given enabled stat
|
||||
* @param index The stat index
|
||||
*/
|
||||
virtual const StatGraphData &get_graph_data(StatIndex index) const
|
||||
{
|
||||
return default_graph_map.at(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve default graphing data for the given stat
|
||||
* @param index The stat index
|
||||
*/
|
||||
static const StatGraphData &default_graph_data(StatIndex index);
|
||||
|
||||
/**
|
||||
* @brief Retrieve a new sample set
|
||||
* @param delta_time Time since last sample
|
||||
*/
|
||||
virtual Counters sample(float delta_time) = 0;
|
||||
|
||||
/**
|
||||
* @brief Retrieve a new sample set from continuous sampling
|
||||
* @param delta_time Time since last sample
|
||||
*/
|
||||
virtual Counters continuous_sample(float delta_time)
|
||||
{
|
||||
return Counters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A command buffer that we want stats about has just begun
|
||||
* @param cb The command buffer
|
||||
*/
|
||||
virtual void begin_sampling(vkb::core::CommandBufferC &cb)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief A command buffer that we want stats about is about to be ended
|
||||
* @param cb The command buffer
|
||||
*/
|
||||
virtual void end_sampling(vkb::core::CommandBufferC &cb)
|
||||
{}
|
||||
|
||||
protected:
|
||||
static std::map<StatIndex, StatGraphData> default_graph_map;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -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
|
||||
@@ -0,0 +1,163 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/query_pool.h"
|
||||
#include "stats_provider.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class RenderContext;
|
||||
|
||||
class VulkanStatsProvider : public StatsProvider
|
||||
{
|
||||
private:
|
||||
struct StatData
|
||||
{
|
||||
StatScaling scaling;
|
||||
uint32_t counter_index;
|
||||
uint32_t divisor_counter_index;
|
||||
VkPerformanceCounterStorageKHR storage;
|
||||
VkPerformanceCounterStorageKHR divisor_storage;
|
||||
StatGraphData graph_data;
|
||||
|
||||
StatData() = default;
|
||||
|
||||
StatData(uint32_t counter_index, VkPerformanceCounterStorageKHR storage,
|
||||
StatScaling stat_scaling = StatScaling::ByDeltaTime,
|
||||
uint32_t divisor_index = std::numeric_limits<uint32_t>::max(),
|
||||
VkPerformanceCounterStorageKHR divisor_storage = VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR) :
|
||||
scaling(stat_scaling),
|
||||
counter_index(counter_index),
|
||||
divisor_counter_index(divisor_index),
|
||||
storage(storage),
|
||||
divisor_storage(divisor_storage)
|
||||
{}
|
||||
};
|
||||
|
||||
struct VendorStat
|
||||
{
|
||||
VendorStat(const std::string &name, const std::string &divisor_name = "") :
|
||||
name(name),
|
||||
divisor_name(divisor_name)
|
||||
{
|
||||
if (divisor_name != "")
|
||||
scaling = StatScaling::ByCounter;
|
||||
}
|
||||
|
||||
void set_vendor_graph_data(const StatGraphData &data)
|
||||
{
|
||||
has_vendor_graph_data = true;
|
||||
graph_data = data;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
StatScaling scaling = StatScaling::ByDeltaTime;
|
||||
std::string divisor_name;
|
||||
bool has_vendor_graph_data = false;
|
||||
StatGraphData graph_data;
|
||||
};
|
||||
|
||||
using StatDataMap = std::unordered_map<StatIndex, StatData, StatIndexHash>;
|
||||
using VendorStatMap = std::unordered_map<StatIndex, VendorStat, StatIndexHash>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a VulkanStatsProvider
|
||||
* @param requested_stats Set of stats to be collected. Supported stats will be removed from the set.
|
||||
* @param sampling_config Sampling mode configuration (polling or continuous)
|
||||
* @param render_context The render context
|
||||
*/
|
||||
VulkanStatsProvider(std::set<StatIndex> &requested_stats, const CounterSamplingConfig &sampling_config,
|
||||
RenderContext &render_context);
|
||||
|
||||
/**
|
||||
* @brief Destructs a VulkanStatsProvider
|
||||
*/
|
||||
~VulkanStatsProvider();
|
||||
|
||||
/**
|
||||
* @brief Checks if this provider can supply the given enabled stat
|
||||
* @param index The stat index
|
||||
* @return True if the stat is available, false otherwise
|
||||
*/
|
||||
bool is_available(StatIndex index) const override;
|
||||
|
||||
/**
|
||||
* @brief Retrieve graphing data for the given enabled stat
|
||||
* @param index The stat index
|
||||
*/
|
||||
const StatGraphData &get_graph_data(StatIndex index) const override;
|
||||
|
||||
/**
|
||||
* @brief Retrieve a new sample set from polled sampling
|
||||
* @param delta_time Time since last sample
|
||||
*/
|
||||
Counters sample(float delta_time) override;
|
||||
|
||||
/**
|
||||
* @brief A command buffer that we want stats about has just begun
|
||||
* @param cb The command buffer
|
||||
*/
|
||||
void begin_sampling(vkb::core::CommandBufferC &cb) override;
|
||||
|
||||
/**
|
||||
* @brief A command buffer that we want stats about is about to be ended
|
||||
* @param cb The command buffer
|
||||
*/
|
||||
void end_sampling(vkb::core::CommandBufferC &cb) override;
|
||||
|
||||
private:
|
||||
bool is_supported(const CounterSamplingConfig &sampling_config) const;
|
||||
|
||||
bool fill_vendor_data();
|
||||
|
||||
bool create_query_pools(uint32_t queue_family_index);
|
||||
|
||||
float get_best_delta_time(float sw_delta_time) const;
|
||||
|
||||
private:
|
||||
// The render context
|
||||
RenderContext &render_context;
|
||||
|
||||
// The query pool for the performance queries
|
||||
std::unique_ptr<QueryPool> query_pool;
|
||||
|
||||
// Do we support timestamp queries
|
||||
bool has_timestamps{false};
|
||||
|
||||
// The timestamp period
|
||||
float timestamp_period{1.0f};
|
||||
|
||||
// Query pool for timestamps
|
||||
std::unique_ptr<QueryPool> timestamp_pool;
|
||||
|
||||
// Map of vendor specific stat data
|
||||
VendorStatMap vendor_data;
|
||||
|
||||
// Only stats which are available and were requested end up in stat_data
|
||||
StatDataMap stat_data;
|
||||
|
||||
// An ordered list of the Vulkan counter ids
|
||||
std::vector<uint32_t> counter_indices;
|
||||
|
||||
// How many queries have been ended?
|
||||
uint32_t queries_ready = 0;
|
||||
};
|
||||
|
||||
} // namespace vkb
|
||||
Reference in New Issue
Block a user