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