Modules/Graphics/private/GpuTimer.cpp
Шурупов Илья Викторович 37a650b1e8 Add frame profiling, GPU timers and input recording to the libraries
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.
2026-10-15 20:03:20 +03:00

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);
}
}
}