ScreenLockDetector/docs/FPS_CALCULATION.md

6.9 KiB
Raw Permalink Blame History

FPS 计算实现文档

概述

本文档描述了如何在 ScreenLockDetector 项目中实现真实的 FPS每秒帧数计算功能。

问题背景

之前的实现中FPS 显示为硬编码的 "FPS: ~60",无法反映真实的渲染性能。

解决方案

采用 移动平均窗口法Moving Average Window 来计算实时 FPS。

核心算法

  1. 记录帧时间间隔:每渲染一帧,记录与上一帧的时间间隔(微秒)
  2. 维护循环缓冲区:保存最近 N 帧的时间间隔数据
  3. 计算平均 FPS
    • 计算所有样本的平均帧时间
    • 转换为 FPSFPS = 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 微秒(可忽略不计)

优化建议

如果需要进一步优化性能,可以考虑:

  1. 使用循环队列:避免 erase() 操作

    std::array<qint64, FPS_SAMPLE_COUNT> m_frameTimes;
    size_t m_frameTimeIndex = 0;
    
  2. 增量更新总和:避免每次重新计算

    qint64 m_totalFrameTime = 0;
    // 更新时:
    m_totalFrameTime -= m_frameTimes[oldestIndex];
    m_totalFrameTime += newFrameTime;
    
  3. 降低更新频率:每 N 帧更新一次 FPS 显示

使用方法

获取 FPS 值

// 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

调试输出

可以添加调试日志查看详细信息:

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 显示:反映实际渲染性能
平滑的数值变化:避免抖动
低性能开销:对渲染性能影响可忽略
易于扩展:可以轻松调整样本数量和计算方式

这个实现为性能监控和调优提供了重要的参考数据。