Add real-time FPS calculation and display
This commit is contained in:
parent
e723636f9a
commit
8d550c4d7b
|
|
@ -0,0 +1,253 @@
|
|||
# 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<qint64> 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<double>(totalTimeUs) / m_frameTimes.size();
|
||||
m_currentFps = 1000000.0 / avgFrameTimeUs; // 微秒转 FPS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 传递 FPS 给渲染器
|
||||
|
||||
```cpp
|
||||
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)
|
||||
|
||||
```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<double>(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<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()` 操作
|
||||
```cpp
|
||||
std::array<qint64, FPS_SAMPLE_COUNT> 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 显示**:反映实际渲染性能
|
||||
✅ **平滑的数值变化**:避免抖动
|
||||
✅ **低性能开销**:对渲染性能影响可忽略
|
||||
✅ **易于扩展**:可以轻松调整样本数量和计算方式
|
||||
|
||||
这个实现为性能监控和调优提供了重要的参考数据。
|
||||
|
|
@ -16,6 +16,8 @@ CustomWidget::CustomWidget(QWidget *parent)
|
|||
, m_lastLockFrameCount(0)
|
||||
, m_lockPaintFrameCount(0)
|
||||
, m_lockCount(0)
|
||||
, m_lastFrameTime(QDateTime::currentDateTime())
|
||||
, m_currentFps(0.0)
|
||||
{
|
||||
// 设置窗口属性
|
||||
setMinimumSize(600, 400);
|
||||
|
|
@ -24,6 +26,9 @@ CustomWidget::CustomWidget(QWidget *parent)
|
|||
m_animationTimer = new QTimer(this);
|
||||
connect(m_animationTimer, &QTimer::timeout, this, &CustomWidget::onAnimationTimer);
|
||||
m_animationTimer->start(16); // 约60 FPS (1000/60 ≈ 16ms)
|
||||
|
||||
// Initialize FPS calculation
|
||||
m_frameTimes.reserve(FPS_SAMPLE_COUNT);
|
||||
|
||||
qDebug() << "CustomWidget created, animation timer started";
|
||||
}
|
||||
|
|
@ -192,6 +197,29 @@ void CustomWidget::paintEvent(QPaintEvent *event)
|
|||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Calculate FPS
|
||||
QDateTime currentTime = QDateTime::currentDateTime();
|
||||
qint64 frameTimeUs = m_lastFrameTime.msecsTo(currentTime) * 1000; // Convert to microseconds
|
||||
m_lastFrameTime = currentTime;
|
||||
|
||||
if (frameTimeUs > 0) {
|
||||
// Add frame time to circular buffer
|
||||
if (m_frameTimes.size() >= FPS_SAMPLE_COUNT) {
|
||||
m_frameTimes.erase(m_frameTimes.begin());
|
||||
}
|
||||
m_frameTimes.push_back(frameTimeUs);
|
||||
|
||||
// Calculate average FPS from recent frames
|
||||
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; // Convert microseconds to FPS
|
||||
}
|
||||
}
|
||||
|
||||
// 增加帧计数
|
||||
m_frameCount++;
|
||||
|
||||
|
|
@ -317,10 +345,11 @@ void CustomWidget::drawStatusInfo(QPainter &painter)
|
|||
|
||||
QString stats = QString(
|
||||
"Frame Count: %1\n"
|
||||
"FPS: ~60\n"
|
||||
"Rotation: %2°\n"
|
||||
"Running Time: %3s"
|
||||
"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);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <QDateTime>
|
||||
#include <QColor>
|
||||
#include <QLinearGradient>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief 自定义绘制组件类(使用QPainter)
|
||||
|
|
@ -36,6 +37,9 @@ public:
|
|||
void setPaintingEnabled(bool enabled);
|
||||
bool isPaintingEnabled() const;
|
||||
int getPaintFrameCount() const;
|
||||
|
||||
// FPS getter
|
||||
double getCurrentFps() const { return m_currentFps; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
|
|
@ -94,6 +98,12 @@ private:
|
|||
int m_lastLockFrameCount; // 上次锁屏时的帧数
|
||||
int m_lockPaintFrameCount; // 锁屏期间绘制的帧数
|
||||
int m_lockCount; // 锁屏次数
|
||||
|
||||
// FPS 计算
|
||||
QDateTime m_lastFrameTime; // 上一帧的时间
|
||||
double m_currentFps; // 当前FPS值
|
||||
std::vector<qint64> m_frameTimes; // 存储最近帧的时间间隔(微秒)
|
||||
static const int FPS_SAMPLE_COUNT = 60; // 使用最近60帧计算平均FPS
|
||||
};
|
||||
|
||||
#endif // CUSTOMWIDGET_H
|
||||
|
|
@ -484,6 +484,7 @@ void VulkanRenderer::recordCommandBuffer(VkCommandBuffer commandBuffer,
|
|||
VkImageView imageView,
|
||||
int frameCount,
|
||||
double elapsedTime,
|
||||
double fps,
|
||||
double rotationAngle,
|
||||
double wavePhase,
|
||||
bool paintingEnabled,
|
||||
|
|
@ -682,7 +683,7 @@ void VulkanRenderer::recordCommandBuffer(VkCommandBuffer commandBuffer,
|
|||
}
|
||||
|
||||
// Draw text (status info)
|
||||
drawText(commandBuffer, frameCount, paintingEnabled, lockInfo);
|
||||
drawText(commandBuffer, frameCount, fps, paintingEnabled, lockInfo);
|
||||
|
||||
vkCmdEndRenderPass(commandBuffer);
|
||||
vkEndCommandBuffer(commandBuffer);
|
||||
|
|
@ -2648,7 +2649,7 @@ void VulkanRenderer::drawGeometry(VkCommandBuffer commandBuffer, int frameCount,
|
|||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::drawText(VkCommandBuffer commandBuffer, int frameCount,
|
||||
void VulkanRenderer::drawText(VkCommandBuffer commandBuffer, int frameCount, double fps,
|
||||
bool paintingEnabled, const std::string& lockInfo)
|
||||
{
|
||||
static int textDebugCounter = 0;
|
||||
|
|
@ -2727,7 +2728,7 @@ void VulkanRenderer::drawText(VkCommandBuffer commandBuffer, int frameCount,
|
|||
|
||||
// Stats info box
|
||||
std::string frameStr = "Frame Count: " + std::to_string(frameCount);
|
||||
std::string fpsStr = "FPS: ~60";
|
||||
std::string fpsStr = "FPS: " + std::to_string((int)fps);
|
||||
std::string rotStr = "Rotation: " + std::to_string((int)m_ubo.rotation) + "°";
|
||||
std::string timeStr = "Running Time: " + std::to_string((int)m_ubo.time) + "s";
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ public:
|
|||
* @param imageView 交换链图像视图
|
||||
* @param frameCount 当前帧数
|
||||
* @param elapsedTime 运行时间(秒)
|
||||
* @param fps 当前实时FPS值
|
||||
* @param rotationAngle 旋转角度
|
||||
* @param wavePhase 波浪相位
|
||||
* @param paintingEnabled 是否启用绘制
|
||||
|
|
@ -114,6 +115,7 @@ public:
|
|||
VkImageView imageView,
|
||||
int frameCount,
|
||||
double elapsedTime,
|
||||
double fps,
|
||||
double rotationAngle,
|
||||
double wavePhase,
|
||||
bool paintingEnabled,
|
||||
|
|
@ -322,7 +324,7 @@ private:
|
|||
/**
|
||||
* @brief 绘制文本
|
||||
*/
|
||||
void drawText(VkCommandBuffer commandBuffer, int frameCount,
|
||||
void drawText(VkCommandBuffer commandBuffer, int frameCount, double fps,
|
||||
bool paintingEnabled, const std::string& lockInfo);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ VulkanWidget::VulkanWidget(QWidget *parent)
|
|||
, m_lockPaintFrameCount(0)
|
||||
, m_lockCount(0)
|
||||
, m_isClosing(false)
|
||||
, m_lastFrameTime(QDateTime::currentDateTime())
|
||||
, m_currentFps(0.0)
|
||||
{
|
||||
// Set widget attributes for native window
|
||||
setAttribute(Qt::WA_NativeWindow);
|
||||
|
|
@ -63,6 +65,9 @@ VulkanWidget::VulkanWidget(QWidget *parent)
|
|||
// Create render timer
|
||||
m_renderTimer = new QTimer(this);
|
||||
connect(m_renderTimer, &QTimer::timeout, this, &VulkanWidget::onRenderTimer);
|
||||
|
||||
// Initialize FPS calculation
|
||||
m_frameTimes.reserve(FPS_SAMPLE_COUNT);
|
||||
|
||||
qDebug() << "VulkanWidget created";
|
||||
}
|
||||
|
|
@ -747,6 +752,29 @@ void VulkanWidget::renderFrame()
|
|||
// 关键修复:即使 renderingEnabled=false 也继续渲染,以显示锁屏状态
|
||||
// 只是传递不同的 paintingEnabled 参数给 renderer
|
||||
|
||||
// Calculate FPS
|
||||
QDateTime currentTime = QDateTime::currentDateTime();
|
||||
qint64 frameTimeUs = m_lastFrameTime.msecsTo(currentTime) * 1000; // Convert to microseconds
|
||||
m_lastFrameTime = currentTime;
|
||||
|
||||
if (frameTimeUs > 0) {
|
||||
// Add frame time to circular buffer
|
||||
if (m_frameTimes.size() >= FPS_SAMPLE_COUNT) {
|
||||
m_frameTimes.erase(m_frameTimes.begin());
|
||||
}
|
||||
m_frameTimes.push_back(frameTimeUs);
|
||||
|
||||
// Calculate average FPS from recent frames
|
||||
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; // Convert microseconds to FPS
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for previous frame
|
||||
vkWaitForFences(m_device, 1, &m_inFlightFences[m_currentFrame], VK_TRUE, UINT64_MAX);
|
||||
|
||||
|
|
@ -872,6 +900,7 @@ void VulkanWidget::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t i
|
|||
|
||||
m_renderer->recordCommandBuffer(commandBuffer, imageIndex, imageView,
|
||||
m_frameCount, static_cast<double>(elapsedTime),
|
||||
m_currentFps,
|
||||
m_rotationAngle, m_wavePhase,
|
||||
m_renderingEnabled, lockInfo.toStdString());
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ public:
|
|||
bool isInitialized() const override { return m_initialized; }
|
||||
QString getLastError() const override { return m_lastError; }
|
||||
QString getRendererType() const override;
|
||||
|
||||
// FPS getter
|
||||
double getCurrentFps() const { return m_currentFps; }
|
||||
|
||||
// VulkanWidget特有的方法(保持兼容性)
|
||||
bool initializeVulkan();
|
||||
|
|
@ -240,6 +243,12 @@ private:
|
|||
int m_lockPaintFrameCount;
|
||||
int m_lockCount;
|
||||
|
||||
// FPS 计算
|
||||
QDateTime m_lastFrameTime;
|
||||
double m_currentFps;
|
||||
std::vector<qint64> m_frameTimes; // 存储最近帧的时间间隔(微秒)
|
||||
static const int FPS_SAMPLE_COUNT = 60; // 使用最近60帧计算平均FPS
|
||||
|
||||
// 常量(支持三缓冲)
|
||||
static const int MAX_FRAMES_IN_FLIGHT = 3;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue