在本页

Qt 画布画家 - 紧凑型健康示例

演示如何在QWindow 中使用QCanvasPainter

该示例演示了在纯QWindow 应用程序中使用 Qt Canvas Painter。完全没有使用Qt QuickQWidget ,除了 Core、GUI 和 Canvas Painter 之外,对 Qt 模块没有依赖性。

应用程序使用QRhi 设置加速 3D 渲染,并使用QCanvasPainter 渲染窗口中的所有内容。渲染和QCanvasPainterQRhi 的集成通过QCanvasRhiPaintDriver 进行管理。

演练

在根据平台选择要使用的 3D API 的同时,应用程序还提供了使用命令行参数请求一个 API 的可能性。

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents);

    QRhi::Implementation graphicsApi;
#if defined(Q_OS_WIN)
    graphicsApi = QRhi::D3D11;
#elif QT_CONFIG(metal)
    graphicsApi = QRhi::Metal;
#elif QT_CONFIG(vulkan)
    graphicsApi = QRhi::Vulkan;
#else
    graphicsApi = QRhi::OpenGLES2;
#endif

    QCommandLineParser cmdLineParser;
    cmdLineParser.addHelpOption();
    QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null"));
    cmdLineParser.addOption(nullOption);
    QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL"));
    cmdLineParser.addOption(glOption);
    QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
    cmdLineParser.addOption(vkOption);
    QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
    cmdLineParser.addOption(d3d11Option);
    QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
    cmdLineParser.addOption(d3d12Option);
    QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
    cmdLineParser.addOption(mtlOption);

    cmdLineParser.process(app);

    if (cmdLineParser.isSet(nullOption))
        graphicsApi = QRhi::Null;
    if (cmdLineParser.isSet(glOption))
        graphicsApi = QRhi::OpenGLES2;
    if (cmdLineParser.isSet(vkOption))
        graphicsApi = QRhi::Vulkan;
    if (cmdLineParser.isSet(d3d11Option))
        graphicsApi = QRhi::D3D11;
    if (cmdLineParser.isSet(d3d12Option))
        graphicsApi = QRhi::D3D12;
    if (cmdLineParser.isSet(mtlOption))
        graphicsApi = QRhi::Metal;

#if QT_CONFIG(opengl)
    QSurfaceFormat fmt;
    fmt.setDepthBufferSize(24);
    fmt.setStencilBufferSize(8);
#ifdef Q_OS_MACOS
    fmt.setVersion(4, 1);
    fmt.setProfile(QSurfaceFormat::CoreProfile);
#endif
    QSurfaceFormat::setDefaultFormat(fmt);
#endif

    MainWindow window(graphicsApi);
    window.resize(1920 / 2, 1080 / 2);
    window.show();

    return app.exec();
}

MainWindow 是 PainterWindow 的子类,PainterWindow 是QWindow 。PainterWindow 实现了一个窗口,显示通过 Qt 3D 图形抽象QRhi 渲染的内容。它的任务是在接收到暴露事件(即窗口显示时)时初始化QRhi 实例,管理交换链和深度模板缓冲区,同时在窗口调整大小时采取行动,并调用 MainWindow 中实现的虚拟 paint() 函数。

要更好地了解 PainterWindow 的实现,建议查看RHI 窗口示例,因为它在本质上与该示例中的 RhiWindow 类非常相似。

QCanvasPainter 的一个具体步骤是在成功初始化QRhi 后检索QCanvasPainterFactory 实例:

    if (!m_factory) {
        m_factory = new QCanvasPainterFactory;
        m_factory->create(m_rhi.get());
    }

render()函数会在窗口显示、调整大小以及响应通过requestUpdate() 调度的更新请求时调用。

RHI 窗口示例不同,我们不会通过QRhi API 创建顶点和统一缓冲区或图形管道,也不会直接发出绘制调用。相反,QCanvasRhiPaintDriver 将用于记录由QCanvasPainter 生成的渲染过程。

voidPainterWindow::render() {if(!m_factory|| !m_factory->isValid()|| !m_rhi|| !m_sc)return;if(!m_hasSwapChain||m_notExposed)return;if(m_sc->currentPixelSize()=  m_sc->surfacePixelSize()||m_newlyExposed) { resizeSwapChain();if(!m_hasSwapChain)return; m_newlyExposed= false; }=m_newlyExposed= false; }=m_newlyExposed= false.    QRhi::FrameOpResult r=m_rhi->beginFrame(m_sc.get());如果(r==QRhi::FrameOpSwapChainOutOfDate) { resizeSwapChain();if(!m_hasSwapChain)return; r=  m_rhi->beginFrame(m_sc.get()); }if(r!=) QRhi::FrameOpSuccess) {        qDebug("beginFrame failed with %d, retry", r);
        requestUpdate();return; } QRhiCommandBuffer*cb =  m_sc->currentFrameCommandBuffer();    QRhiRenderTarget*rt =  m_sc->currentFrameRenderTarget();    QCanvasRhiPaintDriver*pd =  m_factory->paintDriver();    QCanvasPainter*painter =  m_factory->painter(); pd->resetForNewFrame(); pd->beginPaint(cb,rt,m_fillColor,size(), float(devicePixelRatio())); paint(painter); pd->endPaint(); m_rhi->endFrame(m_sc.get()); }

MainWindow 的 paint() 实现与QCanvasPainter API 配合使用,无需关心低级细节,如QRhi.

首先,如果尚未完成,它会加载并注册图像:

void MainWindow::paint(QCanvasPainter *p)
{
    if (!m_initialized) {
        auto flags = QCanvasPainter::ImageFlag::GenerateMipmaps;
        m_b1ImageLight = p->addImage(QImage(":/images/icon_random_light.png"), flags);
        m_b1ImageDark = p->addImage(QImage(":/images/icon_random_dark.png"), flags);
        m_b2ImageLight = p->addImage(QImage(":/images/icon_theme_light.png"), flags);
        m_b2ImageDark = p->addImage(QImage(":/images/icon_theme_dark.png"), flags);
        m_b3ImageLight = p->addImage(QImage(":/images/icon_settings_light.png"), flags);
        m_b3ImageDark = p->addImage(QImage(":/images/icon_settings_dark.png"), flags);
        m_sImageLight = p->addImage(QImage(":/images/icon_run_light.png"), flags);
        m_sImageDark = p->addImage(QImage(":/images/icon_run_dark.png"), flags);
        m_initialized = true;
    }

然后是实际绘制,包括填充和描边路径、渲染文本和绘制图像。例如

    // Highlight pressed button
    if (m_selectedButton) {
        p->beginPath();
        p->roundRect(m_views[m_selectedButton].rect, viewRadius);
        p->setLineWidth(2.0f * m_px);
        p->setStrokeStyle(m_theme.highlight());
        p->stroke();
    }

QCanvasPainter 填充和描边调用在内部准备顶点、索引、统一数据和基于QRhi 的绘制调用。当在 PainterWindow::render() 中调用endPaint() 时,命令流将被刷新。此时,相关的QRhiCommandBuffer 上会记录一次渲染过程。

在返回之前,MainWindow::paint() 会调用 requestUpdate(),这是一个QWindow 函数,用于请求与显示器显示速率同步的新帧。这样就能确保窗口内容不断更新,从而使其能够产生动画效果。

详情请查看下面链接的完整示例源代码。

示例项目 @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.