Profiler (Modules) collects per-frame wall and thread CPU time, named inclusive sections, counters and GPU durations. It is off by default, so a disabled scope costs one branch. Application, WidgetApplication, VulkanContext and Canvas mark the frame phases (process, dispatch, advance, draw, paint, UI flush, acquire, submit, present). GpuTimer wraps Vulkan timestamp queries in a small ring that is read without stalling; the window frame and any RenderBuffer that asks for it report GPU time into the profiler. InputRecording stores raw window input with exact timing, pen pressure and tilt as a line-based text file, and InputRecorder captures it through a new raw tap in the InputCoalescer that sees every platform event before coalescing.
76 lines
2.6 KiB
C++
76 lines
2.6 KiB
C++
#include "GpuTimer.hpp"
|
|
|
|
#include <vector>
|
|
|
|
namespace tp {
|
|
|
|
GpuTimer::GpuTimer(VkDevice device, VkPhysicalDevice physicalDevice, uint32_t queueFamily, const char* name) :
|
|
mDevice(device),
|
|
mSection(Profiler::global().gpuSection(name)),
|
|
mPending(kSlots, false) {
|
|
VkPhysicalDeviceProperties properties{};
|
|
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
|
|
|
|
uint32_t familyCount = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr);
|
|
std::vector<VkQueueFamilyProperties> families(familyCount);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data());
|
|
|
|
const uint32_t validBits = queueFamily < familyCount ? families[queueFamily].timestampValidBits : 0;
|
|
if (validBits == 0 || properties.limits.timestampPeriod <= 0.0f) return;
|
|
|
|
mTickNs = properties.limits.timestampPeriod;
|
|
mValidMask = validBits >= 64 ? ~0ull : ((1ull << validBits) - 1ull);
|
|
|
|
VkQueryPoolCreateInfo info{
|
|
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
|
.queryType = VK_QUERY_TYPE_TIMESTAMP,
|
|
.queryCount = kSlots * 2,
|
|
};
|
|
if (vkCreateQueryPool(mDevice, &info, nullptr, &mPool) != VK_SUCCESS) mPool = VK_NULL_HANDLE;
|
|
}
|
|
|
|
GpuTimer::~GpuTimer() {
|
|
if (mPool != VK_NULL_HANDLE) vkDestroyQueryPool(mDevice, mPool, nullptr);
|
|
}
|
|
|
|
void GpuTimer::begin(VkCommandBuffer commandBuffer) {
|
|
mOpenSlot = UINT32_MAX;
|
|
if (!isSupported() || !Profiler::global().isEnabled()) return;
|
|
|
|
collect();
|
|
const uint32_t slot = mNextSlot;
|
|
if (mPending[slot]) return;
|
|
|
|
vkCmdResetQueryPool(commandBuffer, mPool, slot * 2, 2);
|
|
vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mPool, slot * 2);
|
|
mOpenSlot = slot;
|
|
}
|
|
|
|
void GpuTimer::end(VkCommandBuffer commandBuffer) {
|
|
if (mOpenSlot == UINT32_MAX) return;
|
|
vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, mOpenSlot * 2 + 1);
|
|
mPending[mOpenSlot] = true;
|
|
mNextSlot = (mOpenSlot + 1) % kSlots;
|
|
mOpenSlot = UINT32_MAX;
|
|
}
|
|
|
|
void GpuTimer::collect() {
|
|
if (!isSupported()) return;
|
|
for (uint32_t offset = 0; offset < kSlots; offset++) {
|
|
const uint32_t slot = (mNextSlot + offset) % kSlots;
|
|
if (!mPending[slot]) continue;
|
|
|
|
uint64_t stamps[2] = {};
|
|
const VkResult result = vkGetQueryPoolResults(
|
|
mDevice, mPool, slot * 2, 2, sizeof(stamps), stamps, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT
|
|
);
|
|
if (result != VK_SUCCESS) continue;
|
|
|
|
mPending[slot] = false;
|
|
const uint64_t ticks = (stamps[1] - stamps[0]) & mValidMask;
|
|
mLastMs = (double) ticks * mTickNs / 1.0e6;
|
|
Profiler::global().addGpuTime(mSection, mLastMs);
|
|
}
|
|
}
|
|
}
|