ScreenLockDetector/docs/unified_rendering_design.md

9.8 KiB
Raw Permalink Blame History

统一渲染架构设计文档

概述

本文档描述了 ScreenLockDetector 项目中统一渲染架构的设计和实现。通过引入基类 RenderWidgetBase,我们实现了对 Vulkan 和 QPainter 两种渲染方式的统一抽象,使得 MainWindow 可以通过统一的接口操作不同的渲染实现。

设计目标

  1. 统一接口:为不同的渲染实现提供统一的操作接口
  2. 编译时选择:通过 CMake 编译选项自动选择 Vulkan 或 QPainter 实现
  3. 简化 MainWindowMainWindow 无需关心具体的渲染实现细节
  4. 易于扩展:未来可以方便地添加其他渲染后端(如 OpenGL、DirectX 等)

架构设计

类层次结构

QWidget
    ↑
    |
RenderWidgetBase (抽象基类)
    ↑
    |
    +--- VulkanWidget (Vulkan 实现)
    |
    +--- CustomWidget (QPainter 实现)

核心组件

1. RenderWidgetBase (基类)

文件位置: src/renderwidgetbase.h

职责:

  • 定义统一的渲染组件接口
  • 继承自 QWidget
  • 所有方法都是纯虚函数,由子类实现

核心接口:

// 初始化渲染器
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* 统一指针
  • 通过编译选项决定使用哪个实现
  • 统一的控制按钮和状态显示

核心代码:

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;
};

组件创建:

// 根据编译选项创建相应的渲染组件
#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 支持:

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 作为后备渲染方案

使用示例

基本使用

// 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));
}

锁屏检测集成

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 构造函数中添加选择逻辑
#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

运行时切换(未来功能)

可以扩展设计以支持运行时切换渲染器:

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 实例,内存影响微乎其微

测试建议

单元测试

// 测试 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. 用户体验:提供了更简洁的界面

这个设计为项目的后续发展提供了坚实的基础,使得添加新功能和维护现有代码变得更加容易。