ScreenLockDetector/docs/unified_rendering_design.md

379 lines
9.8 KiB
Markdown
Raw Normal View History

# 统一渲染架构设计文档
## 概述
本文档描述了 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. ✅ 用户体验:提供了更简洁的界面
这个设计为项目的后续发展提供了坚实的基础,使得添加新功能和维护现有代码变得更加容易。