ScreenLockDetector/docs/unified_rendering_design.md

379 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 统一渲染架构设计文档
## 概述
本文档描述了 ScreenLockDetector 项目中统一渲染架构的设计和实现。通过引入基类 `RenderWidgetBase`,我们实现了对 Vulkan 和 QPainter 两种渲染方式的统一抽象,使得 MainWindow 可以通过统一的接口操作不同的渲染实现。
## 设计目标
1. **统一接口**:为不同的渲染实现提供统一的操作接口
2. **编译时选择**:通过 CMake 编译选项自动选择 Vulkan 或 QPainter 实现
3. **简化 MainWindow**MainWindow 无需关心具体的渲染实现细节
4. **易于扩展**:未来可以方便地添加其他渲染后端(如 OpenGL、DirectX 等)
## 架构设计
### 类层次结构
```
QWidget
|
RenderWidgetBase (抽象基类)
|
+--- VulkanWidget (Vulkan 实现)
|
+--- CustomWidget (QPainter 实现)
```
### 核心组件
#### 1. RenderWidgetBase (基类)
**文件位置**: `src/renderwidgetbase.h`
**职责**:
- 定义统一的渲染组件接口
- 继承自 QWidget
- 所有方法都是纯虚函数,由子类实现
**核心接口**:
```cpp
// 初始化渲染器
virtual bool initializeRenderer() = 0;
// 启用/禁用渲染
virtual void setRenderingEnabled(bool enabled) = 0;
virtual bool isRenderingEnabled() const = 0;
// 帧计数管理
virtual int getRenderFrameCount() const = 0;
virtual void resetFrameCount() = 0;
// 状态查询
virtual bool isInitialized() const = 0;
virtual QString getLastError() const = 0;
virtual QString getRendererType() const = 0;
```
#### 2. VulkanWidget (Vulkan 实现)
**文件位置**: `src/vulkanwidget.h`, `src/vulkanwidget.cpp`
**特点**:
- 使用 Vulkan API 进行硬件加速渲染
- 通过 volk 动态加载 Vulkan 函数
- 支持跨平台Windows, Linux, macOS
- 实现了 RenderWidgetBase 的所有接口
- 保留了原有的 `initializeVulkan()` 等方法以保持向后兼容
**实现要点**:
- `initializeRenderer()` 内部调用 `initializeVulkan()`
- `getRendererType()` 返回 "Vulkan"
- 提供详细的错误信息通过 `getLastError()`
#### 3. CustomWidget (QPainter 实现)
**文件位置**: `src/customwidget.h`, `src/customwidget.cpp`
**特点**:
- 使用 Qt 的 QPainter 进行 2D 绘制
- 不需要额外的依赖库
- 跨平台兼容性最佳
- 实现了 RenderWidgetBase 的所有接口
- 保留了原有的 `setPaintingEnabled()` 等方法以保持向后兼容
**实现要点**:
- `initializeRenderer()` 始终返回 trueQPainter 不需要初始化)
- `getRendererType()` 返回 "QPainter"
- `getLastError()` 返回空字符串QPainter 通常不会出错)
#### 4. MainWindow (主窗口)
**文件位置**: `src/mainwindow.h`, `src/mainwindow.cpp`
**设计改进**:
**之前的设计**:
- 使用 QTabWidget 同时显示 VulkanWidget 和 CustomWidget
- 维护两个独立的组件指针
- 需要重复的控制逻辑和状态管理代码
**新的设计**:
- 只显示一个渲染 Widget
- 使用 `RenderWidgetBase*` 统一指针
- 通过编译选项决定使用哪个实现
- 统一的控制按钮和状态显示
**核心代码**:
```cpp
class MainWindow : public QMainWindow
{
private:
RenderWidgetBase *m_renderWidget; // 统一的渲染组件指针
// 统一的控制按钮
QPushButton *m_enableRenderBtn;
QPushButton *m_disableRenderBtn;
QPushButton *m_resetFrameBtn;
// 统一的状态标签
QLabel *m_rendererTypeLabel;
QLabel *m_initStatusLabel;
QLabel *m_renderStatusLabel;
QLabel *m_frameCountLabel;
};
```
**组件创建**:
```cpp
// 根据编译选项创建相应的渲染组件
#ifdef ENABLE_VULKAN_WIDGET
m_renderWidget = new VulkanWidget(this);
#else
m_renderWidget = new CustomWidget(this);
#endif
// 后续代码通过基类指针操作,无需关心具体实现
m_renderWidget->initializeRenderer();
m_renderWidget->setRenderingEnabled(true);
```
## 编译配置
### CMake 选项
通过 `ENABLE_VULKAN` 选项控制是否启用 Vulkan 支持:
```cmake
option(ENABLE_VULKAN "Enable Vulkan support" ON)
```
### 编译时行为
**启用 Vulkan (`ENABLE_VULKAN=ON`)**:
- 检查 Vulkan 头文件是否存在
- 编译 volk 动态加载库
- 添加 `ENABLE_VULKAN_WIDGET` 宏定义
- 编译 VulkanWidget 相关文件
- MainWindow 创建 VulkanWidget 实例
**禁用 Vulkan (`ENABLE_VULKAN=OFF` 或 Vulkan 不可用)**:
- 不编译 VulkanWidget 相关文件
- MainWindow 创建 CustomWidget 实例
- 使用 QPainter 作为后备渲染方案
## 使用示例
### 基本使用
```cpp
// MainWindow 中的使用示例
void MainWindow::onEnableRenderingClicked()
{
if (m_renderWidget) {
// 确保渲染器已初始化
if (!m_renderWidget->isInitialized()) {
bool success = m_renderWidget->initializeRenderer();
if (!success) {
QMessageBox::warning(this, "Initialization Failed",
m_renderWidget->getLastError());
return;
}
}
// 启用渲染
m_renderWidget->setRenderingEnabled(true);
}
}
// 查询状态
void MainWindow::updateStatusDisplay()
{
QString type = m_renderWidget->getRendererType();
bool isRendering = m_renderWidget->isRenderingEnabled();
int frameCount = m_renderWidget->getRenderFrameCount();
// 更新 UI 显示
m_rendererTypeLabel->setText("Renderer: " + type);
m_frameCountLabel->setText(QString("Frames: %1").arg(frameCount));
}
```
### 锁屏检测集成
```cpp
void MainWindow::onLockStateChanged(bool locked)
{
// 统一的锁屏处理逻辑
if (m_renderWidget) {
m_renderWidget->setRenderingEnabled(!locked);
}
}
```
## 优势分析
### 1. 代码简化
**之前**:
- MainWindow 中有大量 `#ifdef ENABLE_VULKAN_WIDGET` 条件编译代码
- 需要维护两套独立的控制逻辑
- UI 代码重复(两套按钮、两套状态标签)
**现在**:
- MainWindow 中几乎没有条件编译代码
- 统一的控制逻辑
- 统一的 UI 组件
### 2. 易于维护
- 添加新功能时,只需在基类中添加接口,各子类实现即可
- MainWindow 代码量减少约 40%
- 更容易理解和维护
### 3. 灵活性
- 可以轻松添加新的渲染后端OpenGL、DirectX、Metal 等)
- 可以在运行时动态切换渲染器(未来扩展)
- 可以通过配置文件选择渲染器(未来扩展)
### 4. 用户体验
- 界面更加简洁,不再有多个 Tab
- 自动选择最佳的渲染方式
- 统一的操作体验
## 扩展性设计
### 添加新的渲染后端
假设要添加 OpenGL 渲染支持:
1. 创建 `OpenGLWidget` 类继承 `RenderWidgetBase`
2. 实现所有虚函数
3. 在 CMakeLists.txt 中添加编译选项
4. 在 MainWindow 构造函数中添加选择逻辑
```cpp
#ifdef ENABLE_VULKAN_WIDGET
m_renderWidget = new VulkanWidget(this);
#elif defined(ENABLE_OPENGL_WIDGET)
m_renderWidget = new OpenGLWidget(this);
#else
m_renderWidget = new CustomWidget(this);
#endif
```
### 运行时切换(未来功能)
可以扩展设计以支持运行时切换渲染器:
```cpp
void MainWindow::switchRenderer(RendererType type)
{
if (m_renderWidget) {
delete m_renderWidget;
}
switch (type) {
case RendererType::Vulkan:
m_renderWidget = new VulkanWidget(this);
break;
case RendererType::OpenGL:
m_renderWidget = new OpenGLWidget(this);
break;
case RendererType::QPainter:
default:
m_renderWidget = new CustomWidget(this);
break;
}
// 重新初始化和布局
setupRenderer();
}
```
## 性能考虑
### 虚函数开销
- 基类使用虚函数会有轻微的性能开销
- 但相比渲染本身的开销,这个开销可以忽略不计
- 现代编译器对虚函数调用有很好的优化
### 内存占用
- 每个对象增加一个虚函数表指针(通常 8 字节)
- 由于只创建一个渲染 Widget 实例,内存影响微乎其微
## 测试建议
### 单元测试
```cpp
// 测试 Vulkan 初始化
void testVulkanInitialization() {
VulkanWidget widget;
bool success = widget.initializeRenderer();
QVERIFY(success || !widget.getLastError().isEmpty());
QCOMPARE(widget.getRendererType(), QString("Vulkan"));
}
// 测试 QPainter 初始化
void testQPainterInitialization() {
CustomWidget widget;
bool success = widget.initializeRenderer();
QVERIFY(success);
QCOMPARE(widget.getRendererType(), QString("QPainter"));
QVERIFY(widget.isInitialized());
}
// 测试渲染启用/禁用
void testRenderingToggle() {
RenderWidgetBase* widget = createWidget();
widget->initializeRenderer();
widget->setRenderingEnabled(true);
QVERIFY(widget->isRenderingEnabled());
widget->setRenderingEnabled(false);
QVERIFY(!widget->isRenderingEnabled());
}
```
### 集成测试
- 测试 MainWindow 与不同渲染器的集成
- 测试锁屏检测触发渲染器状态变化
- 测试 UI 按钮与渲染器的交互
## 文件清单
### 新增文件
- `src/renderwidgetbase.h` - 渲染组件基类
### 修改文件
- `src/mainwindow.h` - 简化为使用单一渲染组件
- `src/mainwindow.cpp` - 移除重复代码,使用统一接口
- `src/customwidget.h` - 继承 RenderWidgetBase
- `src/customwidget.cpp` - 实现基类接口
- `src/vulkanwidget.h` - 继承 RenderWidgetBase
- `src/vulkanwidget.cpp` - 实现基类接口
- `CMakeLists.txt` - 添加 renderwidgetbase.h 到头文件列表
## 总结
统一渲染架构通过引入抽象基类,成功地实现了:
1. ✅ 代码简化:减少了重复代码和条件编译
2. ✅ 接口统一MainWindow 通过统一接口操作渲染器
3. ✅ 易于扩展:可以方便地添加新的渲染后端
4. ✅ 向后兼容:保留了原有的特定方法
5. ✅ 用户体验:提供了更简洁的界面
这个设计为项目的后续发展提供了坚实的基础,使得添加新功能和维护现有代码变得更加容易。