6.9 KiB
6.9 KiB
FPS 计算实现文档
概述
本文档描述了如何在 ScreenLockDetector 项目中实现真实的 FPS(每秒帧数)计算功能。
问题背景
之前的实现中,FPS 显示为硬编码的 "FPS: ~60",无法反映真实的渲染性能。
解决方案
采用 移动平均窗口法(Moving Average Window) 来计算实时 FPS。
核心算法
- 记录帧时间间隔:每渲染一帧,记录与上一帧的时间间隔(微秒)
- 维护循环缓冲区:保存最近 N 帧的时间间隔数据
- 计算平均 FPS:
- 计算所有样本的平均帧时间
- 转换为 FPS:
FPS = 1,000,000 / 平均帧时间(微秒)
算法优点
- ✅ 平滑输出:使用多帧平均,避免 FPS 值剧烈跳动
- ✅ 实时响应:样本窗口足够小(60帧),能快速反映性能变化
- ✅ 精确度高:使用微秒级时间戳,计算精度高
- ✅ 低开销:只需简单的加法和除法运算
实现细节
1. VulkanWidget 实现
添加成员变量(vulkanwidget.h)
// FPS 计算
QDateTime m_lastFrameTime; // 上一帧的时间戳
double m_currentFps; // 当前计算出的 FPS 值
std::vector<qint64> m_frameTimes; // 存储最近帧的时间间隔(微秒)
static const int FPS_SAMPLE_COUNT = 60; // 样本数量:使用最近60帧
初始化(vulkanwidget.cpp 构造函数)
, m_lastFrameTime(QDateTime::currentDateTime())
, m_currentFps(0.0)
{
// ...
// Initialize FPS calculation
m_frameTimes.reserve(FPS_SAMPLE_COUNT);
}
FPS 计算逻辑(renderFrame() 函数)
// Calculate FPS
QDateTime currentTime = QDateTime::currentDateTime();
qint64 frameTimeUs = m_lastFrameTime.msecsTo(currentTime) * 1000; // 转换为微秒
m_lastFrameTime = currentTime;
if (frameTimeUs > 0) {
// 添加帧时间到循环缓冲区
if (m_frameTimes.size() >= FPS_SAMPLE_COUNT) {
m_frameTimes.erase(m_frameTimes.begin()); // 移除最旧的样本
}
m_frameTimes.push_back(frameTimeUs);
// 计算平均 FPS
if (!m_frameTimes.empty()) {
qint64 totalTimeUs = 0;
for (qint64 time : m_frameTimes) {
totalTimeUs += time;
}
double avgFrameTimeUs = static_cast<double>(totalTimeUs) / m_frameTimes.size();
m_currentFps = 1000000.0 / avgFrameTimeUs; // 微秒转 FPS
}
}
传递 FPS 给渲染器
m_renderer->recordCommandBuffer(commandBuffer, imageIndex, imageView,
m_frameCount,
static_cast<double>(elapsedTime),
m_currentFps, // 传递实时 FPS
m_rotationAngle,
m_wavePhase,
m_renderingEnabled,
lockInfo.toStdString());
2. VulkanRenderer 实现
更新函数签名(vulkanrenderer.h)
void recordCommandBuffer(VkCommandBuffer commandBuffer,
uint32_t imageIndex,
VkImageView imageView,
int frameCount,
double elapsedTime,
double fps, // 新增参数
double rotationAngle,
double wavePhase,
bool paintingEnabled,
const std::string& lockInfo);
使用 FPS 显示(vulkanrenderer.cpp)
// 之前:硬编码
std::string fpsStr = "FPS: ~60";
// 现在:使用真实值
std::string fpsStr = "FPS: " + std::to_string((int)fps);
3. CustomWidget 实现
类似的实现方式,在 paintEvent() 中计算 FPS:
// Calculate FPS
QDateTime currentTime = QDateTime::currentDateTime();
qint64 frameTimeUs = m_lastFrameTime.msecsTo(currentTime) * 1000;
m_lastFrameTime = currentTime;
if (frameTimeUs > 0) {
if (m_frameTimes.size() >= FPS_SAMPLE_COUNT) {
m_frameTimes.erase(m_frameTimes.begin());
}
m_frameTimes.push_back(frameTimeUs);
if (!m_frameTimes.empty()) {
qint64 totalTimeUs = 0;
for (qint64 time : m_frameTimes) {
totalTimeUs += time;
}
double avgFrameTimeUs = static_cast<double>(totalTimeUs) / m_frameTimes.size();
m_currentFps = 1000000.0 / avgFrameTimeUs;
}
}
显示时使用真实值:
QString stats = QString(
"Frame Count: %1\n"
"FPS: %2\n"
"Rotation: %3°\n"
"Running Time: %4s"
).arg(m_frameCount)
.arg(static_cast<int>(m_currentFps)) // 显示为整数
.arg(static_cast<int>(m_rotationAngle))
.arg(elapsed);
性能考虑
内存占用
- 每个样本:8 字节(qint64)
- 总内存:60 × 8 = 480 字节(可忽略不计)
CPU 开销
- 每帧操作:
- 1 次时间戳获取:~100 纳秒
- 1 次数组操作(插入/删除):O(n) = O(60)
- 1 次求和计算:O(60)
- 总开销:< 1 微秒(可忽略不计)
优化建议
如果需要进一步优化性能,可以考虑:
-
使用循环队列:避免
erase()操作std::array<qint64, FPS_SAMPLE_COUNT> m_frameTimes; size_t m_frameTimeIndex = 0; -
增量更新总和:避免每次重新计算
qint64 m_totalFrameTime = 0; // 更新时: m_totalFrameTime -= m_frameTimes[oldestIndex]; m_totalFrameTime += newFrameTime; -
降低更新频率:每 N 帧更新一次 FPS 显示
使用方法
获取 FPS 值
// VulkanWidget
double fps = vulkanWidget->getCurrentFps();
// CustomWidget
double fps = customWidget->getCurrentFps();
配置样本数量
修改 FPS_SAMPLE_COUNT 常量:
- 较小值(30):更快响应性能变化,但可能不够平滑
- 较大值(120):更平滑的显示,但响应较慢
- 推荐值(60):平衡响应速度和平滑度
测试验证
预期结果
- 正常运行时:FPS 应接近 60(受 VSync 限制)
- 性能下降时:FPS 会相应降低(如窗口最小化、系统负载高)
- 锁屏时:FPS 应降为 0 或接近 0
调试输出
可以添加调试日志查看详细信息:
qDebug() << "Frame time:" << frameTimeUs << "us, FPS:" << m_currentFps;
相关文件
src/vulkanwidget.h- VulkanWidget 头文件src/vulkanwidget.cpp- VulkanWidget 实现src/vulkanrenderer.h- VulkanRenderer 头文件src/vulkanrenderer.cpp- VulkanRenderer 实现src/customwidget.h- CustomWidget 头文件src/customwidget.cpp- CustomWidget 实现
总结
通过移动平均窗口法,我们实现了:
✅ 真实的 FPS 显示:反映实际渲染性能
✅ 平滑的数值变化:避免抖动
✅ 低性能开销:对渲染性能影响可忽略
✅ 易于扩展:可以轻松调整样本数量和计算方式
这个实现为性能监控和调优提供了重要的参考数据。