379 lines
9.8 KiB
Markdown
379 lines
9.8 KiB
Markdown
# 统一渲染架构设计文档
|
||
|
||
## 概述
|
||
|
||
本文档描述了 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()` 始终返回 true(QPainter 不需要初始化)
|
||
- `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. ✅ 用户体验:提供了更简洁的界面
|
||
|
||
这个设计为项目的后续发展提供了坚实的基础,使得添加新功能和维护现有代码变得更加容易。 |