Qt Quick 图片加载与 GPU 纹理机制详解:QImage、QQuickTextureFactory、QSGTexture 与异步加载

文章目录

  • [Qt Quick 图片加载与 GPU 纹理机制详解:QImage、QQuickTextureFactory、QSGTexture 与异步加载](#Qt Quick 图片加载与 GPU 纹理机制详解:QImage、QQuickTextureFactory、QSGTexture 与异步加载)
    • 一、前言
    • [二、先理解 Qt Quick 的图片渲染机制](#二、先理解 Qt Quick 的图片渲染机制)
      • [2.1 Qt Widgets 和 Qt Quick 的区别](#2.1 Qt Widgets 和 Qt Quick 的区别)
      • [2.2 Qt Quick 图片渲染流程](#2.2 Qt Quick 图片渲染流程)
    • [三、QImage:Qt 图片数据处理类](#三、QImage:Qt 图片数据处理类)
      • [3.1 QImage 是什么?](#3.1 QImage 是什么?)
      • [3.2 QImage 的内存特点](#3.2 QImage 的内存特点)
      • [3.3 QImage 和 GPU 的关系](#3.3 QImage 和 GPU 的关系)
    • [四、QQuickTextureFactory:Qt Quick 纹理工厂](#四、QQuickTextureFactory:Qt Quick 纹理工厂)
      • [4.1 QQuickTextureFactory 是什么?](#4.1 QQuickTextureFactory 是什么?)
      • [4.2 为什么需要 QQuickTextureFactory?](#4.2 为什么需要 QQuickTextureFactory?)
      • [4.3 QQuickTextureFactory 常用函数](#4.3 QQuickTextureFactory 常用函数)
        • [① textureFactoryForImage()](#① textureFactoryForImage())
        • [② createTexture()](#② createTexture())
        • [③ textureSize()](#③ textureSize())
        • [④ textureByteCount()](#④ textureByteCount())
        • [⑤ image()](#⑤ image())
    • [五、QSGTexture:Qt Quick 场景图纹理](#五、QSGTexture:Qt Quick 场景图纹理)
      • [5.1 QSGTexture 是什么?](#5.1 QSGTexture 是什么?)
      • [5.2 QSGTexture 的作用](#5.2 QSGTexture 的作用)
      • [5.3 QSGTexture 与 QSGSimpleTextureNode](#5.3 QSGTexture 与 QSGSimpleTextureNode)
    • 六、QQuickImageProvider:自定义图片提供器
      • [6.1 QQuickImageProvider 是什么?](#6.1 QQuickImageProvider 是什么?)
      • [6.2 自定义图片提供器](#6.2 自定义图片提供器)
      • [6.3 QQuickImageProvider 的局限性](#6.3 QQuickImageProvider 的局限性)
    • 七、QQuickAsyncImageProvider:异步图片加载
      • [7.1 什么是异步图片提供器?](#7.1 什么是异步图片提供器?)
      • [7.2 异步加载流程](#7.2 异步加载流程)
      • [7.3 QQuickImageResponse 是什么?](#7.3 QQuickImageResponse 是什么?)
    • [八、QQuickAsyncImageProvider 完整代码示例](#八、QQuickAsyncImageProvider 完整代码示例)
      • [8.1 定义异步图片响应类](#8.1 定义异步图片响应类)
      • [8.2 定义异步图片提供器](#8.2 定义异步图片提供器)
      • [8.3 注册 Provider](#8.3 注册 Provider)
      • [8.4 QML 使用](#8.4 QML 使用)
    • 九、重点分析:QQuickTextureFactory::textureFactoryForImage()
    • 十、图片异步加载与缩略图预加载优化
      • [10.1 为什么需要缩略图预加载?](#10.1 为什么需要缩略图预加载?)
      • [10.2 推荐的图片加载架构](#10.2 推荐的图片加载架构)
      • [10.3 缓存策略](#10.3 缓存策略)
      • [10.4 控制并发加载数量](#10.4 控制并发加载数量)
      • [10.5 优先级管理](#10.5 优先级管理)
    • 十一、常见问题及注意事项
      • [11.1 QImage 和 QSGTexture 可以直接互相替代吗?](#11.1 QImage 和 QSGTexture 可以直接互相替代吗?)
      • [11.2 QQuickTextureFactory 是不是 GPU 纹理?](#11.2 QQuickTextureFactory 是不是 GPU 纹理?)
      • [11.3 可以在工作线程直接创建 QSGTexture 吗?](#11.3 可以在工作线程直接创建 QSGTexture 吗?)
      • [11.4 QQuickImageProvider 和 QQuickAsyncImageProvider 有什么区别?](#11.4 QQuickImageProvider 和 QQuickAsyncImageProvider 有什么区别?)
      • [11.5 textureFactoryForImage() 会立即创建 GPU 纹理吗?](#11.5 textureFactoryForImage() 会立即创建 GPU 纹理吗?)
      • [11.6 图片加载完成后为什么还需要 finished()?](#11.6 图片加载完成后为什么还需要 finished()?)
    • 十二、总结
    • 十三、官方参考资料

Qt Quick 图片加载与 GPU 纹理机制详解:QImage、QQuickTextureFactory、QSGTexture 与异步加载

一、前言

在使用 Qt Quick(QML)开发图片浏览器、图像处理软件、工业视觉软件时,经常需要加载大量图片,并将图片显示在 QML 界面中。

例如:

  • 图片浏览器需要批量加载缩略图。
  • 工业视觉软件需要显示相机采集的图像。
  • PCB 检测软件需要显示大量检测图片。
  • 图像分析软件需要实现图片预览、缩放、平移。
  • 大型图片管理软件需要实现异步加载和预加载。

如果只是使用 Qt Widgets,通常可以通过 QPixmap、QLabel 等组件完成图片显示。

但是 Qt Quick 使用的是另一套渲染体系。

Qt Quick 通过 Scene Graph(场景图)组织界面元素,最终通过图形渲染后端将内容绘制到屏幕上。

因此,图片从磁盘读取到最终显示在 QML 界面上,中间涉及多个重要类:

类 主要作用
QImage 图片数据读取和处理
QQuickImageProvider 向 QML 提供自定义图片
QQuickAsyncImageProvider 提供异步图片加载能力
QQuickImageResponse 管理异步图片请求结果
QQuickTextureFactory 创建 Qt Quick 可使用的纹理
QSGTexture 场景图使用的纹理资源
QSGNode 场景图节点,组织渲染内容

本文重点介绍这些类的职责、工作流程以及实际使用方法。


二、先理解 Qt Quick 的图片渲染机制

2.1 Qt Widgets 和 Qt Quick 的区别

Qt Widgets 和 Qt Quick 都可以显示图片,但底层渲染机制存在区别。

Qt Widgets 图片显示:

cpp 复制代码
QPixmap pixmap("test.jpg");
ui->label->setPixmap(pixmap);

这里主要通过 QPixmap 和 Widgets 绘制系统完成图片显示。

Qt Quick 图片显示:

qml 复制代码
Image {
    source: "file:///D:/images/test.jpg"
    width: 400
    height: 300
    fillMode: Image.PreserveAspectFit
}

Qt Quick 会将图片加载和界面渲染分开处理。

大致过程:

  1. 读取图片文件。
  2. 将图片解码为图像数据。
  3. 准备纹理数据。
  4. 在场景图渲染阶段创建纹理资源。
  5. 通过 GPU 完成图片绘制。

这里需要理解一个关键问题:

负责读取图片的线程,不一定具备创建 GPU 纹理所需要的图形上下文。

如果在图片加载线程中直接创建图形纹理,就可能出现线程和图形上下文不匹配的问题。

Qt Quick 使用 QQuickTextureFactory 来解决图片加载阶段与纹理创建阶段之间的衔接问题。

2.2 Qt Quick 图片渲染流程

text 复制代码
             图片文件
                |
                v
          QImage 图片解码
                |
                v
         准备图片像素数据
                |
                v
       QQuickTextureFactory
                |
                v
       Qt Quick 场景图渲染线程
                |
                v
          createTexture()
                |
                v
             QSGTexture
                |
                v
           GPU 图形渲染
                |
                v
           QML 界面显示

这里有三个非常重要的概念:

  • QImage:保存图片数据。
  • QQuickTextureFactory:提供创建纹理的能力。
  • QSGTexture:供 Qt Quick 场景图使用的纹理对象。

这三个类不是同一个东西,也不能相互替代。


三、QImage:Qt 图片数据处理类

3.1 QImage 是什么?

QImage 是 Qt 提供的图片数据处理类,主要用于图片的读取、存储和像素操作。

头文件:

cpp 复制代码
#include <QImage>

常见功能:

  • 加载 PNG、JPG、BMP 等图片。
  • 获取图片宽度和高度。
  • 获取图片像素数据。
  • 修改图片像素。
  • 图片缩放和裁剪。
  • 图片格式转换。
  • 图片保存。

例如:

cpp 复制代码
QImage image("D:/images/test.jpg");

if (image.isNull()) {
    qDebug() << "图片加载失败";
    return;
}

qDebug() << "宽度:" << image.width();
qDebug() << "高度:" << image.height();
qDebug() << "格式:" << image.format();

3.2 QImage 的内存特点

QImage 主要管理 CPU 侧的图片数据。

例如一张 1920×1080 的 RGBA 图片:

text 复制代码
宽度:1920
高度:1080
每个像素:4 字节

理论像素数据大小:

text 复制代码
1920 × 1080 × 4
= 8,294,400 字节
≈ 7.91 MiB

注意:这只是未考虑行对齐、额外元数据和内部存储开销时的理论像素数据大小。

QImage 采用隐式共享机制(Implicit Sharing),多个 QImage 对象可以共享同一份底层数据,发生修改时才可能触发数据分离。

这有利于降低图片复制的内存开销。

3.3 QImage 和 GPU 的关系

QImage 本身不是 GPU 纹理。

它保存的是图片像素数据,通常位于 CPU 可访问的内存中。

如果要在 Qt Quick 中显示 QImage,需要将图片数据交给 Qt Quick 的纹理创建流程。

这就引出了下一个类:QQuickTextureFactory。


四、QQuickTextureFactory:Qt Quick 纹理工厂

4.1 QQuickTextureFactory 是什么?

QQuickTextureFactory 是 Qt Quick 模块提供的一个抽象类。

主要作用:

为 QML 图片加载提供纹理创建接口,将图片数据与 Qt Quick 的场景图渲染过程连接起来。

头文件:

cpp 复制代码
#include <QQuickTextureFactory>

CMake:

cmake 复制代码
find_package(Qt6 REQUIRED COMPONENTS Quick)

target_link_libraries(MyApp PRIVATE Qt6::Quick)

qmake:

pro 复制代码
QT += quick

这个类继承自 QObject。

它本身是一个抽象类,不能直接实例化,因为其中包含纯虚函数。

4.2 为什么需要 QQuickTextureFactory?

假设图片加载过程如下:

cpp 复制代码
QImage image("test.jpg");

此时图片已经加载到内存。

如果立即创建 GPU 纹理,就需要合适的图形渲染上下文。

但是图片解码通常可以在普通工作线程中进行,而 GPU 纹理创建需要遵循 Qt Quick 渲染后端的线程和资源管理规则。

因此,Qt 将这两个过程分开:

图片加载阶段:

text 复制代码
读取图片
   |
   v
解码为 QImage
   |
   v
创建纹理工厂

渲染阶段:

text 复制代码
Qt Quick 渲染线程
   |
   v
调用 createTexture()
   |
   v
创建 QSGTexture
   |
   v
GPU 渲染

QQuickTextureFactory 就是两个阶段之间的桥梁。

需要注意,Qt 官方文档中的部分描述使用 OpenGL 纹理作为历史背景。现代 Qt 6 的 Qt Quick 还支持基于 QRhi 的不同图形后端,例如 Vulkan、Metal、Direct3D 和 OpenGL。实际项目中不应将 QSGTexture 简单等同于某一种图形 API 的纹理对象。

4.3 QQuickTextureFactory 常用函数

Qt 6 中主要有以下函数:

函数 类型 作用
textureFactoryForImage() 静态函数 根据 QImage 创建纹理工厂
createTexture() 纯虚函数 创建 QSGTexture
textureSize() 纯虚函数 获取纹理尺寸
textureByteCount() 纯虚函数 获取纹理占用的字节数
image() 虚函数 返回纹理对应的 QImage

下面分别介绍。

① textureFactoryForImage()

函数:

cpp 复制代码
static QQuickTextureFactory *
textureFactoryForImage(const QImage &image);

作用:

将 QImage 包装成 QQuickTextureFactory 对象。

示例:

cpp 复制代码
QImage image("test.jpg");

QQuickTextureFactory *factory =
    QQuickTextureFactory::textureFactoryForImage(image);

如果图片无效,函数会返回 nullptr。

例如:

cpp 复制代码
QImage image;

QQuickTextureFactory *factory =
    QQuickTextureFactory::textureFactoryForImage(image);

if (!factory) {
    qDebug() << "纹理工厂创建失败";
}

这里需要注意:

textureFactoryForImage() 并不是直接返回 QSGTexture,而是返回一个纹理工厂。

真正创建 QSGTexture 的操作发生在后续渲染阶段。

② createTexture()

函数:

cpp 复制代码
virtual QSGTexture *createTexture(
    QQuickWindow *window
) const = 0;

这是 QQuickTextureFactory 最核心的函数之一。

作用:

根据纹理工厂中的图片数据创建 QSGTexture 对象。

参数:

window:当前 Qt Quick 窗口,提供对应的图形渲染环境。

返回值:

QSGTexture*:Qt Quick 场景图使用的纹理对象。

这个函数由场景图渲染线程调用。

注意:

  • 不要在普通图片加载线程中随意调用。
  • 不要在没有对应渲染环境的情况下直接创建底层图形资源。
  • 每次调用都应该返回一个新的、独立的纹理实例。
  • 纹理资源的释放也需要遵循 Qt Quick 的渲染资源生命周期规则。
③ textureSize()

函数:

cpp 复制代码
virtual QSize textureSize() const = 0;

作用:

获取纹理尺寸。

示例:

cpp 复制代码
QSize size = factory->textureSize();

qDebug() << "纹理宽度:" << size.width();
qDebug() << "纹理高度:" << size.height();

例如图片尺寸是 1920×1080:

text 复制代码
width  = 1920
height = 1080

该函数可能在任意线程被调用,因此不能依赖已经绑定的 OpenGL 上下文。

④ textureByteCount()

函数:

cpp 复制代码
virtual int textureByteCount() const = 0;

作用:

返回纹理占用的内存字节数。

例如:

cpp 复制代码
int bytes = factory->textureByteCount();

qDebug() << "纹理占用字节数:" << bytes;

它可以帮助 Qt Quick 了解纹理的内存开销。

注意:实际纹理内存大小可能受到像素格式、GPU 对齐、内部存储方式等因素影响,不能简单地认为一定等于 width × height × 4。

⑤ image()

函数:

cpp 复制代码
virtual QImage image() const;

作用:

返回这个纹理对应的 QImage。

不过,这个函数并不常用,而且官方文档明确指出它可能比较慢。

如果需要返回图片,应该确保返回的是自包含的 QImage,避免依赖生命周期不确定的外部像素内存。


五、QSGTexture:Qt Quick 场景图纹理

5.1 QSGTexture 是什么?

QSGTexture 是 Qt Quick Scene Graph 中的纹理抽象类。

它表示可以被场景图渲染节点使用的纹理资源。

头文件:

cpp 复制代码
#include <QSGTexture>

它与 QQuickTextureFactory 的区别:

  • QQuickTextureFactory:负责提供创建纹理的能力。
  • QSGTexture:表示创建完成后供场景图使用的纹理资源。

可以这样理解:

text 复制代码
QImage
  |
  v
QQuickTextureFactory
  |
  v
QSGTexture
  |
  v
Scene Graph
  |
  v
GPU 渲染

5.2 QSGTexture 的作用

主要包括:

  1. 保存和管理纹理资源。
  2. 提供纹理尺寸等信息。
  3. 供场景图节点引用。
  4. 支持纹理坐标和纹理采样。
  5. 配合图形渲染后端完成图片绘制。

需要注意,QSGTexture 是 Qt Quick 的纹理抽象,不建议在普通业务逻辑中直接操作其底层图形资源。

5.3 QSGTexture 与 QSGSimpleTextureNode

如果需要自定义场景图节点显示图片,可以使用 QSGSimpleTextureNode。

例如:

cpp 复制代码
QSGSimpleTextureNode *node =
    new QSGSimpleTextureNode();

node->setTexture(texture);

node->setRect(
    QRectF(0, 0, 400, 300)
);

这里:

  • texture 是 QSGTexture 对象。
  • setTexture() 设置节点使用的纹理。
  • setRect() 设置纹理显示区域。

最终 Qt Quick 会将这个节点纳入场景图渲染流程。

注意:QSGNode 及其子类通常需要在场景图渲染线程中创建和操作,不能随意跨线程访问。


六、QQuickImageProvider:自定义图片提供器

6.1 QQuickImageProvider 是什么?

QQuickImageProvider 是 Qt Quick 提供的自定义图片提供接口。

当 QML 需要加载图片时,可以通过自定义 Provider 从数据库、网络、内存缓存或者其他数据源获取图片。

头文件:

cpp 复制代码
#include <QQuickImageProvider>

常见使用场景:

  • 从数据库加载图片。
  • 从内存缓存加载缩略图。
  • 从网络服务器获取图片。
  • 根据图片 ID 动态生成图片。
  • 加载工业检测系统中的图像数据。

6.2 自定义图片提供器

例如,我们希望通过图片 ID 加载图片。

首先定义 Provider:

cpp 复制代码
class MyImageProvider : public QQuickImageProvider
{
public:
    MyImageProvider()
        : QQuickImageProvider(QQuickImageProvider::Image)
    {
    }

    QImage requestImage(
        const QString &id,
        QSize *size,
        const QSize &requestedSize
    ) override
    {
        Q_UNUSED(size);
        Q_UNUSED(requestedSize);

        QImage image(
            QString("D:/images/%1.jpg").arg(id)
        );

        return image;
    }
};

然后注册:

cpp 复制代码
engine.addImageProvider(
    "myimage",
    new MyImageProvider()
);

QML 中使用:

qml 复制代码
Image {
    source: "image://myimage/10001"
}

当 QML 加载图片时,会调用 Provider 的 requestImage()。

这里的 10001 就是传入的图片 ID。

6.3 QQuickImageProvider 的局限性

普通 QQuickImageProvider 可以实现自定义图片加载,但同步请求可能导致调用方等待图片加载完成。

如果图片较大,或者需要同时加载大量图片,就可能影响界面响应。

例如:

text 复制代码
用户打开图片列表
       |
       v
同时加载 100 张图片
       |
       v
大量图片解码
       |
       v
界面响应受到影响

这种情况下,可以考虑异步图片提供器。


七、QQuickAsyncImageProvider:异步图片加载

7.1 什么是异步图片提供器?

QQuickAsyncImageProvider 继承自 QQuickImageProvider,提供异步图片加载接口。

主要作用:

将耗时的图片读取、解码等工作放到后台执行,减少对界面线程的影响。

头文件:

cpp 复制代码
#include <QQuickAsyncImageProvider>

它的核心接口是:

cpp 复制代码
virtual QQuickImageResponse *requestImageResponse(
    const QString &id,
    const QSize &requestedSize
) = 0;

当 QML 发起图片请求时,Qt Quick 会调用这个接口。

返回一个 QQuickImageResponse 对象,用来管理异步加载过程。

7.2 异步加载流程

text 复制代码
             QML Image
                 |
                 v
       QQuickAsyncImageProvider
                 |
                 v
         requestImageResponse()
                 |
                 v
         QQuickImageResponse
                 |
                 v
           后台加载图片
                 |
                 v
              QImage
                 |
                 v
       发出 finished() 信号
                 |
                 v
       Qt Quick 获取纹理工厂
                 |
                 v
       QQuickTextureFactory
                 |
                 v
            QSGTexture
                 |
                 v
              GPU

这里有两个重要的类:

  • QQuickAsyncImageProvider:负责创建异步图片请求。
  • QQuickImageResponse:负责保存请求结果,并通知 Qt Quick 图片加载已经完成。

7.3 QQuickImageResponse 是什么?

它是异步图片请求的响应类。

主要接口:

cpp 复制代码
virtual QQuickTextureFactory *
textureFactory() const = 0;

返回图片对应的纹理工厂。

另外还有:

cpp 复制代码
void finished();

表示图片加载任务已经完成。

以及:

cpp 复制代码
virtual void cancel();

用于通知图片请求不再需要。

注意:即使请求被取消,响应对象仍然需要在任务真正结束后发出 finished(),以便 Qt Quick 正确清理响应对象。不能在后台任务还在运行时提前发出完成信号。


八、QQuickAsyncImageProvider 完整代码示例

下面通过一个简单的示例,演示如何实现异步图片加载。

8.1 定义异步图片响应类

cpp 复制代码
#include <QQuickImageResponse>
#include <QQuickTextureFactory>
#include <QImage>
#include <QThreadPool>
#include <QRunnable>
#include <QMutex>
#include <QMutexLocker>
#include <QAtomicInteger>

class MyImageResponse : public QQuickImageResponse
{
public:
    explicit MyImageResponse(const QString &path)
        : m_path(path)
    {
        QThreadPool::globalInstance()->start(
            QRunnable::create([this]() {
                QImage image(m_path);

                {
                    QMutexLocker locker(&m_mutex);
                    m_image = std::move(image);
                }

                emit finished();
            })
        );
    }

    QQuickTextureFactory *textureFactory() const override
    {
        QMutexLocker locker(&m_mutex);

        if (m_image.isNull())
            return nullptr;

        return QQuickTextureFactory::
            textureFactoryForImage(m_image);
    }

private:
    QString m_path;
    mutable QMutex m_mutex;
    QImage m_image;
};

代码核心:

  1. 创建响应对象。
  2. 在线程池中执行图片读取。
  3. 将读取结果保存到 m_image。
  4. 发出 finished() 信号。
  5. Qt Quick 在需要时调用 textureFactory()。
  6. 通过 QQuickTextureFactory 返回纹理工厂。

说明:这是用于理解调用关系的简化示例。生产项目还需要处理响应对象与后台任务之间的生命周期、取消请求、错误信息以及任务完成后的安全清理。不能简单地让后台任务持有裸 this 指针,并假设响应对象在任务结束前一定存在。

8.2 定义异步图片提供器

cpp 复制代码
class MyAsyncImageProvider
    : public QQuickAsyncImageProvider
{
public:
    QQuickImageResponse *requestImageResponse(
        const QString &id,
        const QSize &requestedSize
    ) override
    {
        Q_UNUSED(requestedSize);

        QString path =
            QString("D:/images/%1.jpg").arg(id);

        return new MyImageResponse(path);
    }
};

这里根据图片 ID 生成图片路径,然后创建异步响应对象。

8.3 注册 Provider

cpp 复制代码
engine.addImageProvider(
    "asyncimage",
    new MyAsyncImageProvider()
);

8.4 QML 使用

qml 复制代码
Image {
    source: "image://asyncimage/10001"

    width: 400
    height: 300

    fillMode: Image.PreserveAspectFit

    asynchronous: true
}

这里的图片请求由自定义 Provider 处理。

注意:QQuickAsyncImageProvider 本身已经提供异步响应机制,Image.asynchronous 不能替代 Provider 内部正确的线程和任务管理。


九、重点分析:QQuickTextureFactory::textureFactoryForImage()

现在回到最初遇到的代码:

cpp 复制代码
return QQuickTextureFactory::textureFactoryForImage(m_image);

这行代码经常出现在:

cpp 复制代码
QQuickTextureFactory *
MyImageResponse::textureFactory() const
{
    return QQuickTextureFactory::
        textureFactoryForImage(m_image);
}

它的作用就是:

将已经加载完成的 QImage 包装成 Qt Quick 能够使用的纹理工厂。

从源码实现的角度看,Qt 会尝试创建适合当前场景图后端的纹理工厂;如果没有可用的特定实现,则使用默认实现。

另外,如果传入的 QImage 为空,函数会返回 nullptr。

因此,这行代码并不是直接将 QImage 上传到 GPU,而是提供后续纹理创建所需要的对象。

可以将它理解为:

text 复制代码
QImage
  |
  | 图片像素数据
  v
textureFactoryForImage()
  |
  | 创建纹理工厂
  v
QQuickTextureFactory
  |
  | Qt Quick 在渲染阶段调用
  v
createTexture()
  |
  v
QSGTexture
  |
  v
GPU 渲染

这也是 Qt Quick 能够将图片加载与 GPU 纹理创建分离的重要机制。


十、图片异步加载与缩略图预加载优化

如果开发的是图片浏览器或者工业视觉软件,仅仅实现异步加载还不够。

因为当图片数量达到几万甚至几十万张时,还需要考虑缓存、预加载和内存管理。

10.1 为什么需要缩略图预加载?

假设一个图片列表有 10000 张图片。

如果每次用户滚动到某个位置时才开始读取图片:

text 复制代码
用户滚动
   |
   v
读取图片
   |
   v
解码图片
   |
   v
创建纹理
   |
   v
显示缩略图

就可能出现图片显示延迟、滚动卡顿等问题。

因此,可以提前加载当前页面后面的图片。

例如:

cpp 复制代码
m_thumbnailPrefetchPages =
    qBound(
        0,
        config.value("thumbnail_prefetch_pages").toInt(5),
        20
    );

这行代码表示:

  • 默认预加载 5 页。
  • 最少预加载 0 页。
  • 最多预加载 20 页。

10.2 推荐的图片加载架构

可以将整个图片系统划分为几个模块:

text 复制代码
             QML 图片列表
                  |
                  v
          图片加载请求管理器
                  |
          +-------+-------+
          |               |
          v               v
       缩略图缓存       预加载管理器
          |               |
          +-------+-------+
                  |
                  v
             任务调度器
                  |
                  v
             线程池解码
                  |
                  v
                QImage
                  |
                  v
         QQuickTextureFactory
                  |
                  v
              QSGTexture
                  |
                  v
                GPU

各模块的职责:

模块 职责
图片列表 展示图片和接收用户操作
请求管理器 管理图片加载请求
缩略图缓存 避免重复读取和解码
预加载管理器 提前加载后续图片
任务调度器 控制并发数量和优先级
线程池 执行图片读取和解码
QImage 保存解码后的图片数据
QQuickTextureFactory 提供纹理创建接口
QSGTexture 为场景图提供纹理资源

10.3 缓存策略

建议至少设计两级缓存:

第一级:CPU 图片缓存

保存 QImage 数据。

优点:

  • 不需要重复读取磁盘。
  • 不需要重复解码图片。
  • 可以快速生成纹理工厂。

缺点:

  • 大量图片会占用较多内存。

第二级:GPU 纹理缓存

由 Qt Quick 场景图及其内部机制管理纹理资源。

优点:

  • 避免不必要的重复纹理创建。
  • 提高图片重复显示时的效率。

缺点:

  • 受 GPU 显存限制。
  • 纹理资源需要遵循场景图生命周期。
  • 场景图重建、窗口变化等情况可能影响纹理资源。

需要注意:Qt Quick 自身已经有图片和纹理缓存机制,业务层不应在不了解其行为的情况下重复建立大量 GPU 纹理缓存。

10.4 控制并发加载数量

如果同时加载大量图片,可能导致:

  • 磁盘 IO 过高。
  • CPU 解码占用过高。
  • 内存快速增长。
  • 图片请求排队。
  • GPU 纹理上传压力增大。

建议使用任务队列控制并发。

例如:

cpp 复制代码
QThreadPool *pool =
    QThreadPool::globalInstance();

pool->setMaxThreadCount(4);

这里将线程池最大线程数设置为 4。

实际项目中,应根据 CPU 核数、图片大小、磁盘性能以及其他任务负载进行调整。

10.5 优先级管理

对于图片浏览器,可以设置三种任务优先级:

优先级 任务 说明
高 当前可见区域图片 优先保证用户看到的图片
中 即将进入可见区域的图片 提前准备
低 后续页面预加载 空闲时执行

这样可以避免后台预加载任务占用所有资源,导致当前界面图片无法及时显示。


十一、常见问题及注意事项

11.1 QImage 和 QSGTexture 可以直接互相替代吗?

不能。

QImage 主要负责图片像素数据处理。

QSGTexture 是 Qt Quick 场景图使用的纹理资源。

两者处于不同的处理阶段。

11.2 QQuickTextureFactory 是不是 GPU 纹理?

不是。

它是纹理工厂,负责提供创建纹理的能力。

实际创建出来的对象是 QSGTexture。

11.3 可以在工作线程直接创建 QSGTexture 吗?

通常不应该。

QSGTexture 属于场景图渲染资源,其创建和使用需要遵循 Qt Quick 的渲染线程及图形资源规则。

应当让 Qt Quick 在合适的渲染阶段创建纹理。

11.4 QQuickImageProvider 和 QQuickAsyncImageProvider 有什么区别?

对比项 QQuickImageProvider QQuickAsyncImageProvider
加载方式 通过 requestImage 等接口提供图片 通过异步响应提供图片
主要接口 requestImage() requestImageResponse()
适用场景 简单自定义图片加载 大量图片或耗时图片加载
响应对象 直接返回图片数据 QQuickImageResponse
纹理工厂 可由 Qt Quick 处理图片转换 通过 textureFactory() 返回

11.5 textureFactoryForImage() 会立即创建 GPU 纹理吗?

不能这样理解。

它创建的是纹理工厂对象。

真正创建 QSGTexture 的工作由后续场景图渲染阶段完成。

11.6 图片加载完成后为什么还需要 finished()?

因为 Qt Quick 需要知道异步请求什么时候结束。

finished() 用于通知引擎响应任务已经完成。

随后引擎才可以根据需要获取纹理工厂并继续处理图片显示。


十二、总结

本文介绍了 Qt Quick 图片加载和 GPU 纹理创建过程中涉及的核心类。

可以用下面这张表进行总结:

类 核心职责 关键词
QImage 保存和处理图片 CPU 图片数据
QQuickImageProvider 自定义图片来源 图片提供
QQuickAsyncImageProvider 异步图片请求 后台加载
QQuickImageResponse 管理异步加载结果 请求响应
QQuickTextureFactory 创建纹理的工厂 纹理创建
QSGTexture 场景图纹理资源 GPU 渲染
QSGNode 组织场景图内容 渲染节点

最重要的是理解下面这条链路:

text 复制代码
图片读取
   |
   v
QImage
   |
   v
QQuickTextureFactory
   |
   v
QSGTexture
   |
   v
Qt Quick Scene Graph
   |
   v
GPU
   |
   v
QML 图片显示

如果开发的是图片浏览器、工业视觉软件或者大规模图片管理系统,建议重点掌握:

  1. QImage 的内存管理和隐式共享机制。
  2. QQuickAsyncImageProvider 的异步加载机制。
  3. QQuickImageResponse 的任务生命周期管理。
  4. QQuickTextureFactory 的纹理创建机制。
  5. QSGTexture 的渲染资源管理。
  6. 缩略图缓存、预加载、并发控制和优先级调度。

理解这些内容之后,就能够更好地分析 Qt Quick 图片显示延迟、缩略图加载卡顿、CPU 占用过高以及 GPU 纹理资源管理等问题。


十三、官方参考资料

  1. QQuickTextureFactory 官方文档
  2. QQuickImageResponse 官方文档
  3. QQuickAsyncImageProvider 官方文档
  4. QQuickImageProvider 官方文档
  5. QSGTexture 官方文档
  6. Qt Quick Scene Graph 官方文档

关键词: Qt、Qt Quick、QML、QImage、QQuickTextureFactory、QQuickImageProvider、QQuickAsyncImageProvider、QQuickImageResponse、QSGTexture、GPU 渲染、异步图片加载、缩略图预加载。

相关推荐
青梅味猪大肠21 分钟前
【操作系统-60】临近适应算法
java·linux·开发语言·嵌入式硬件·算法·操作系统
starzy19902 小时前
深入理解Scala可变Map:动态构建场景下的性能与设计权衡
开发语言·scala
zhanghaha13143 小时前
C# 系列教程_7 判断语句(分支)保姆级精讲
开发语言·c#
apcipot_rain3 小时前
C++转Java日记(02)
java·开发语言·c++
杜 硕3 小时前
从零开始的C++生活——第七讲——02
开发语言·c++·生活
zhanghaha13143 小时前
C# 系列教程_8 循环语句 保姆级精讲(面向新手)
开发语言·c#
麦麦麦当劳大王12 小时前
【油猴脚本】Boss直聘 岗位职责复制
开发语言·前端·javascript
starzy199012 小时前
深入理解Scala Map:从实现原理到工程实践
开发语言·后端·scala
只睡四小时13 小时前
Three.js Sub-tick:漏判 60.7%→0
开发语言·javascript·性能优化·ecmascript·游戏开发·three.js·碰撞检测