diff --git a/REFACTORING_SUMMARY.md b/REFACTORING_SUMMARY.md deleted file mode 100644 index 075daa5..0000000 --- a/REFACTORING_SUMMARY.md +++ /dev/null @@ -1,242 +0,0 @@ -# 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 - 面向对象架构** \ No newline at end of file diff --git a/docs/DEVICE_LOST_RECOVERY.md b/docs/DEVICE_LOST_RECOVERY.md new file mode 100644 index 0000000..3a48db2 --- /dev/null +++ b/docs/DEVICE_LOST_RECOVERY.md @@ -0,0 +1,344 @@ +# 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)` diff --git a/src/vulkanwidget.cpp b/src/vulkanwidget.cpp index 91b5de7..6ca02c1 100644 --- a/src/vulkanwidget.cpp +++ b/src/vulkanwidget.cpp @@ -39,6 +39,8 @@ VulkanWidget::VulkanWidget(QWidget *parent) , m_renderingEnabled(false) , m_needsResize(false) , m_needsLockedFrameUpdate(false) + , m_isClosing(false) + , m_deviceLost(false) , m_frameCount(0) , m_queueFamilyIndex(0) , m_currentFrame(0) @@ -53,7 +55,6 @@ VulkanWidget::VulkanWidget(QWidget *parent) , m_lastLockFrameCount(0) , m_lockPaintFrameCount(0) , m_lockCount(0) - , m_isClosing(false) , m_lastFrameTime(QDateTime::currentDateTime()) , m_currentFps(0.0) { @@ -108,6 +109,17 @@ 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 @@ -748,6 +760,11 @@ void VulkanWidget::renderFrame() if (!m_initialized || m_isClosing) { return; } + + // Don't render if device is lost (recovery will be attempted on next enable) + if (m_deviceLost) { + return; + } // 关键修复:即使 renderingEnabled=false 也继续渲染,以显示锁屏状态 // 只是传递不同的 paintingEnabled 参数给 renderer @@ -787,6 +804,16 @@ 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"; return; @@ -817,7 +844,19 @@ 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; + 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"); + } + } return; } @@ -837,6 +876,15 @@ 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"; } @@ -1155,3 +1203,173 @@ std::vector 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(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"; + } + + // Now recreate everything from surface onwards + // Step 1: Recreate surface + if (!createSurface()) { + setError("Failed to recreate surface after device lost"); + return false; + } + qDebug() << "Surface recreated"; + + // 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 + 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 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(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; +} diff --git a/src/vulkanwidget.h b/src/vulkanwidget.h index 37794a1..8e925b6 100644 --- a/src/vulkanwidget.h +++ b/src/vulkanwidget.h @@ -193,6 +193,18 @@ private: */ std::vector getRequiredDeviceExtensions(); + /** + * @brief 检测并处理设备丢失情况(如从休眠唤醒后) + * @return true表示成功恢复,false表示失败 + */ + bool handleDeviceLost(); + + /** + * @brief 重新创建设备和所有Vulkan资源 + * @return true表示成功,false表示失败 + */ + bool recreateDevice(); + private: // Vulkan对象 VkInstance m_instance; @@ -217,6 +229,7 @@ private: bool m_needsResize; bool m_needsLockedFrameUpdate; // 是否需要渲染锁屏帧(锁屏时只渲染一帧) bool m_isClosing; // 标记窗口正在关闭,防止在销毁过程中继续渲染 + bool m_deviceLost; // 标记设备是否丢失(如休眠后唤醒) int m_frameCount; uint32_t m_queueFamilyIndex; uint32_t m_currentFrame;