Compare commits
No commits in common. "ad3524bd235150c09824a018081eb798c542335e" and "8d550c4d7b2530319b44d77cccc53d121cbf7903" have entirely different histories.
ad3524bd23
...
8d550c4d7b
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(ScreenLockDetector VERSION 1.3.0 LANGUAGES C CXX)
|
||||
project(ScreenLockDetector VERSION 1.0.0 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
|
@ -123,20 +123,6 @@ else()
|
|||
message(STATUS "Vulkan support disabled")
|
||||
endif()
|
||||
|
||||
# Generate version header from template (for C++ code)
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/version.h"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Generate version shell script from template (for shell scripts)
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/version.sh.in"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/version.sh"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Common source files
|
||||
set(SOURCES
|
||||
src/main.cpp
|
||||
|
|
@ -144,7 +130,6 @@ set(SOURCES
|
|||
src/customwidget.cpp
|
||||
src/screenlockdetector.cpp
|
||||
src/screenlockdetector_base.cpp
|
||||
src/powermonitor.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
|
|
@ -153,7 +138,6 @@ set(HEADERS
|
|||
src/customwidget.h
|
||||
src/screenlockdetector.h
|
||||
src/screenlockdetector_base.h
|
||||
src/powermonitor.h
|
||||
)
|
||||
|
||||
# Add Vulkan widget if Vulkan is available
|
||||
|
|
@ -201,11 +185,6 @@ add_executable(${PROJECT_NAME}
|
|||
${HEADERS}
|
||||
)
|
||||
|
||||
# Include build directory for generated version.h
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Link Qt5 libraries
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
Qt5::Core
|
||||
|
|
|
|||
|
|
@ -0,0 +1,242 @@
|
|||
# ScreenLockDetector 重构总结
|
||||
|
||||
## 重构完成时间
|
||||
2024年11月
|
||||
|
||||
## 重构目标
|
||||
使用公共抽象类重构 ScreenLockDetector,按 Linux 和 MacOS 分别构建子类,实现更好的面向对象设计。
|
||||
|
||||
## 重构成果
|
||||
|
||||
### ✅ 已完成的工作
|
||||
|
||||
1. **创建抽象基类**
|
||||
- `screenlockdetector_base.h` - 定义跨平台公共接口
|
||||
- `screenlockdetector_base.cpp` - 实现公共逻辑(状态管理、信号发射)
|
||||
|
||||
2. **Linux 平台实现**
|
||||
- `screenlockdetector_linux.h` - Linux 子类头文件
|
||||
- `screenlockdetector_linux.cpp` - Linux 子类实现
|
||||
- 支持:Deepin DDE、GNOME、KDE、XFCE、UKUI 等桌面环境
|
||||
- 使用 Qt DBus API 监听系统事件
|
||||
|
||||
3. **MacOS 平台实现**
|
||||
- `screenlockdetector_macos.h` - MacOS 子类头文件(重命名自 screenlockdetector_mac.h)
|
||||
- `screenlockdetector_macos.mm` - MacOS 子类实现(重命名自 screenlockdetector_mac.mm)
|
||||
- 使用 NSDistributedNotificationCenter 监听系统通知
|
||||
- 支持屏幕锁定/解锁和屏保事件
|
||||
|
||||
4. **重构主类为工厂类**
|
||||
- `screenlockdetector.h` - 工厂类头文件(大幅简化)
|
||||
- `screenlockdetector.cpp` - 工厂类实现(移除所有平台特定代码)
|
||||
- 实现 `createPlatformDetector()` 工厂方法
|
||||
- 完全向后兼容的公共 API
|
||||
|
||||
5. **构建系统更新**
|
||||
- 更新 `CMakeLists.txt` 添加新的源文件
|
||||
- 保持平台条件编译逻辑
|
||||
- 自动选择正确的平台实现
|
||||
|
||||
6. **文档完善**
|
||||
- `docs/REFACTORING.md` - 详细的重构说明文档
|
||||
- `docs/MIGRATION_GUIDE.md` - 用户迁移指南
|
||||
- `docs/CLASS_DIAGRAM.md` - UML 类图和设计模式说明
|
||||
- `REFACTORING_SUMMARY.md` - 本总结文档
|
||||
|
||||
## 架构对比
|
||||
|
||||
### 重构前
|
||||
```
|
||||
ScreenLockDetector (单一类)
|
||||
├── #ifdef Q_OS_LINUX
|
||||
│ ├── QDBusInterface 相关代码
|
||||
│ ├── Linux 信号处理槽函数
|
||||
│ └── Linux 私有成员变量
|
||||
├── #ifdef Q_OS_MAC
|
||||
│ ├── ScreenLockDetectorMac 辅助类
|
||||
│ └── macOS 私有成员变量
|
||||
└── 大量条件编译指令
|
||||
```
|
||||
|
||||
### 重构后
|
||||
```
|
||||
ScreenLockDetectorBase (抽象基类)
|
||||
├── 公共接口定义
|
||||
├── 公共状态管理
|
||||
└── 信号定义
|
||||
|
||||
ScreenLockDetectorLinux (Linux 实现)
|
||||
├── 继承自 ScreenLockDetectorBase
|
||||
├── DBus 接口管理
|
||||
└── Linux 特定逻辑
|
||||
|
||||
ScreenLockDetectorMacOS (macOS 实现)
|
||||
├── 继承自 ScreenLockDetectorBase
|
||||
├── Objective-C 观察者管理
|
||||
└── macOS 特定逻辑
|
||||
|
||||
ScreenLockDetector (工厂类)
|
||||
├── 创建平台特定实例
|
||||
├── 转发信号
|
||||
└── 提供统一接口
|
||||
```
|
||||
|
||||
## 代码统计
|
||||
|
||||
### 新增文件(6个)
|
||||
- `src/screenlockdetector_base.h` (66 行)
|
||||
- `src/screenlockdetector_base.cpp` (36 行)
|
||||
- `src/screenlockdetector_linux.h` (98 行)
|
||||
- `src/screenlockdetector_linux.cpp` (380 行)
|
||||
- `src/screenlockdetector_macos.h` (68 行)
|
||||
- `src/screenlockdetector_macos.mm` (218 行)
|
||||
|
||||
### 修改文件(3个)
|
||||
- `src/screenlockdetector.h` (减少约 70 行代码)
|
||||
- `src/screenlockdetector.cpp` (减少约 375 行代码)
|
||||
- `CMakeLists.txt` (更新源文件列表)
|
||||
|
||||
### 文档文件(3个)
|
||||
- `docs/REFACTORING.md` (234 行)
|
||||
- `docs/MIGRATION_GUIDE.md` (296 行)
|
||||
- `docs/CLASS_DIAGRAM.md` (270 行)
|
||||
|
||||
### 代码改进
|
||||
- 减少条件编译指令:约 80%
|
||||
- 提高代码模块化:每个平台独立文件
|
||||
- 增强可测试性:可单独测试每个平台
|
||||
- 改善可维护性:职责清晰分离
|
||||
|
||||
## 设计模式应用
|
||||
|
||||
1. **工厂方法模式 (Factory Method)**
|
||||
- `ScreenLockDetector::createPlatformDetector()` 根据平台创建实例
|
||||
|
||||
2. **模板方法模式 (Template Method)**
|
||||
- 基类定义 `initialize()` 接口
|
||||
- 子类实现平台特定的初始化逻辑
|
||||
|
||||
3. **门面模式 (Facade)**
|
||||
- `ScreenLockDetector` 为客户端提供简单统一的接口
|
||||
- 隐藏内部平台检测的复杂性
|
||||
|
||||
## 主要优势
|
||||
|
||||
### 1. 代码组织
|
||||
- ✅ 平台代码完全分离
|
||||
- ✅ 消除大量 `#ifdef` 条件编译
|
||||
- ✅ 每个类职责单一明确
|
||||
|
||||
### 2. 可维护性
|
||||
- ✅ 修改 Linux 代码不影响 macOS
|
||||
- ✅ 修改 macOS 代码不影响 Linux
|
||||
- ✅ 易于定位和修复平台特定 bug
|
||||
|
||||
### 3. 可扩展性
|
||||
- ✅ 添加新平台只需创建新子类
|
||||
- ✅ 无需修改现有平台代码
|
||||
- ✅ 符合开闭原则(对扩展开放,对修改关闭)
|
||||
|
||||
### 4. 可测试性
|
||||
- ✅ 可为每个平台创建独立测试
|
||||
- ✅ 可模拟基类进行单元测试
|
||||
- ✅ 减少平台相关的测试依赖
|
||||
|
||||
### 5. 向后兼容
|
||||
- ✅ 公共 API 完全保持不变
|
||||
- ✅ 现有代码无需修改
|
||||
- ✅ 信号定义保持一致
|
||||
|
||||
## 兼容性保证
|
||||
|
||||
### API 兼容性:100%
|
||||
```cpp
|
||||
// 所有现有代码都可以继续使用,无需修改
|
||||
ScreenLockDetector *detector = new ScreenLockDetector(parent);
|
||||
detector->initialize();
|
||||
bool locked = detector->isScreenLocked();
|
||||
connect(detector, &ScreenLockDetector::screenLocked, ...);
|
||||
```
|
||||
|
||||
### 平台支持
|
||||
- ✅ Linux (Deepin/UOS, Ubuntu, Fedora, KylinOS, etc.)
|
||||
- ✅ macOS (10.x+)
|
||||
- ⏳ Windows (未来可轻松添加)
|
||||
- ⏳ Android/iOS (未来可添加)
|
||||
|
||||
## 构建验证
|
||||
|
||||
### Linux 构建
|
||||
```bash
|
||||
cd ScreenLockDetector
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
# 编译成功,所有平台特定代码正确分离
|
||||
```
|
||||
|
||||
### macOS 构建
|
||||
```bash
|
||||
cd ScreenLockDetector
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
# 编译成功,Objective-C++ 文件正确处理
|
||||
```
|
||||
|
||||
## 未来扩展建议
|
||||
|
||||
### 短期(1-3个月)
|
||||
1. 添加单元测试框架
|
||||
2. 添加 Windows 平台支持
|
||||
3. 改进错误处理和日志记录
|
||||
|
||||
### 中期(3-6个月)
|
||||
1. 添加配置选项(超时、重试等)
|
||||
2. 支持自定义检测策略
|
||||
3. 性能优化和资源管理改进
|
||||
|
||||
### 长期(6-12个月)
|
||||
1. 添加移动平台支持(Android/iOS)
|
||||
2. 提供插件机制,允许第三方扩展
|
||||
3. 创建独立的 SDK 包
|
||||
|
||||
## SOLID 原则遵循
|
||||
|
||||
- ✅ **单一职责原则** (SRP): 每个类只负责一个平台
|
||||
- ✅ **开闭原则** (OCP): 对扩展开放,对修改关闭
|
||||
- ✅ **里氏替换原则** (LSP): 子类可替换基类使用
|
||||
- ✅ **接口隔离原则** (ISP): 客户端只依赖需要的接口
|
||||
- ✅ **依赖倒置原则** (DIP): 依赖抽象而非具体实现
|
||||
|
||||
## 总结
|
||||
|
||||
这次重构成功地将 ScreenLockDetector 从一个包含大量条件编译的单一类,转换为一个清晰的面向对象架构。新架构不仅保持了完全的向后兼容性,还大大提高了代码的可维护性、可测试性和可扩展性。
|
||||
|
||||
### 关键成就
|
||||
- 📦 6个新文件,清晰的职责分离
|
||||
- 🔄 100% API 兼容,现有代码无需修改
|
||||
- 📚 完善的文档,包含迁移指南和设计说明
|
||||
- 🎯 符合 SOLID 原则和设计模式最佳实践
|
||||
- 🚀 为未来扩展奠定良好基础
|
||||
|
||||
### 团队收益
|
||||
- 开发人员:更容易理解和修改代码
|
||||
- 测试人员:更容易进行平台特定测试
|
||||
- 维护人员:更容易定位和修复问题
|
||||
- 用户:无感升级,无需修改现有代码
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [详细重构说明](docs/REFACTORING.md)
|
||||
- [迁移指南](docs/MIGRATION_GUIDE.md)
|
||||
- [类图和设计模式](docs/CLASS_DIAGRAM.md)
|
||||
|
||||
## 致谢
|
||||
|
||||
感谢所有参与和支持这次重构的团队成员!
|
||||
|
||||
---
|
||||
|
||||
**ScreenLockDetector 开发团队**
|
||||
**Version 2.0.0 - 面向对象架构**
|
||||
|
|
@ -1,344 +0,0 @@
|
|||
# Vulkan 设备丢失恢复机制
|
||||
|
||||
## 问题描述
|
||||
|
||||
当系统从休眠/睡眠状态唤醒时,Vulkan 渲染会失败并报错:
|
||||
|
||||
```
|
||||
Failed to submit draw command buffer! Error code = -4
|
||||
```
|
||||
|
||||
错误码 `-4` 对应 `VK_ERROR_DEVICE_LOST`,表示 Vulkan 逻辑设备已丢失。这是因为:
|
||||
|
||||
1. 系统休眠时 GPU 驱动会被挂起或重置
|
||||
2. 唤醒后 GPU 物理设备重新初始化
|
||||
3. 之前创建的 Vulkan 逻辑设备和资源变为无效状态
|
||||
4. 任何 Vulkan 命令调用都会返回 `VK_ERROR_DEVICE_LOST`
|
||||
|
||||
## 解决方案
|
||||
|
||||
实现了一个完整的设备丢失检测和恢复机制,包括以下几个关键步骤:
|
||||
|
||||
### 1. 添加设备丢失状态标志
|
||||
|
||||
在 `VulkanWidget` 类中添加了 `m_deviceLost` 布尔标志来跟踪设备状态:
|
||||
|
||||
```cpp
|
||||
bool m_deviceLost; // 标记设备是否丢失(如休眠后唤醒)
|
||||
```
|
||||
|
||||
### 2. 在关键 Vulkan 调用点检测设备丢失
|
||||
|
||||
在三个可能返回 `VK_ERROR_DEVICE_LOST` 的关键位置添加了检测:
|
||||
|
||||
#### a) `vkAcquireNextImageKHR` - 获取交换链图像
|
||||
```cpp
|
||||
VkResult result = vkAcquireNextImageKHR(...);
|
||||
if (result == VK_ERROR_DEVICE_LOST || result == -4) {
|
||||
qDebug() << "VK_ERROR_DEVICE_LOST detected in vkAcquireNextImageKHR!";
|
||||
m_deviceLost = true;
|
||||
if (handleDeviceLost()) {
|
||||
qDebug() << "Device recovery successful";
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
setError("Failed to recover from device lost error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
#### b) `vkQueueSubmit` - 提交命令缓冲
|
||||
```cpp
|
||||
result = vkQueueSubmit(m_queue, 1, &submitInfo, m_inFlightFences[m_currentFrame]);
|
||||
if (result != VK_SUCCESS) {
|
||||
if (result == VK_ERROR_DEVICE_LOST || result == -4) {
|
||||
qDebug() << "VK_ERROR_DEVICE_LOST detected! Attempting to recover device...";
|
||||
m_deviceLost = true;
|
||||
if (handleDeviceLost()) {
|
||||
qDebug() << "Device recovery successful";
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
setError("Failed to recover from device lost error");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
#### c) `vkQueuePresentKHR` - 呈现图像
|
||||
```cpp
|
||||
result = vkQueuePresentKHR(m_queue, &presentInfo);
|
||||
if (result == VK_ERROR_DEVICE_LOST || result == -4) {
|
||||
qDebug() << "VK_ERROR_DEVICE_LOST detected in vkQueuePresentKHR!";
|
||||
m_deviceLost = true;
|
||||
if (handleDeviceLost()) {
|
||||
qDebug() << "Device recovery successful";
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
setError("Failed to recover from device lost error");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 实现设备恢复函数
|
||||
|
||||
#### `handleDeviceLost()` - 设备丢失处理函数
|
||||
|
||||
```cpp
|
||||
bool VulkanWidget::handleDeviceLost()
|
||||
{
|
||||
qDebug() << "=== Handling device lost error ===";
|
||||
|
||||
// 1. 停止渲染定时器,防止在恢复期间继续渲染
|
||||
if (m_renderTimer && m_renderTimer->isActive()) {
|
||||
m_renderTimer->stop();
|
||||
}
|
||||
|
||||
// 2. 等待设备空闲(可能会失败,但仍然尝试)
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(m_device);
|
||||
}
|
||||
|
||||
// 3. 重新创建设备和所有资源
|
||||
bool success = recreateDevice();
|
||||
|
||||
if (success) {
|
||||
m_deviceLost = false;
|
||||
|
||||
// 4. 如果渲染已启用,重新启动定时器
|
||||
if (m_renderingEnabled && m_renderTimer && !m_renderTimer->isActive()) {
|
||||
m_renderTimer->start(16); // ~60 FPS
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
```
|
||||
|
||||
#### `recreateDevice()` - 设备重建函数
|
||||
|
||||
完整的资源重建流程:
|
||||
|
||||
```cpp
|
||||
bool VulkanWidget::recreateDevice()
|
||||
{
|
||||
// 1. 清理 VulkanRenderer
|
||||
if (m_renderer) {
|
||||
delete m_renderer;
|
||||
m_renderer = nullptr;
|
||||
}
|
||||
|
||||
// 2. 清理同步对象(Semaphores、Fences)
|
||||
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||
vkDestroySemaphore(m_device, m_renderFinishedSemaphores[i], nullptr);
|
||||
vkDestroySemaphore(m_device, m_imageAvailableSemaphores[i], nullptr);
|
||||
vkDestroyFence(m_device, m_inFlightFences[i], nullptr);
|
||||
}
|
||||
|
||||
// 3. 清理命令对象(Command Pool 和 Command Buffers)
|
||||
vkFreeCommandBuffers(m_device, m_commandPool, ...);
|
||||
vkDestroyCommandPool(m_device, m_commandPool, nullptr);
|
||||
|
||||
// 4. 清理交换链(Swapchain 和相关资源)
|
||||
cleanupSwapchain();
|
||||
|
||||
// 5. 销毁逻辑设备
|
||||
vkDestroyDevice(m_device, nullptr);
|
||||
|
||||
// 6. 销毁 Surface
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
|
||||
// === 重建阶段 ===
|
||||
|
||||
// 7. 重新创建 Surface
|
||||
if (!createSurface()) return false;
|
||||
|
||||
// 8. 重新创建逻辑设备
|
||||
if (!createDevice()) return false;
|
||||
|
||||
// 9. 重新创建交换链
|
||||
if (!createSwapchain()) return false;
|
||||
|
||||
// 10. 重新创建命令对象
|
||||
if (!createCommandObjects()) return false;
|
||||
|
||||
// 11. 重新创建同步对象
|
||||
if (!createSyncObjects()) return false;
|
||||
|
||||
// 12. 重新创建 VulkanRenderer
|
||||
m_renderer = new VulkanRenderer();
|
||||
if (!m_renderer->initialize(...)) {
|
||||
delete m_renderer;
|
||||
m_renderer = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 13. 重置帧计数器
|
||||
m_currentFrame = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 在渲染启用时主动检查
|
||||
|
||||
在 `setRenderingEnabled(true)` 时主动检查设备是否丢失:
|
||||
|
||||
```cpp
|
||||
void VulkanWidget::setRenderingEnabled(bool enabled)
|
||||
{
|
||||
if (m_renderingEnabled) {
|
||||
// 检查设备是否丢失(例如从睡眠中唤醒后)
|
||||
if (m_deviceLost) {
|
||||
qDebug() << "Device lost detected on resume, attempting recovery...";
|
||||
if (!handleDeviceLost()) {
|
||||
qDebug() << "Failed to recover device, rendering cannot resume";
|
||||
m_renderingEnabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 启动渲染定时器
|
||||
if (!m_renderTimer->isActive()) {
|
||||
m_renderTimer->start(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 渲染前防护检查
|
||||
|
||||
在 `renderFrame()` 开始时添加保护检查:
|
||||
|
||||
```cpp
|
||||
void VulkanWidget::renderFrame()
|
||||
{
|
||||
// 如果设备丢失,不进行渲染
|
||||
if (m_deviceLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常渲染流程...
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 正常流程
|
||||
1. 用户锁定屏幕 → 停止动画,渲染一帧锁屏画面
|
||||
2. 系统进入休眠/睡眠
|
||||
3. 系统唤醒 → 触发 `aboutToWakeUp` 信号
|
||||
4. 调用 `setRenderingEnabled(true)`
|
||||
5. 检测到 `m_deviceLost == false`,正常恢复渲染
|
||||
|
||||
### 设备丢失恢复流程
|
||||
1. 用户锁定屏幕 → 停止动画
|
||||
2. 系统进入休眠/睡眠 → **GPU 驱动重置,设备丢失**
|
||||
3. 系统唤醒 → 触发 `aboutToWakeUp` 信号
|
||||
4. 调用 `setRenderingEnabled(true)`
|
||||
5. 检测到 `m_deviceLost == true`(或首次渲染时检测到)
|
||||
6. 调用 `handleDeviceLost()`
|
||||
7. 执行 `recreateDevice()` 重建所有 Vulkan 资源
|
||||
8. 设置 `m_deviceLost = false`
|
||||
9. 恢复正常渲染
|
||||
|
||||
### 渲染时设备丢失流程
|
||||
1. 正在渲染中
|
||||
2. 调用 `vkQueueSubmit` 返回 `VK_ERROR_DEVICE_LOST`
|
||||
3. 检测到错误,设置 `m_deviceLost = true`
|
||||
4. 调用 `handleDeviceLost()` 进行恢复
|
||||
5. 下一帧继续正常渲染
|
||||
|
||||
## 关键点说明
|
||||
|
||||
### 为什么需要销毁 Surface?
|
||||
|
||||
虽然 Vulkan 规范中 Surface 不直接依赖于逻辑设备,但在某些平台(特别是 Windows)上:
|
||||
- GPU 驱动重置可能影响 Surface 的底层窗口系统连接
|
||||
- 旧的 Surface 可能与新设备不兼容
|
||||
- 重新创建 Surface 确保与新设备的完全兼容性
|
||||
|
||||
### 为什么不保留 Instance 和 PhysicalDevice?
|
||||
|
||||
- `VkInstance`:Vulkan 加载器级别的对象,不受设备丢失影响,可以保留
|
||||
- `VkPhysicalDevice`:物理设备句柄,表示 GPU 硬件,也不受逻辑设备丢失影响,可以保留
|
||||
- `VkDevice`:逻辑设备,设备丢失后**必须**重新创建
|
||||
- `VkSurface`:虽然理论上可以保留,但为了保证跨平台兼容性,选择重新创建
|
||||
|
||||
### 同步问题
|
||||
|
||||
在清理资源前调用 `vkDeviceWaitIdle()` 确保:
|
||||
- 所有提交的命令都已完成
|
||||
- 没有资源正在被 GPU 使用
|
||||
- 避免在销毁时出现验证层错误
|
||||
|
||||
即使 `vkDeviceWaitIdle()` 因设备丢失而失败,我们仍然继续清理流程,因为:
|
||||
- 设备已丢失,所有命令都已停止
|
||||
- 资源对象仍需要正确释放以避免内存泄漏
|
||||
|
||||
## 测试场景
|
||||
|
||||
1. **正常休眠/唤醒**
|
||||
- 锁定屏幕 → 休眠 → 唤醒 → 解锁
|
||||
- 应该能够正常恢复渲染,无错误
|
||||
|
||||
2. **长时间休眠**
|
||||
- 休眠超过数小时或过夜
|
||||
- GPU 驱动更可能被完全重置
|
||||
- 应该能够检测设备丢失并成功恢复
|
||||
|
||||
3. **多次休眠/唤醒循环**
|
||||
- 连续多次休眠和唤醒
|
||||
- 每次都应该能够正确恢复
|
||||
- 无内存泄漏
|
||||
|
||||
4. **渲染中途设备丢失**
|
||||
- 在活跃渲染期间触发设备丢失
|
||||
- 应该能够捕获错误并恢复
|
||||
|
||||
## 日志输出
|
||||
|
||||
成功恢复的日志示例:
|
||||
|
||||
```
|
||||
📤 系统已从睡眠中唤醒
|
||||
MainWindow: Screen unlocked event received
|
||||
Vulkan rendering ENABLED - Resuming animations
|
||||
Device lost detected on resume, attempting recovery...
|
||||
=== Handling device lost error ===
|
||||
Render timer stopped for device recovery
|
||||
=== Recreating Vulkan device ===
|
||||
Renderer cleaned up
|
||||
Sync objects cleaned up
|
||||
Command objects cleaned up
|
||||
Logical device destroyed
|
||||
Surface destroyed
|
||||
Surface recreated
|
||||
Logical device recreated
|
||||
Swapchain recreated
|
||||
Command objects recreated
|
||||
Sync objects recreated
|
||||
VulkanRenderer recreated successfully!
|
||||
=== Device recreation complete ===
|
||||
Device recovery successful!
|
||||
Render timer restarted after recovery
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **帧计数器重置**:设备恢复后 `m_currentFrame` 重置为 0,确保同步对象索引正确
|
||||
|
||||
2. **渲染器状态**:VulkanRenderer 会被完全重建,所有内部状态都会重置
|
||||
|
||||
3. **用户体验**:恢复过程通常在 100-500ms 内完成,用户可能会注意到短暂的黑屏或暂停
|
||||
|
||||
4. **错误处理**:如果恢复失败,渲染会被禁用,防止崩溃,用户仍可使用应用的其他功能
|
||||
|
||||
5. **跨平台**:此方案在 Windows、Linux 和 macOS 上都应该工作,但具体行为可能因驱动而异
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `src/vulkanwidget.h` - 添加了 `m_deviceLost` 标志和恢复函数声明
|
||||
- `src/vulkanwidget.cpp` - 实现了完整的设备丢失检测和恢复逻辑
|
||||
- `src/powermonitor.cpp` - 发送 `aboutToWakeUp` 信号
|
||||
- `src/mainwindow.cpp` - 连接信号并调用 `setRenderingEnabled(true)`
|
||||
19
make_deb.sh
19
make_deb.sh
|
|
@ -12,24 +12,7 @@ NC='\033[0m' # No Color
|
|||
# 配置变量
|
||||
APP_NAME="ScreenLockDetector"
|
||||
PACKAGE_NAME="ScreenLockDetector"
|
||||
|
||||
# 从自动生成的 version.sh 中读取版本号
|
||||
if [ ! -f "version.sh" ]; then
|
||||
echo -e "${RED}错误: version.sh 文件不存在${NC}"
|
||||
echo -e "${YELLOW}请先运行 ./build.sh 生成版本文件${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source version.sh to get APP_VERSION
|
||||
source version.sh
|
||||
|
||||
if [ -z "$APP_VERSION" ]; then
|
||||
echo -e "${RED}错误: 无法从 version.sh 中读取版本号${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="$APP_VERSION"
|
||||
echo -e "${GREEN}✓ 从 version.sh 读取版本号: ${VERSION}${NC}"
|
||||
VERSION="1.0.0"
|
||||
ARCH="amd64"
|
||||
QT_DIR="$HOME/sdk/qt-5.15.2"
|
||||
LINUXDEPLOYQT="${LINUXDEPLOYQT:-$HOME/tools/sunvpack-py/bin/linuxdeployqt}"
|
||||
|
|
|
|||
|
|
@ -31,9 +31,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
// 初始化锁屏检测器
|
||||
m_lockDetector = new ScreenLockDetector(this);
|
||||
|
||||
// 初始化电源监视器
|
||||
m_powerMonitor = new PowerMonitor(this);
|
||||
|
||||
// 初始化UI
|
||||
setupUI();
|
||||
|
||||
|
|
@ -166,12 +163,6 @@ void MainWindow::setupConnections()
|
|||
connect(m_lockDetector, &ScreenLockDetector::lockStateChanged,
|
||||
this, &MainWindow::onLockStateChanged);
|
||||
|
||||
// 连接PowerMonitor信号
|
||||
connect(m_powerMonitor, &PowerMonitor::aboutToSleep,
|
||||
this, &MainWindow::onScreenLocked);
|
||||
connect(m_powerMonitor, &PowerMonitor::aboutToWakeUp,
|
||||
this, &MainWindow::onScreenUnlocked);
|
||||
|
||||
// 连接按钮信号
|
||||
connect(m_enableRenderBtn, &QPushButton::clicked,
|
||||
this, &MainWindow::onEnableRenderingClicked);
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@
|
|||
#include <QGroupBox>
|
||||
#include <QTimer>
|
||||
#include "screenlockdetector.h"
|
||||
#include "powermonitor.h"
|
||||
#include "renderwidgetbase.h"
|
||||
|
||||
/**
|
||||
* @brief 主窗口类
|
||||
*
|
||||
*
|
||||
* 整合锁屏检测器和渲染组件,提供用户界面
|
||||
* 使用统一的RenderWidgetBase基类指针,根据编译选项选择Vulkan或QPainter实现
|
||||
*/
|
||||
|
|
@ -82,19 +81,18 @@ private:
|
|||
private:
|
||||
// 核心组件
|
||||
ScreenLockDetector *m_lockDetector;
|
||||
PowerMonitor *m_powerMonitor;
|
||||
RenderWidgetBase *m_renderWidget; // 统一的渲染组件指针
|
||||
|
||||
|
||||
// UI组件
|
||||
QWidget *m_centralWidget;
|
||||
QVBoxLayout *m_mainLayout;
|
||||
|
||||
|
||||
// 控制面板
|
||||
QGroupBox *m_controlGroup;
|
||||
QPushButton *m_enableRenderBtn;
|
||||
QPushButton *m_disableRenderBtn;
|
||||
QPushButton *m_resetFrameBtn;
|
||||
|
||||
|
||||
// 状态显示
|
||||
QGroupBox *m_statusGroup;
|
||||
QLabel *m_rendererTypeLabel;
|
||||
|
|
@ -103,9 +101,9 @@ private:
|
|||
QLabel *m_frameCountLabel;
|
||||
QLabel *m_detectorStatusLabel;
|
||||
QLabel *m_initStatusLabel;
|
||||
|
||||
|
||||
// 更新定时器
|
||||
QTimer *m_updateTimer;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
#endif // MAINWINDOW_H
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
#include "powermonitor.h"
|
||||
|
||||
void PowerMonitor::initLogin1Dbus() {
|
||||
// 连接 org.freedesktop.login1 服务(系统总线)
|
||||
m_login1Interface = new QDBusInterface(
|
||||
"org.freedesktop.login1", // DBus 服务名
|
||||
"/org/freedesktop/login1", // 对象路径
|
||||
"org.freedesktop.login1.Manager", // 接口名
|
||||
QDBusConnection::systemBus(), // 系统总线(关键!不是会话总线)
|
||||
this
|
||||
);
|
||||
|
||||
// 检查接口是否有效
|
||||
if (m_login1Interface->isValid()) {
|
||||
// 监听 PrepareForSleep 信号(签名:b = bool)
|
||||
bool connectOk = QDBusConnection::systemBus().connect(
|
||||
"org.freedesktop.login1", // 服务名
|
||||
"/org/freedesktop/login1", // 对象路径
|
||||
"org.freedesktop.login1.Manager", // 接口名
|
||||
"PrepareForSleep", // 信号名
|
||||
this,
|
||||
SLOT(onPrepareForSleep(bool)) // 接收信号的槽函数
|
||||
);
|
||||
|
||||
if (connectOk) {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "✅ 成功连接 login1 电源管理接口";
|
||||
qDebug() << "=================================================";
|
||||
} else {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 连接 login1 信号失败:" << QDBusConnection::systemBus().lastError().message();
|
||||
qDebug() << "=================================================";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 无法创建 login1 接口:" << m_login1Interface->lastError().message();
|
||||
qDebug() << "=================================================";
|
||||
}
|
||||
}
|
||||
|
||||
// 槽函数:处理睡眠/唤醒事件
|
||||
void PowerMonitor::onPrepareForSleep(bool enteringSleep) {
|
||||
if (enteringSleep) {
|
||||
// 系统即将进入 休眠/待机 状态
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "\n📥 系统即将进入睡眠(休眠/待机)";
|
||||
emit aboutToSleep();
|
||||
} else {
|
||||
// 系统从睡眠中唤醒
|
||||
qDebug() << "📤 系统已从睡眠中唤醒\n";
|
||||
qDebug() << "=================================================";
|
||||
emit aboutToWakeUp();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusConnection>
|
||||
#include <QDebug>
|
||||
|
||||
class PowerMonitor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PowerMonitor(QObject *parent = nullptr) : QObject(parent) { initLogin1Dbus(); }
|
||||
|
||||
signals:
|
||||
/*
|
||||
* @brief 发送即将唤醒的信号
|
||||
*/
|
||||
void aboutToWakeUp();
|
||||
|
||||
/*
|
||||
* @brief 发送即将进入睡眠的信号
|
||||
*/
|
||||
void aboutToSleep();
|
||||
|
||||
private slots:
|
||||
/*
|
||||
* @brief 处理睡眠/唤醒信号的槽函数
|
||||
* @param enteringSleep 是否进入睡眠状态
|
||||
*/
|
||||
void onPrepareForSleep(bool enteringSleep);
|
||||
|
||||
private:
|
||||
/*
|
||||
* @brief 初始化Login1 DBus接口
|
||||
*/
|
||||
void initLogin1Dbus();
|
||||
|
||||
QDBusInterface *m_login1Interface = nullptr;
|
||||
};
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
// Auto-generated version information from CMakeLists.txt
|
||||
// DO NOT EDIT THIS FILE MANUALLY - Edit CMakeLists.txt instead
|
||||
|
||||
#define APP_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define APP_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define APP_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
#define APP_VERSION_STRING "@PROJECT_VERSION@"
|
||||
|
||||
// Vulkan version macros
|
||||
#define APP_VK_VERSION VK_MAKE_VERSION(APP_VERSION_MAJOR, APP_VERSION_MINOR, APP_VERSION_PATCH)
|
||||
|
||||
#endif // VERSION_H
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
#include "vulkanwidget.h"
|
||||
#include "vulkanrenderer.h"
|
||||
#include "version.h"
|
||||
#include <QDebug>
|
||||
#include <QShowEvent>
|
||||
#include <QHideEvent>
|
||||
|
|
@ -9,7 +8,6 @@
|
|||
#include <QCloseEvent>
|
||||
#include <QWindow>
|
||||
#include <QApplication>
|
||||
#include <QThread>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
|
|
@ -37,13 +35,10 @@ VulkanWidget::VulkanWidget(QWidget *parent)
|
|||
, m_surface(VK_NULL_HANDLE)
|
||||
, m_swapchain(VK_NULL_HANDLE)
|
||||
, m_commandPool(VK_NULL_HANDLE)
|
||||
, m_x11Display(nullptr)
|
||||
, m_initialized(false)
|
||||
, m_renderingEnabled(false)
|
||||
, m_needsResize(false)
|
||||
, m_needsLockedFrameUpdate(false)
|
||||
, m_isClosing(false)
|
||||
, m_deviceLost(false)
|
||||
, m_frameCount(0)
|
||||
, m_queueFamilyIndex(0)
|
||||
, m_currentFrame(0)
|
||||
|
|
@ -58,6 +53,7 @@ VulkanWidget::VulkanWidget(QWidget *parent)
|
|||
, m_lastLockFrameCount(0)
|
||||
, m_lockPaintFrameCount(0)
|
||||
, m_lockCount(0)
|
||||
, m_isClosing(false)
|
||||
, m_lastFrameTime(QDateTime::currentDateTime())
|
||||
, m_currentFps(0.0)
|
||||
{
|
||||
|
|
@ -69,7 +65,7 @@ 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);
|
||||
|
||||
|
|
@ -112,17 +108,6 @@ void VulkanWidget::setRenderingEnabled(bool enabled)
|
|||
|
||||
if (m_renderingEnabled) {
|
||||
qDebug() << "Vulkan rendering ENABLED - Resuming animations";
|
||||
|
||||
// Check if device was lost (e.g., after wake from sleep)
|
||||
if (m_deviceLost) {
|
||||
qDebug() << "Device lost detected on resume, attempting recovery...";
|
||||
if (!handleDeviceLost()) {
|
||||
qDebug() << "Failed to recover device, rendering cannot resume";
|
||||
m_renderingEnabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复渲染时,重新启动定时器(锁屏时已停止)
|
||||
if (!m_renderTimer->isActive()) {
|
||||
m_renderTimer->start(16); // ~60 FPS
|
||||
|
|
@ -292,7 +277,7 @@ bool VulkanWidget::createInstance()
|
|||
VkApplicationInfo appInfo = {};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.pApplicationName = "VulkanWidget";
|
||||
appInfo.applicationVersion = APP_VK_VERSION; // Auto-generated from CMakeLists.txt
|
||||
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.pEngineName = "No Engine";
|
||||
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.apiVersion = VK_API_VERSION_1_0;
|
||||
|
|
@ -345,22 +330,12 @@ bool VulkanWidget::createSurface()
|
|||
#elif defined(__linux__)
|
||||
VkXlibSurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
|
||||
|
||||
// Reuse existing X11 display or open a new one
|
||||
Display* x11Display = static_cast<Display*>(m_x11Display);
|
||||
if (!x11Display) {
|
||||
x11Display = XOpenDisplay(nullptr);
|
||||
if (!x11Display) {
|
||||
qDebug() << "Failed to open X11 display";
|
||||
return false;
|
||||
}
|
||||
m_x11Display = x11Display;
|
||||
qDebug() << "X11 display opened and stored";
|
||||
} else {
|
||||
qDebug() << "Reusing existing X11 display";
|
||||
// Get X11 display - use default display
|
||||
createInfo.dpy = XOpenDisplay(nullptr);
|
||||
if (!createInfo.dpy) {
|
||||
qDebug() << "Failed to open X11 display";
|
||||
return false;
|
||||
}
|
||||
|
||||
createInfo.dpy = x11Display;
|
||||
createInfo.window = static_cast<Window>(window->winId());
|
||||
|
||||
VkResult result = vkCreateXlibSurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||
|
|
@ -774,11 +749,6 @@ void VulkanWidget::renderFrame()
|
|||
return;
|
||||
}
|
||||
|
||||
// Don't render if device is lost (recovery will be attempted on next enable)
|
||||
if (m_deviceLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 关键修复:即使 renderingEnabled=false 也继续渲染,以显示锁屏状态
|
||||
// 只是传递不同的 paintingEnabled 参数给 renderer
|
||||
|
||||
|
|
@ -786,14 +756,14 @@ void VulkanWidget::renderFrame()
|
|||
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;
|
||||
|
|
@ -817,18 +787,8 @@ void VulkanWidget::renderFrame()
|
|||
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||
recreateSwapchain();
|
||||
return;
|
||||
} else if (result == VK_ERROR_DEVICE_LOST || result == -4) {
|
||||
qDebug() << "VK_ERROR_DEVICE_LOST detected in vkAcquireNextImageKHR! Attempting to recover device...";
|
||||
m_deviceLost = true;
|
||||
if (handleDeviceLost()) {
|
||||
qDebug() << "Device recovery successful, will retry rendering on next frame";
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
setError("Failed to recover from device lost error");
|
||||
}
|
||||
return;
|
||||
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
||||
//qDebug() << "Failed to acquire swapchain image";
|
||||
qDebug() << "Failed to acquire swapchain image";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -857,19 +817,7 @@ void VulkanWidget::renderFrame()
|
|||
|
||||
result = vkQueueSubmit(m_queue, 1, &submitInfo, m_inFlightFences[m_currentFrame]);
|
||||
if (result != VK_SUCCESS) {
|
||||
qDebug() << "Failed to submit draw command buffer! Error code = " << result;
|
||||
|
||||
// Handle device lost error (e.g., after wake from sleep/hibernation)
|
||||
if (result == VK_ERROR_DEVICE_LOST || result == -4) {
|
||||
qDebug() << "VK_ERROR_DEVICE_LOST detected! Attempting to recover device...";
|
||||
m_deviceLost = true;
|
||||
if (handleDeviceLost()) {
|
||||
qDebug() << "Device recovery successful, will retry rendering on next frame";
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
setError("Failed to recover from device lost error");
|
||||
}
|
||||
}
|
||||
qDebug() << "Failed to submit draw command buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -889,15 +837,6 @@ void VulkanWidget::renderFrame()
|
|||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || m_needsResize) {
|
||||
m_needsResize = false;
|
||||
recreateSwapchain();
|
||||
} else if (result == VK_ERROR_DEVICE_LOST || result == -4) {
|
||||
qDebug() << "VK_ERROR_DEVICE_LOST detected in vkQueuePresentKHR! Attempting to recover device...";
|
||||
m_deviceLost = true;
|
||||
if (handleDeviceLost()) {
|
||||
qDebug() << "Device recovery successful, will retry rendering on next frame";
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
setError("Failed to recover from device lost error");
|
||||
}
|
||||
} else if (result != VK_SUCCESS) {
|
||||
qDebug() << "Failed to present swapchain image";
|
||||
}
|
||||
|
|
@ -958,7 +897,7 @@ void VulkanWidget::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t i
|
|||
// Calculate elapsed time in seconds
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
qint64 elapsedTime = m_startTime.secsTo(now);
|
||||
|
||||
|
||||
m_renderer->recordCommandBuffer(commandBuffer, imageIndex, imageView,
|
||||
m_frameCount, static_cast<double>(elapsedTime),
|
||||
m_currentFps,
|
||||
|
|
@ -1085,15 +1024,6 @@ void VulkanWidget::cleanupVulkan()
|
|||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
}
|
||||
|
||||
// Close X11 display if it was opened (Linux only)
|
||||
#ifdef __linux__
|
||||
if (m_x11Display) {
|
||||
XCloseDisplay(static_cast<Display*>(m_x11Display));
|
||||
m_x11Display = nullptr;
|
||||
qDebug() << "X11 display closed";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Cleanup instance
|
||||
if (m_instance != VK_NULL_HANDLE) {
|
||||
vkDestroyInstance(m_instance, nullptr);
|
||||
|
|
@ -1225,208 +1155,3 @@ std::vector<const char*> VulkanWidget::getRequiredDeviceExtensions()
|
|||
extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool VulkanWidget::handleDeviceLost()
|
||||
{
|
||||
qDebug() << "=== Handling device lost error ===";
|
||||
|
||||
// Stop rendering timer to prevent more render attempts during recovery
|
||||
if (m_renderTimer && m_renderTimer->isActive()) {
|
||||
m_renderTimer->stop();
|
||||
qDebug() << "Render timer stopped for device recovery";
|
||||
}
|
||||
|
||||
// Wait for any pending operations to complete (may fail, but try anyway)
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
VkResult result = vkDeviceWaitIdle(m_device);
|
||||
if (result != VK_SUCCESS) {
|
||||
qDebug() << "vkDeviceWaitIdle failed (expected with device lost), continuing recovery...";
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to recreate the device and all resources
|
||||
bool success = recreateDevice();
|
||||
|
||||
if (success) {
|
||||
m_deviceLost = false;
|
||||
qDebug() << "Device recovery successful!";
|
||||
|
||||
// Restart rendering timer if rendering was enabled
|
||||
if (m_renderingEnabled && m_renderTimer && !m_renderTimer->isActive()) {
|
||||
m_renderTimer->start(16); // ~60 FPS
|
||||
qDebug() << "Render timer restarted after recovery";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Device recovery failed!";
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VulkanWidget::recreateDevice()
|
||||
{
|
||||
qDebug() << "=== Recreating Vulkan device ===";
|
||||
|
||||
// Clean up renderer first
|
||||
if (m_renderer) {
|
||||
delete m_renderer;
|
||||
m_renderer = nullptr;
|
||||
qDebug() << "Renderer cleaned up";
|
||||
}
|
||||
|
||||
// Clean up sync objects
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||
if (m_renderFinishedSemaphores[i] != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(m_device, m_renderFinishedSemaphores[i], nullptr);
|
||||
}
|
||||
if (m_imageAvailableSemaphores[i] != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(m_device, m_imageAvailableSemaphores[i], nullptr);
|
||||
}
|
||||
if (m_inFlightFences[i] != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(m_device, m_inFlightFences[i], nullptr);
|
||||
}
|
||||
}
|
||||
qDebug() << "Sync objects cleaned up";
|
||||
}
|
||||
|
||||
m_renderFinishedSemaphores.clear();
|
||||
m_imageAvailableSemaphores.clear();
|
||||
m_inFlightFences.clear();
|
||||
|
||||
// Clean up command objects
|
||||
if (m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE) {
|
||||
if (!m_commandBuffers.empty()) {
|
||||
vkFreeCommandBuffers(m_device, m_commandPool,
|
||||
static_cast<uint32_t>(m_commandBuffers.size()),
|
||||
m_commandBuffers.data());
|
||||
m_commandBuffers.clear();
|
||||
}
|
||||
vkDestroyCommandPool(m_device, m_commandPool, nullptr);
|
||||
m_commandPool = VK_NULL_HANDLE;
|
||||
qDebug() << "Command objects cleaned up";
|
||||
}
|
||||
|
||||
// Clean up swapchain
|
||||
cleanupSwapchain();
|
||||
|
||||
// Destroy logical device
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
vkDestroyDevice(m_device, nullptr);
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_queue = VK_NULL_HANDLE;
|
||||
qDebug() << "Logical device destroyed";
|
||||
}
|
||||
|
||||
// Destroy surface
|
||||
if (m_surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
qDebug() << "Surface destroyed";
|
||||
}
|
||||
|
||||
// Close and reset X11 display (Linux only) - will be reopened when creating new surface
|
||||
#ifdef __linux__
|
||||
if (m_x11Display) {
|
||||
XCloseDisplay(static_cast<Display*>(m_x11Display));
|
||||
m_x11Display = nullptr;
|
||||
qDebug() << "X11 display closed for recreation";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Now recreate everything from surface onwards
|
||||
|
||||
// Important: Add a small delay to let the GPU driver fully reset
|
||||
// This prevents VK_ERROR_SURFACE_LOST_KHR on some systems
|
||||
qDebug() << "Waiting for GPU driver to stabilize...";
|
||||
QThread::msleep(200); // 200ms delay
|
||||
|
||||
// Step 1: Recreate surface
|
||||
if (!createSurface()) {
|
||||
setError("Failed to recreate surface after device lost");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Surface recreated";
|
||||
|
||||
// Verify the physical device still supports this surface
|
||||
VkBool32 surfaceSupported = VK_FALSE;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(m_physicalDevice, m_queueFamilyIndex, m_surface, &surfaceSupported);
|
||||
if (!surfaceSupported) {
|
||||
qDebug() << "Physical device no longer supports the surface after recovery";
|
||||
setError("Surface not supported by physical device after recovery");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Surface support verified";
|
||||
|
||||
// Step 2: Recreate logical device
|
||||
if (!createDevice()) {
|
||||
setError("Failed to recreate device after device lost");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Logical device recreated";
|
||||
|
||||
// Step 3: Recreate swapchain
|
||||
// Query surface capabilities again to ensure they're current
|
||||
VkSurfaceCapabilitiesKHR surfaceCaps;
|
||||
VkResult capsResult = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice, m_surface, &surfaceCaps);
|
||||
if (capsResult != VK_SUCCESS) {
|
||||
qDebug() << "Failed to query surface capabilities, error:" << capsResult;
|
||||
setError("Failed to query surface capabilities after device lost");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Surface capabilities validated";
|
||||
|
||||
if (!createSwapchain()) {
|
||||
setError("Failed to recreate swapchain after device lost");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Swapchain recreated";
|
||||
|
||||
// Step 4: Recreate command objects
|
||||
if (!createCommandObjects()) {
|
||||
setError("Failed to recreate command objects after device lost");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Command objects recreated";
|
||||
|
||||
// Step 5: Recreate synchronization objects
|
||||
if (!createSyncObjects()) {
|
||||
setError("Failed to recreate sync objects after device lost");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Sync objects recreated";
|
||||
|
||||
// Step 6: Recreate VulkanRenderer
|
||||
if (m_surfaceWidth >= 100 && m_surfaceHeight >= 100) {
|
||||
m_renderer = new VulkanRenderer();
|
||||
|
||||
// Get swapchain format
|
||||
VkSurfaceFormatKHR surfaceFormat;
|
||||
uint32_t formatCount = 0;
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physicalDevice, m_surface, &formatCount, nullptr);
|
||||
if (formatCount > 0) {
|
||||
std::vector<VkSurfaceFormatKHR> formats(formatCount);
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physicalDevice, m_surface, &formatCount, formats.data());
|
||||
surfaceFormat = formats[0];
|
||||
}
|
||||
|
||||
// Initialize renderer
|
||||
if (!m_renderer->initialize(m_device, m_physicalDevice,
|
||||
m_queue, m_queueFamilyIndex,
|
||||
static_cast<uint32_t>(surfaceFormat.format),
|
||||
m_surfaceWidth, m_surfaceHeight)) {
|
||||
setError("Failed to reinitialize VulkanRenderer after device lost");
|
||||
delete m_renderer;
|
||||
m_renderer = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "VulkanRenderer recreated successfully!";
|
||||
}
|
||||
|
||||
// Reset frame counter
|
||||
m_currentFrame = 0;
|
||||
|
||||
qDebug() << "=== Device recreation complete ===";
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,18 +193,6 @@ private:
|
|||
*/
|
||||
std::vector<const char*> getRequiredDeviceExtensions();
|
||||
|
||||
/**
|
||||
* @brief 检测并处理设备丢失情况(如从休眠唤醒后)
|
||||
* @return true表示成功恢复,false表示失败
|
||||
*/
|
||||
bool handleDeviceLost();
|
||||
|
||||
/**
|
||||
* @brief 重新创建设备和所有Vulkan资源
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool recreateDevice();
|
||||
|
||||
private:
|
||||
// Vulkan对象
|
||||
VkInstance m_instance;
|
||||
|
|
@ -214,7 +202,6 @@ private:
|
|||
VkSurfaceKHR m_surface;
|
||||
VkSwapchainKHR m_swapchain;
|
||||
VkCommandPool m_commandPool;
|
||||
void* m_x11Display; // X11 Display pointer (Linux only), stored as void* to avoid X11 header dependency
|
||||
std::vector<VkCommandBuffer> m_commandBuffers;
|
||||
std::vector<VkSemaphore> m_imageAvailableSemaphores;
|
||||
std::vector<VkSemaphore> m_renderFinishedSemaphores;
|
||||
|
|
@ -230,7 +217,6 @@ private:
|
|||
bool m_needsResize;
|
||||
bool m_needsLockedFrameUpdate; // 是否需要渲染锁屏帧(锁屏时只渲染一帧)
|
||||
bool m_isClosing; // 标记窗口正在关闭,防止在销毁过程中继续渲染
|
||||
bool m_deviceLost; // 标记设备是否丢失(如休眠后唤醒)
|
||||
int m_frameCount;
|
||||
uint32_t m_queueFamilyIndex;
|
||||
uint32_t m_currentFrame;
|
||||
|
|
|
|||
16
version.sh
16
version.sh
|
|
@ -1,16 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Auto-generated version information from CMakeLists.txt
|
||||
# DO NOT EDIT THIS FILE MANUALLY - Edit CMakeLists.txt instead
|
||||
# This file is sourced by shell scripts to get version information
|
||||
|
||||
APP_VERSION_MAJOR=1
|
||||
APP_VERSION_MINOR=3
|
||||
APP_VERSION_PATCH=0
|
||||
APP_VERSION="1.3.0"
|
||||
|
||||
# Export for use in other scripts
|
||||
export APP_VERSION_MAJOR
|
||||
export APP_VERSION_MINOR
|
||||
export APP_VERSION_PATCH
|
||||
export APP_VERSION
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Auto-generated version information from CMakeLists.txt
|
||||
# DO NOT EDIT THIS FILE MANUALLY - Edit CMakeLists.txt instead
|
||||
# This file is sourced by shell scripts to get version information
|
||||
|
||||
APP_VERSION_MAJOR=@PROJECT_VERSION_MAJOR@
|
||||
APP_VERSION_MINOR=@PROJECT_VERSION_MINOR@
|
||||
APP_VERSION_PATCH=@PROJECT_VERSION_PATCH@
|
||||
APP_VERSION="@PROJECT_VERSION@"
|
||||
|
||||
# Export for use in other scripts
|
||||
export APP_VERSION_MAJOR
|
||||
export APP_VERSION_MINOR
|
||||
export APP_VERSION_PATCH
|
||||
export APP_VERSION
|
||||
Loading…
Reference in New Issue