QRhi类

加速2D/3D图形API抽象。 更多...

头文件 #include <QRhi>
CMakefind_package(Qt6 REQUIRED COMPONENTS Gui)
target_link_libraries(mytarget PRIVATE Qt6::Gui)
qmakeQT += gui
Qt 6.6

公共类型

枚举BeginFrameFlag { }
标志BeginFrameFlags
枚举EndFrameFlag { SkipPresent }
标志EndFrameFlags
枚举Feature { MultisampleTexture, MultisampleRenderBuffer, DebugMarkers, Timestamps, Instancing, …, MultiView }
枚举Flag { EnableDebugMarkers, EnableTimestamps, PreferSoftwareRenderer, EnablePipelineCacheDataSave, SuppressSmokeTestWarnings }
标志标志
枚举FrameOpResult { FrameOpSuccess, FrameOpError, FrameOpSwapChainOutOfDate, FrameOpDeviceLost }
枚举Implementation { Null, Vulkan, OpenGLES2, D3D11, D3D12, Metal }
枚举ResourceLimit { TextureSizeMin, TextureSizeMax, MaxColorAttachments, FramesInFlight, MaxAsyncReadbackFrames, …, MaxVertexOutputs }

公共函数

~QRhi()
voidaddCleanupCallback(const QRhi::CleanupCallback &callback)
voidaddCleanupCallback(const void *key, const QRhi::CleanupCallback &callback)
QRhi::Implementationbackend() const
const char *backendName() const
QRhi::FrameOpResultbeginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags = {})
QRhi::FrameOpResultbeginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags = {})
QMatrix4x4clipSpaceCorrMatrix() const
intcurrentFrameSlot() const
QRhiDriverInfodriverInfo() const
QRhi::FrameOpResultendFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags = {})
QRhi::FrameOpResultendOffscreenFrame(QRhi::EndFrameFlags flags = {})
QRhi::FrameOpResultfinish()
boolisClipDepthZeroToOne() const
boolisDeviceLost() const
boolisFeatureSupported(QRhi::Feature feature) const
boolisRecordingFrame() const
boolisTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = {}) const
boolisYUpInFramebuffer() const
boolisYUpInNDC() const
boolmakeThreadLocalNativeContextCurrent()
const QRhiNativeHandles *nativeHandles()
QRhiBuffer *newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
QRhiComputePipeline *newComputePipeline()
QRhiGraphicsPipeline *newGraphicsPipeline()
QRhiRenderBuffer *newRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount = 1, QRhiRenderBuffer::Flags flags = {}, QRhiTexture::Format backingFormatHint = QRhiTexture::UnknownFormat)
QRhiSampler *newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode addressU, QRhiSampler::AddressMode addressV, QRhiSampler::AddressMode addressW = QRhiSampler::Repeat)
QRhiShaderResourceBindings *newShaderResourceBindings()
QRhiSwapChain *newSwapChain()
QRhiTexture *newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount = 1, QRhiTexture::Flags flags = {})
QRhiTexture *newTexture(QRhiTexture::Format format, int width, int height, int depth, int sampleCount = 1, QRhiTexture::Flags flags = {})
QRhiTexture *newTextureArray(QRhiTexture::Format format, int arraySize, const QSize &pixelSize, int sampleCount = 1, QRhiTexture::Flags flags = {})
QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags = {})
QRhiResourceUpdateBatch *nextResourceUpdateBatch()
QByteArraypipelineCacheData()
voidreleaseCachedResources()
voidremoveCleanupCallback(const void *key)
intresourceLimit(QRhi::ResourceLimit limit) const
voidrunCleanup()
voidsetPipelineCacheData(const QByteArray &data)
QRhiStatsstatistics() const
QList<int>supportedSampleCounts() const
QThread *thread() const
intubufAligned(int v) const
intubufAlignment() const

静态公共成员

const char *backendName(QRhi::Implementation impl)
QRhi *create(QRhi::Implementation impl, QRhiInitParams *params, QRhi::Flags flags = {}, QRhiNativeHandles *importDevice = nullptr)
intmipLevelsForSize(const QSize &size)
boolprobe(QRhi::Implementation impl, QRhiInitParams *params)
QSizesizeForMipLevel(int mipLevel, const QSize &baseLevelSize)
QRhiSwapChainProxyDataupdateSwapChainProxyData(QRhi::Implementation impl, QWindow *window)

详细介绍

Qt渲染硬件接口是硬件加速图形API的抽象,例如,OpenGLOpenGL ESDirect3DMetalVulkan

警告: Qt Gui模块中的QRhi类族,包括QShaderQShaderDescription,提供了有限的兼容性保证。这些类没有源代码或二进制兼容性保证,这意味着API仅保证与应用程序开发所使用的Qt版本兼容。然而,源代码不兼容的更改旨在保持最小化,并且只会在小版本发布中(6.7,6.8等)进行。要在应用程序中使用这些类,请链接到Qt::GuiPrivate(如果使用CMake),并包含带有rhi前缀的头文件,例如#include <rhi/qrhi.h>

每个QRhi实例都有一个针对特定图形API的后端。后端的选取是一个运行时选择,由创建QRhi实例的应用程序或库负责。一些后端在多个平台上可用(OpenGL,Vulkan,Null),而特定平台的API只能在这个平台上运行时使用(Metal在macOS/iOS上,Direct3D在Windows上)。

目前可用的后端包括

  • OpenGL 2.1 / OpenGL ES 2.0或更新版本。当存在时,将使用一些扩展和较新的核心规范功能,例如启用多样本帧缓冲区或计算着色器。也支持在核心配置文件上下文中运行。如果需要,应用程序可以在运行时查询功能标志以检查QRhiBehindOpenGL上下文中不支持的OpenGL功能。OpenGL后端基于QOpenGLContextQOpenGLFunctions以及Qt GUI模块的跨平台基础设施。
  • Direct3D 11.1或更新版本,使用Shader Model 5.0或更新版本。当D3D运行时没有支持11.1功能或Shader Model 5.0时,使用加速图形设备初始化将失败,但使用软件适配器仍然是一个选项。
  • Windows 10版本1703及更新的Direct3D 12,使用Shader Model 5.0或更新版本。Qt需要ID3D12Device2存在,因此需要至少Windows 10版本1703。默认情况下,创建D3D12设备时,指定最小功能级别为D3D_FEATURE_LEVEL_11_0
  • Metal 1.2或更新版本。
  • Vulkan 1.0或更新版本,可选地利用一些Vulkan 1.1级别的功能。
  • Null,一个不发出任何图形调用的一“虚假”后端。

为了允许在Qt应用程序和库中一次编写着色器代码,所有着色器都期望使用单一语言编写,然后编译成SPIR-V。然后从这个版本生成各种着色器语言版本,以及反射信息(输入,输出,着色器资源)。然后这些信息被打包成易于高效序列化的QShader实例。生成此类着色器的编译器和工具不是QRhi和Qt GUI模块的一部分,但使用这些着色器的核心类,即QShaderQShaderDescription是。编译和转换API和工具是Qt Shader Tools模块的一部分。

请参阅RHI Window Example,以了解如何使用QRhi创建一个可移植、跨平台的应用程序,该程序在QWindow上执行加速3D渲染的入门示例。

API的印象

为了快速查看API,以下是一个短小全面且不涉及窗口设置的示例,呈现了一个跨平台的完整、可运行的应用程序,它在屏幕外渲染20帧,然后读取GPU上的纹理内容并将其保存到文件中。有关在屏幕上渲染的示例,这将涉及设置QWindow和swapchain,请参阅RHI Window Example

出于简洁起见,QRhi的初始化基于平台:这里提供的示例代码在Windows上选择了Direct 3D 12,在macOS和iOS上选择了Metal,否则使用Vulkan。该应用程序从不使用OpenGL和Direct 3D 11,但可以添加一些额外的行来支持这些。

#include <QGuiApplication>
#include <QImage>
#include <QFile>
#include <rhi/qrhi.h>

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

#if QT_CONFIG(vulkan)
    QVulkanInstance inst;
#endif
    std::unique_ptr<QRhi> rhi;
#if defined(Q_OS_WIN)
    QRhiD3D12InitParams params;
    rhi.reset(QRhi::create(QRhi::D3D12, &params));
#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
    QRhiMetalInitParams params;
    rhi.reset(QRhi::create(QRhi::Metal, &params));
#elif QT_CONFIG(vulkan)
    inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
    if (inst.create()) {
        QRhiVulkanInitParams params;
        params.inst = &inst;
        rhi.reset(QRhi::create(QRhi::Vulkan, &params));
    } else {
        qFatal("Failed to create Vulkan instance");
    }
#endif
    if (rhi)
        qDebug() << rhi->backendName() << rhi->driverInfo();
    else
        qFatal("Failed to initialize RHI");

    float rotation = 0.0f;
    float opacity = 1.0f;
    int opacityDir = 1;

    std::unique_ptr<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8,
                                                     QSize(1280, 720),
                                                     1,
                                                     QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
    tex->create();
    std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.get() }));
    std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
    rt->setRenderPassDescriptor(rp.get());
    rt->create();

    QMatrix4x4 viewProjection = rhi->clipSpaceCorrMatrix();
    viewProjection.perspective(45.0f, 1280 / 720.f, 0.01f, 1000.0f);
    viewProjection.translate(0, 0, -4);

    static float vertexData[] = { // Y up, CCW
        0.0f,   0.5f,     1.0f, 0.0f, 0.0f,
        -0.5f, -0.5f,     0.0f, 1.0f, 0.0f,
        0.5f,  -0.5f,     0.0f, 0.0f, 1.0f,
    };

    std::unique_ptr<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable,
                                                    QRhiBuffer::VertexBuffer,
                                                    sizeof(vertexData)));
    vbuf->create();

    std::unique_ptr<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic,
                                                    QRhiBuffer::UniformBuffer,
                                                    64 + 4));
    ubuf->create();

    std::unique_ptr<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
    srb->setBindings({
        QRhiShaderResourceBinding::uniformBuffer(0,
                                                 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
                                                 ubuf.get())
    });
    srb->create();

    std::unique_ptr<QRhiGraphicsPipeline> ps(rhi->newGraphicsPipeline());
    QRhiGraphicsPipeline::TargetBlend premulAlphaBlend;
    premulAlphaBlend.enable = true;
    ps->setTargetBlends({ premulAlphaBlend });
    static auto getShader = [](const QString &name) {
        QFile f(name);
        return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
    };
    ps->setShaderStages({
        { QRhiShaderStage::Vertex, getShader(QLatin1String("color.vert.qsb")) },
        { QRhiShaderStage::Fragment, getShader(QLatin1String("color.frag.qsb")) }
    });
    QRhiVertexInputLayout inputLayout;
    inputLayout.setBindings({
        { 5 * sizeof(float) }
    });
    inputLayout.setAttributes({
        { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
        { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
    });
    ps->setVertexInputLayout(inputLayout);
    ps->setShaderResourceBindings(srb.get());
    ps->setRenderPassDescriptor(rp.get());
    ps->create();

    QRhiCommandBuffer *cb;
    for (int frame = 0; frame < 20; ++frame) {
        rhi->beginOffscreenFrame(&cb);

        QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
        if (frame == 0)
            u->uploadStaticBuffer(vbuf.get(), vertexData);

        QMatrix4x4 mvp = viewProjection;
        mvp.rotate(rotation, 0, 1, 0);
        u->updateDynamicBuffer(ubuf.get(), 0, 64, mvp.constData());
        rotation += 5.0f;

        u->updateDynamicBuffer(ubuf.get(), 64, 4, &opacity);
        opacity += opacityDir * 0.2f;
        if (opacity < 0.0f || opacity > 1.0f) {
            opacityDir *= -1;
            opacity = qBound(0.0f, opacity, 1.0f);
        }

        cb->beginPass(rt.get(), Qt::green, { 1.0f, 0 }, u);
        cb->setGraphicsPipeline(ps.get());
        cb->setViewport({ 0, 0, 1280, 720 });
        cb->setShaderResources();
        const QRhiCommandBuffer::VertexInput vbufBinding(vbuf.get(), 0);
        cb->setVertexInput(0, 1, &vbufBinding);
        cb->draw(3);
        QRhiReadbackResult readbackResult;
        u = rhi->nextResourceUpdateBatch();
        u->readBackTexture({ tex.get() }, &readbackResult);
        cb->endPass(u);

        rhi->endOffscreenFrame();

        QImage image(reinterpret_cast<const uchar *>(readbackResult.data.constData()),
                     readbackResult.pixelSize.width(),
                     readbackResult.pixelSize.height(),
                     QImage::Format_RGBA8888_Premultiplied);
        if (rhi->isYUpInFramebuffer())
            image = image.mirrored();
        image.save(QString::asprintf("frame%d.png", frame));
    }

    return 0;
}

应用程序的结果是20个PNG图像(frame0.png - frame19.png)。这些图像包含一个在绿色背景上具有良好的透明度的旋转三角形。

期望将顶点和片段着色器处理并打包到.qsb文件中。以下是与Vulkan兼容的GLSL源代码

color.vert

#version 440

layout(location = 0) in vec4 position;
layout(location = 1) in vec3 color;
layout(location = 0) out vec3 v_color;

layout(std140, binding = 0) uniform buf {
    mat4 mvp;
    float opacity;
};

void main()
{
    v_color = color;
    gl_Position = mvp * position;
}

color.frag

#version 440

layout(location = 0) in vec3 v_color;
layout(location = 0) out vec4 fragColor;

layout(std140, binding = 0) uniform buf {
    mat4 mvp;
    float opacity;
};

void main()
{
    fragColor = vec4(v_color * opacity, opacity);
}

要手动编译和转换这些着色器到多个目标(SPIR-V、HLSL、MSL、GLSL)并在运行时生成应用程序加载的.qsb文件,请运行qsb --qt6 color.vert -o color.vert.qsbqsb --qt6 color.frag -o color.frag.qsb。或者,Qt Shader Tools模块提供了CMake的构建系统集成,使用qt_add_shaders() CMake函数,可以在构建时实现相同的功能。

设计基础

QRhi不能直接实例化。相反,使用create()函数。通常删除QRhi实例以释放图形设备。

资源

源自QRhiResource的类(如QRhiBufferQRhiTexture等)封装了零个、一个或多个本机图形资源。此类实例始终通过QRhi的new函数创建,例如,newBuffer()、newTexture()、newTextureRenderTarget()、newSwapChain()。

QRhiBuffer *vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
if (!vbuf->create()) { error(); }
// ...
delete vbuf;

命令缓冲区和延迟命令执行

无论底层图形API的设计和能力如何,所有QRhi后端都实现了一定级别的命令缓冲区。没有任何QRhiCommandBuffer函数直接发布任何原生绑定或绘制命令(例如,glDrawElements)。命令总是在队列中记录,无论是原生的还是由QRhi后端提供的。只有在调用QRhi::endFrame()或QRhi::finish()后,才会提交命令缓冲区,然后才开始执行。

延迟的特性对某些类型的对象有影响。例如,在帧内多次写入动态缓冲区,如果此类缓冲区由宿主可见内存支撑,则所有写入的结果都将对所有命令缓冲区的绘制调用可见,而不考虑动态缓冲区更新与绘制调用之间的关系。

此外,任何用法的QRhiResource子类实例必须在帧内被视为不可变的。在开始为下一帧记录命令之前,先创建所有资源。在帧内重用QRhiResource实例(通过调用create()然后在同一beginFrame - endFrame节中再次引用它)应予以避免,因为它可能会导致意外的结果,具体取决于后端。

一般规则是,所有引用的QRhiResource对象必须保持有效和未修改,直到通过调用endFrame()提交帧。另一方面,在提交帧之后调用destroy或删除QRhiResource总是安全的,无论底层原生资源的状态如何(这些资源可能仍在GPU中使用 - 但这由内部处理)。

与OpenGL等API不同,上传和复制类型的命令不能与绘制命令混合。典型的渲染器将涉及以下类似序列:

  • (re)创建资源
  • 开始帧
  • 记录/发布上传和复制
  • 开始记录渲染通道
  • 记录绘制调用
  • 结束渲染通道
  • 结束帧

通过QRhiResourceUpdateBatch记录复制类型的操作。这些操作通常在beginPass()上提交。

当使用为OpenGL设计的旧版渲染器工作时,迁移到QRhi通常需要从具有单个render步骤(该步骤执行复制和上传、清除缓冲区以及发布绘制调用,全部混合在一起)重新设计为一个明显分开、分两阶段的prepare - render设置,其中render步骤仅开始渲染通道并记录绘制调用,而在prepare步骤中在之前就发生所有资源创建和更新、上传和复制的排队。

当前QRhi不允许自由创建和提交命令缓冲区。这可能在将来在一定程度上得到放宽,尤其是当引入计算支持时,但定义良好的frame-startframe-end点的模型,结合专用、"帧"命令缓冲区,其中frame-end表示呈现,将继续成为主要的工作方式,因为这是最适合Qt各种UI技术的。

线程

QRhi实例及其关联的资源可以创建和使用在任何线程上,但所有使用必须限于该一个单独的线程。当在一个应用程序中将渲染到多个QWindows时,通常建议每个窗口都有一个专用线程和QRhi实例,因为这可以消除由多个窗口呈现引起的意外 throttling问题。从概念上讲,这与Qt Quick场景图有线渲染循环直接使用OpenGL时相同:每个窗口一个线程,每个线程一个QOpenGLContext。转到QRhi后,QOpenGLContext被QRhi替换,使迁移简单直接。

当涉及到外部创建的原生对象,如通过QRhiGles2NativeHandles传递的OpenGL上下文时,应用程序应确保它们不会被其他线程误用。

资源在不同QRhi实例之间不可共享。这是一个有意选择,因为QRhi隐藏了大多数队列、命令缓冲区和资源同步相关任务,并为它们提供了没有API。从多个线程安全高效地并发使用图形资源与这些概念相关联,因此这是一个目前超出范围的话题,但可能在未来引入。

注意:Metal后端要求渲染线程有可用的autorelease池,理想情况下包装渲染循环的每一次迭代。当在主(GUI)线程上进行渲染时,QRhi的用户无需采取任何操作,但当使用单独的专用渲染线程时,这变得很重要。

资源同步

QRhi不公开资源屏障或图像布局转换的API。这种同步在适用的后端(例如,Vulkan)中隐式执行,通过按照必要的跟踪资源使用情况进行。在渲染或计算遍历之前透明地插入缓冲区和图像屏障。

注意:在渲染或计算遍历期间,资源应预期绑定到单一的使用。例如,一个缓冲区可以用作顶点、索引、统一或存储缓冲区,但不能在单个遍历中同时绑定这些用途。但是,如果在创建时在缓冲区中声明了这两个用法,那么将缓冲区用作存储缓冲区在计算遍历中,然后用作渲染遍历中的顶点缓冲区,例如,是完全可以接受的。

注意:在某些情况下,纹理的此规则放宽了,因为即使在同一遍历中,也支持使用相同纹理的不同子资源(通常是不同的mip级别)进行不同访问(一个用于加载,一个用于存储)。

资源重用

从用户的角度来看,QRhiResource在调用QRhiResource::destroy后立即可以重用。除了交换链之外,对已创建的对象调用create()执行隐式destroy()。这提供了一个方便的简捷方式来重用具有不同参数的QRhiResource实例,在底层有一个新的原生图形对象。

重用同一对象的重要性在于,一些对象会引用其他对象:例如,一个 QRhiShaderResourceBindings 可以引用 QRhiBufferQRhiTextureQRhiSampler 实例。如果在后续帧中,其中这些缓冲区需要调整大小或需要更改采样器参数,那么销毁并创建全新的 QRhiBufferQRhiSampler 将会使所有旧实例的引用无效。只需通过 QRhiBuffer::setSize() 或类似方法更改适当的参数,然后调用 QRhiBuffer::create(),一切都将按预期工作,而且根本不需要触摸 QRhiShaderResourceBindings,尽管在底层,QRhiBuffer 可能现在由全新的本地缓冲区支持。

QRhiBuffer *ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
ubuf->create();

QRhiShaderResourceBindings *srb = rhi->newShaderResourceBindings()
srb->setBindings({
    QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf)
});
srb->create();

// ...

// now in a later frame we need to grow the buffer to a larger size
ubuf->setSize(512);
ubuf->create(); // same as ubuf->destroy(); ubuf->create();

// srb needs no changes whatsoever, any references in it to ubuf
// stay valid. When it comes to internal details, such as that
// ubuf may now be backed by a completely different native buffer
// resource, that is is recognized and handled automatically by the
// next setShaderResources().

QRhiTextureRenderTarget 提供了相同的合约:即使渲染目标关联的一个纹理或渲染缓冲区已经重建(通过对其调用 create()),调用 QRhiCommandBuffer::beginPass() 也是安全的。这允许应用程序通过在 QRhiTexture 上设置新的像素大小并调用 create() 来调整纹理的大小,从而在底层创建全新的本地纹理资源,而无需更新 QRhiTextureRenderTarget,因为这将隐式地在 beginPass() 中完成。

池化对象

除了资源之外,还有池化对象,例如,QRhiResourceUpdateBatch。通过类似于 nextResourceUpdateBatchnext 函数来检索实例。在这种情况下,调用者不拥有返回的实例。在这里,唯一有效的操作方法是调用 QRhiResourceUpdateBatch 上的函数,然后将它传递给 QRhiCommandBuffer::beginPass() 或 QRhiCommandBuffer::endPass()。这些函数负责将批处理返回到池中。或者,可以通过调用 QRhiResourceUpdateBatch::release() 将批处理“取消”并返回池中,而不进行任何处理。

因此,典型的模式是

QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch();
// ...
resUpdates->updateDynamicBuffer(ubuf, 0, 64, mvp.constData());
if (!image.isNull()) {
    resUpdates->uploadTexture(texture, image);
    image = QImage();
}
// ...
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
// note the last argument
cb->beginPass(swapchain->currentFrameRenderTarget(), clearCol, clearDs, resUpdates);

交换链特定内容

由于交换链的特殊性质,QRhiSwapChain 具有一些特殊的语义。

  • 它没有 create() 函数,而是有 QRhiSwapChain::createOrResize。反复调用此函数与先调用 QRhiSwapChain::destroy() 然后调用 QRhiSwapChain::createOrResize 不同。这是因为交换链通常有方法来处理缓冲区需要更有效地调整大小的情况,而不是摧毁并从零开始重建。
  • 在使用 QWindow 的底层 QPlatformWindow 以及相关的本地窗口对象被销毁之前,必须通过调用 destroy() 或简单地销毁对象来释放活跃的 QRhiSwapChain。这不应被推迟,因为在本地窗口不再存在时(例如,由于 QPlatformWindow 在接收 QWindow::close 调用时被销毁),释放 swapchain 可能会变得复杂(在某些 API,如 Vulkan 中,这是明令禁止的)。因此,当目标 QWindow 发送 QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed 事件时,必须释放 swapchain。如果该事件在 QWindow 被销毁之前未到达 - 这在使用 QCoreApplication::quit() 时可能会发生 - 那么在事件循环退出后检查 QWindow::handle(),并在非空时(表示底层的本地窗口仍然存在)调用 swapchain 释放。

拥有权

一般规则是没有拥有权转移。使用已经存在的图形设备创建 QRhi 并不意味着 QRhi 获取设备对象的拥有权。类似地,当设备或纹理对象通过 QRhi::nativeHandles() 或 QRhiTexture::nativeTexture() "导出" 时,也不会赋予拥有权。最重要的是,通过结构体中的指针和通过设置器传递指针不会转移拥有权。

故障排除和性能分析

错误报告

QRhi::create() 和资源类的 create() 成员函数(例如,QRhiBuffer::create)等函数通过返回值(分别返回 nullptrfalse)指示失败。当与 QShader 一起工作时,如果传递给函数的数据无法成功反序列化,则 QShader::fromSerialized() 返回一个无效的 QShader(对于该 isValid() 返回 false)。一些函数,特别是 beginFrame(),有时也可能报告“软故障”,例如 FrameOpSwapChainOutOfDate,这并不表示无法恢复的错误,而应被视为“稍后再试”的响应。

警告和错误可能会在任何时候通过 qWarning() 打印到调试输出。因此,始终建议检查应用程序的输出。

可以通过以下日志分类启用额外的调试消息。除非通过 QLoggingCategoryQT_LOGGING_RULES 环境变量显式启用,否则默认情况下不会打印这些分类的消息。为了更好地与 Qt Quick 交互,环境变量 QSG_INFO 还启用了这些调试打印。

  • qt.rhi.general

此外,应用程序可以从成功初始化的 QRhi 中查询 QRhi 后端名称图形设备信息。这可以随后将它们打印给用户或存储在应用程序日志中,即使是在生产构建中,如果需要的话。

调查渲染问题

当渲染结果不符合预期或应用程序出现问题时,始终可以考虑使用本地 3D API 的调试和验证功能。QRhi 本身具有有限的自检,因为复制底层中已存在的大量功能是不合理的。

  • 对于Vulkan,控制Vulkan验证层不在QRhi的范围内,而是可以通过配置

    QVulkanInstance以适当的层来实现。例如,在调用create()函数之前,对QVulkanInstance调用instance.setLayers({ "VK_LAYER_KHRONOS_validation" });。请注意,这假设验证层已经安装并可用,例如来自Vulkan SDK)。默认情况下,QVulkanInstance方便地将Vulkan调试消息重定向到qDebug,这意味着验证消息将以其他Qt警告一样的方式打印。

  • 使用Direct 3D 11和12,可以通过在适当的初始化参数结构中切换enableDebugLayer标志来请求启用调试层的光栅设备。消息将显示在调试输出中,这在Qt Creator的消息面板或使用DebugView等工具中可见。
  • 对于Metal,控制Metal验证不在QRhi的范围内。相反,要启用验证,可以设置环境变量METAL_DEVICE_WRAPPER_TYPE=1并运行应用程序,或将应用程序在XCode中运行。现代XCode和macOS版本可能还有其他设置和环境变量。例如,请参阅此页面

帧捕获和性能

使用QRhi将Qt应用程序渲染到窗口,同时在内部依赖3D API,从窗口和图形管道角度来看,与使用相同3D API的任何其他(非Qt)应用程序没有区别。这意味着涉及3D图形(如游戏)的应用程序的调试和调优工具也适用于此类Qt应用程序。

以下是一些可以提供有关使用QRhi的Qt应用程序(包括基于Qt Quick和Qt Quick 3D的项目)渲染内部结构的见解的工具的示例

  • RenderDoc允许在Windows和Linux上对使用OpenGL、Vulkan、D3D11或D3D12的应用程序进行帧捕获和检查记录的命令以及管道状态。当试图找出为什么3D场景中的某些部分没有按预期显示时,RenderDoc通常是检查管道阶段和相关状态的快速有效方式,并发现丢失或不正确的值。它也是开发Qt本身时积极使用的一种工具。
  • 对于基于NVIDIA的系统,Windows和Linux上Nsight Graphics提供了一种图形调试工具。除了调查帧和管道中的命令外,供应商特定的工具还允许查看定时和硬件性能信息,这是简单的帧捕获无法提供的。
  • 对于基于AMD的系统,可以使用Radeon GPU Profiler深入了解应用程序的渲染和性能。
  • 由于QRhi支持Direct 3D 12,使用PIX,Windows上DirectX 12游戏的性能调优和调试工具,也是一个选项。
  • 在macOS上,可以使用XCode Metal调试器进行帧捕获和进行详细性能调查,并调试着色器。在macOS 13中,也可以通过设置环境变量MTL_HUD_ENABLED=1来启用一个叠加显示,该显示显示任何基于Metal的窗口的帧率和其他信息。

在移动和嵌入式平台上,可能有由GPU或SoC供应商提供的特定于供应商和平台的工具,可用于对使用OpenGL ES或Vulkan的应用程序进行性能分析。

在捕获帧时,请注意可以通过调试标记对对象和命令组进行命名,前提是启用了QRhi的调试标记,并且所使用的图形API支持该功能。要注释命令流,请调用debugMarkBegin(),debugMarkEnd()和/或debugMarkMsg()。这在大型帧和多个渲染通道中尤其有用。在调用create()之前,通过调用setName()来命名资源。

要在应用程序中对CPU和GPU侧进行基本时间测量,可以使用QElapsedTimerQRhiCommandBuffer::lastCompletedGpuTime()。目前,后者仅在选择的图形API中可用,并需要通过QRhi::EnableTimestamps标志进行激活。

资源泄露检查

在没有正确销毁由其创建的所有缓冲区、纹理和其他资源的情况下销毁QRhi对象时,如果在调试构建或当环境变量QT_RHI_LEAK_CHECK设置为非零值时,将在调试输出中打印有关此问题的警告。这是一种发现应用程序渲染逻辑中资源处理周围设计问题的简单方法。然而,请注意,某些平台和底层图形API可能也会执行自己的分配和资源泄露检测,Qt对其没有直接控制。例如,在使用Vulkan时,迭代器内存分配器可能在QRhi之前销毁拥有图形内存分配的资源时在调试构建中引发失败的断言。此外,当启用验证层时,Vulkan验证层将发出有关未释放本机图形资源的警告。类似地,如果应用程序没有按正确顺序销毁QRhi及其资源,Direct 3D可能会在打印关于未释放的COM对象时发出警告。

另请参阅RHI窗口示例QRhiCommandBufferQRhiResourceUpdateBatchQRhiShaderResourceBindingsQShaderQRhiBufferQRhiTextureQRhiRenderBufferQRhiSamplerQRhiTextureRenderTargetQRhiGraphicsPipelineQRhiComputePipelineQRhiSwapChain

成员类型文档

enum QRhi::BeginFrameFlag
flags QRhi::BeginFrameFlags

QRhi::beginFrame()的标志值

BeginFrameFlags类型是QFlags<BeginFrameFlag>的类型定义。它存储 BeginFrameFlag 值的 OR 组合。

enum QRhi::EndFrameFlag
flags QRhi::EndFrameFlags

QRhi::endFrame()的标志值

常量描述
QRhi::SkipPresent1 << 0表示不应排队或不在swapBuffers中对命令进行调用。这样就不会显示任何图像。不建议生成设置此标志的所有帧的多帧(例如,对于基准测试目的 - 但是请注意,后端在等待命令完成而不显示时可能表现不同,因此结果在它们之间是不可比较的)

EndFrameFlags 类型是 QFlags<EndFrameFlag> 的typedef。它存储了 EndFrameFlag 值的或组合。

枚举 QRhi::Feature

标志值表示当前使用的后端支持哪些功能。

常量描述
QRhi::MultisampleTexture1表示支持样本计数大于1的纹理。实际上,对于版本低于3.1的OpenGL ES和低于3.0的OpenGL,这个特性将不被支持。
QRhi::MultisampleRenderBuffer2表示支持样本计数大于1的渲染缓冲区。实际上,对于OpenGL ES 2.0,以及除非存在相关扩展,否则也可能不支持OpenGL 2.x。
QRhi::DebugMarkers3表示支持调试标记组(以及所以 QRhiCommandBuffer::debugMarkBegin())。
QRhi::Timestamps4表示支持命令缓冲区时间戳。对 QRhiCommandBuffer::lastCompletedGpuTime相关。这通常可以在Metal、Vulkan、Direct 3D 11和12以及版本3.3或更新的OpenGL上下文中得到支持。然而,随着一些API字节查询支持的技术上是可选的,因此不能保证此功能始终在它们的每个实现中始终得到支持。
QRhi::Instancing5表示支持实例绘制。实际上,对于OpenGL ES 2.0和OpenGL 3.2或更早版本,此功能将不被支持。
QRhi::CustomInstanceStepRate6表示支持非1的实例步率。实际上,与OpenGL一起运行始终不会支持此功能。此外,在没有 VK_EXT_vertex_attribute_divisor 的 Vulkan 1.0 上运行也将导致报告此功能为假。
QRhi::PrimitiveRestart7表示在遇到索引值0xFFFF(IndexUInt16)或0xFFFFFFFF(IndexUInt32)时启用基本形的组装重新启动,对于某些基本形拓扑至少。 QRhi 将尝试启用所有后端,但在某些情况下可能不支持。由于某些API使用固定索引的基本形重新启动始终开启,因此无法动态控制基本形重新启动。无论此功能报告为支持与否,都要假定功能报告受到支持时上述提到的索引值可能根据拓扑进行处理。只要此功能报告为支持,只有两个拓扑可以保证基本形重新启动在各种后端中的行为相同,即LineStripTriangleStrip
QRhi::NonDynamicUniformBuffers8表示支持使用UniformBuffer的用途和类型ImmutableStatic创建缓冲区。当报告为不支持时,必须将统一(常数)缓冲区创建为Dynamic。(这也是推荐做法)
QRhi::NonFourAlignedEffectiveIndexBufferOffset9表示支持不是4字节对齐的有效索引缓冲区偏移量。如果不支持,尝试使用非对齐偏移量发出 drawIndexed()可能会导致未指定的行为。特别是对于Metal,这将报告为不支持。
QRhi::NPOTTextureRepeat10表示支持非2的幂大小的纹理Repeat包裹模式和mipmap过滤模式。
QRhi::RedOrAlpha8IsRed11表示RED_OR_ALPHA8格式映射到单分量8位格式。对于所有后端(除了OpenGL,当使用OpenGL ES或非核心配置文件上下文时),都是这种情况。在此情况下,使用的是GL_ALPHA,一种单分量8位alpha格式。使用特殊纹理格式允许有一个创建纹理的单一路径代码,由后端决定实际格式,而功能标志可用于选择用于采样纹理的适当着色器变体。
QRhi::ElementIndexUint12表示索引缓冲区支持32位无符号整数元素。实际上,在普通OpenGL ES 2.0实现上运行且没有必要的扩展时,这不是这种情况。当为false时,索引缓冲区只支持16位无符号元素。
QRhi::Compute13表示支持计算着色器、图像加载/存储和存储缓冲区。低于4.3的OpenGL和低于3.1的OpenGL ES没有任何计算支持。
QRhi::WideLines14表示支持宽度不是1的线。当报告为不支持时,图形管道状态上设置的线宽会被忽略。由于某些后端(D3D11、D3D12、Metal)的限制,这总是为false。在Vulkan中,该值取决于实现。在OpenGL中,核心配置文件上下文中不支持宽线。
QRhi::VertexShaderPointSize15表示考虑到顶点着色器中通过gl_PointSize设置的点大小。当报告为不支持时,不支持使用除1以外大小的绘制点。在此情况下,仍在着色器中设置gl_PointSize仍然有效,但会被忽略。注意,一些API(例如Metal、Vulkan)要求在绘制点时显式设置点大小,即使在大小为1的情况下,因为它们不会自动默认为1。
QRhi::BaseVertex16表示drawIndexed()支持vertexOffset参数。当报告为不支持时,索引绘制中的vertexOffset值会被忽略。实际上,这个功能在低于3.2的OpenGL和OpenGL ES版本以及旧iOS设备上的Metal,包括iOS模拟器中可能不会被支持。
QRhi::BaseInstance17表示实例绘制命令支持firstInstance参数。当报告为不支持时,会忽略firstInstance值,并且实例ID从0开始。实际上,这个功能可能在OpenGL以及旧iOS设备上的Metal中不被支持,包括iOS模拟器。
QRhi::TriangleFanTopology18表示QRhiGraphicsPipeline::setTopology()支持QRhiGraphicsPipeline::TriangleFan。实际上,这个功能可能在Metal和Direct 3D 11/12中不被支持。
QRhi::ReadBackNonUniformBuffer19表示对于使用除UniformBuffer以外的不同用途的QRhiBuffer实例,支持读取缓冲区内容。实际上,这个特性可能在OpenGL ES 2.0中不被支持。
QRhi::ReadBackNonBaseMipLevel20表示在回读纹理内容时支持指定非0等级。如果不支持,则指定QRhiReadbackDescription中非零等级会导致返回全部为零的图像。实际上,这个功能可能在OpenGL ES 2.0中不被支持。
QRhi::TexelFetch21表示在着色器中可以使用texelFetch()和textureLod()。实际上,在OpenGL ES 2.0和OpenGL 2.x上下文中可能报告为不支持,因为这些函数在GLSL 100 es和版本低于130之前不支持。
QRhi::RenderToNonBaseMipLevel22表示在创建一个QRhiTextureRenderTarget时,如果将QRhiTexture指定为其颜色附件,则支持指定非0级的mip级别。如果不支持,当目标mip级别不是零时,create()将失败。实际上,在OpenGL ES 2.0中,此功能将不受支持。
QRhi::IntAttributes23表示支持在着色器管线中指定有符号和无符号整型类型的输入属性。如果不支持,build()将成功,但会显示警告消息,目标属性的值将损坏。实际上,在OpenGL ES 2.0和OpenGL 2.x中,此功能将不受支持。
QRhi::ScreenSpaceDerivatives24表示在着色器中支持诸如dFdx()、dFdy()和fwidth()等函数。实际上,在未安装GL_OES_standard_derivatives扩展的OpenGL ES 2.0中,此功能不受支持。
QRhi::ReadBackAnyTextureFormat25表示预期读取任何QRhiTexture::Format格式的纹理内容都可以正常工作。除了OpenGL之外的后端可以预期会返回此功能为true。当报告为false时,通常会在OpenGL中发生,仅保证以下格式支持读取:QRhiTexture::RGBA8QRhiTexture::BGRA8。此外,在OpenGL中(但不在OpenGL ES中),支持以每组件1个字节的格式QRhiTexture::R8QRhiTexture::RED_OR_ALPHA8 进行读取。OpenGL下,只要有实现支持这些格式,浮点格式QRhiTexture::RGBA16F和RGBA32F也可能工作,但QRhi不能保证,如该标志所示。
QRhi::PipelineCacheDataLoadSave26表示pipelineCacheData()和setPipelineCacheData()函数是可用的。如果不支持,这些函数不会执行任何操作,检索到的blob始终为空,因此无法从检索和在应用程序的后续运行中重新加载管道缓存内容中期待任何好处。
QRhi::ImageDataStride27表示支持在纹理上传中指定原始图像数据的自定义步长(行长度)。如果不支持(当底层API为没有GL_UNPACK_ROW_LENGTH支持的OpenGL ES 2.0时可能发生),则不得使用QRhiTextureSubresourceUploadDescription::setDataStride()。
QRhi::RenderBufferImport28表示支持QRhiRenderBuffer::createFrom()。对于大多数图形API,这并不合理,因为QRhiRenderBuffer内部封装了纹理对象,就像QRhiTexture一样。然而,在使用OpenGL时,渲染缓冲区对象作为API中独立的对象类型存在,在特定环境中(例如,当想将渲染缓冲区对象与EGLImage对象相关联时)允许将现有的OpenGL渲染缓冲区对象封装在一个QRhiRenderBuffer中是重要的。
QRhi::ThreeDimensionalTextures29表示支持3D纹理。实际上,在OpenGL和OpenGL ES版本低于3.0的情况下,此功能将不受支持。
QRhi::RenderTo3DTextureSlice30表示支持将渲染到3D纹理的切片。这可能会因为依赖于VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT(这是一个Vulkan 1.1功能)而不被Vulkan 1.0支持。
QRhi::TextureArrays31表示支持纹理数组,并且QRhi::newTextureArray()是可用的。请注意,即使不支持纹理数组,纹理数组仍然可用,因为这些是两个独立的功能。
QRhi::Tessellation32表示支持细分控制阶段和评估阶段。当报告为支持时,QRhiGraphicsPipeline的拓扑结构可以设置为Patches,控制点数量可以通过setPatchControlPointCount()设置,并且在QRhiShaderStage列表中可以指定细分控制和评估着色器。细分着色器在API之间可能存在可移植性问题(例如,由于Hull着色器的结构,将GLSL/SPIR-V转换为HLSL存在问题,而Metal使用的细分管道与其它不同),因此即使基本功能在所有底层API中已实现,仍可能出现意外问题。对于Direct 3D特别地,手写的HLSL Hull和Domain着色器必须分别注入到QShader的细分控制和评估阶段中,因为qsb无法从SPIR-V生成这些着色器。请注意,应避免使用等高线细分,因为并非所有后端都支持它。后端之间可移植的最大补丁控制点数为32。
QRhi::GeometryShader33表示支持几何着色阶段。当支持时,可以在QRhiShaderStage列表中指定几何着色器。几何着色器在QRhi中被视为试验性特性,并且仅预期与Vulkan、Direct 3D、OpenGL (3.2+)和OpenGL ES (3.2+)一起支持,前提是在运行时报告为支持。几何着色器在API之间存在可移植性问题,因此无法保证通用解决方案。而永远不会在Metal中支持。而对于Direct 3D,由于qsb无法从SPIR-V生成,必须将手写的HLSL几何着色器注入到每个QShader的几何阶段。
QRhi::TextureArrayRange34表示对于纹理数组,可以指定一个暴露给着色器的范围。通常所有数组层都暴露出来,由着色器选择层(通过将第三个坐标传递给当采样sampler2DArray时的texture())。当支持时,在创建导入原生纹理之前调用QRhiTexture::setArrayRangeStart()和QRhiTexture::setArrayRangeLength()具有效果,并且仅选择数组的指定范围。在特殊情况下,例如使用加速视频解码和Direct 3D 11时,这是必要的,因为具有D3D11_BIND_DECODERD3D11_BIND_SHADER_RESOURCE的纹理数组只能作为着色器资源使用,如果只选择单个数组层。请注意,所有这些都仅在将纹理用作QRhiShaderResourceBinding::SampledTextureQRhiShaderResourceBinding::Texture着色器资源时适用,并且与图像加载/存储不兼容。这个特性仅适用于某些后端,因为它不太适合所有图形API,并且其主要目的是提供对特殊情况的支援。实际上,这项功能可以预期仅在Direct3D 11/12和Vulkan中得到支持。
QRhi::NonFillPolygonMode35表示支持为QRhiGraphicsPipeline设置非默认填充的PolygonMode。将模式更改为线的常见用例是为了获取线框渲染。然而,这并不是核心OpenGL ES特性,Vulkan以及一些移动GPU也可能不提供这一功能。
QRhi::OneDimensionalTextures36表示支持1D纹理。实际上,在OpenGL ES上可能不支持此功能。
QRhi::OneDimensionalTextureMipmaps37表示支持生成1D纹理米普(mipmap)。在实际操作中,如果后端不支持OneDimensionalTextures、Metal和Direct 3D 12,则此功能可能不可用。
QRhi::HalfAttributes38表示支持使用半精度(16位)浮点类型指定着色器管道的输入属性。如果不支持,build() 将成功,但会显示警告消息,目标属性的值将被破坏。在实际操作中,此功能可能在某些OpenGL ES 2.0和OpenGL 2.x的实现中不受支持。注意,虽然Direct3D 11/12支持半精度输入属性,但不支持half3类型。D3D后端将half3属性作为half4传递。为确保跨平台兼容性,half3输入应填充至8个字节。
QRhi::RenderToOneDimensionalTexture39表示支持1D纹理渲染目标。在实际操作中,如果后端不支持OneDimensionalTextures和Metal,则此功能可能不可用。
QRhi::ThreeDimensionalTextureMipmaps40表示支持生成3D纹理米普(mipmap)。在实际操作中,使用Direct 3D 12时不支持此功能。
QRhi::MultiView41表示支持多视角,例如参见VK_KHR_multiview。在使用OpenGL ES 2.0、Direct 3D 11和没有GL_OVR_multiview2的OpenGL (ES)实现中,此功能将不受支持。在Vulkan 1.1及更高版本和Direct 3D 12中,通常支持多视角。当报告为支持时,创建一个带有QRhiColorAttachmentQRhiTextureRenderTarget,该QRhiColorAttachment引用一个纹理数组并设置了multiViewCount,可以启用记录使用多视角渲染的渲染通道。此外,用于该渲染通道的任何QRhiGraphicsPipeline必须设置相同的视图数。请注意,多视角仅在与其他2D纹理数组结合使用时可用。它不能用于优化渲染为单独的纹理(例如,两个,一个用于左眼,一个用于右眼)。多视角渲染通道的目标始终是纹理数组,自动渲染到对应每个视图的层(数组元素)。因此,此功能暗示了TextureArrays。多视角渲染不支持与细分或几何着色器结合使用。有关多视角渲染的更多信息,请参阅QRhiColorAttachment::setMultiViewCount()。此枚举值是在Qt 6.7中引入的。

enum QRhi::Flag
flags QRhi::Flags

描述要启用哪些特殊功能。

常量描述
QRhi::EnableDebugMarkers1 << 0启用调试标记组。如果没有此帧调试功能(例如,在外部GPU调试工具中使调试组和自定义资源名称可见)将不可用,并且诸如QRhiCommandBuffer::debugMarkBegin()之类的函数将变为空操作。避免在生产构建中启用,因为它可能涉及轻微的性能影响。当QRhi::DebugMarkers功能报告为不受支持时不产生作用。
QRhi::EnableTimestamps1 << 3启用GPU时间戳收集。未设置时,QRhiCommandBuffer::lastCompletedGpuTime()始终返回0。仅在需要时启用此功能,因为可能涉及少量额外的工作(例如,时间戳查询),这取决于底层图形API。当QRhi::Timestamps功能报告为不受支持时不产生作用。
QRhi::PreferSoftwareRenderer1 << 1表示后端应该首选选择能够在CPU上通过软件渲染的适配器或物理设备。例如,在使用Direct3D时,通常有一个带有DXGI_ADAPTER_FLAG_SOFTWARE的“基本渲染驱动器”适配器可用。设置此标志将请求后端选择此适配器而不是任何其他适配器,除非其他后端特定方式已强制指定特定适配器。在使用Vulkan时,这映射为优先使用具有VK_PHYSICAL_DEVICE_TYPE_CPU的物理设备。当不可用,或无法确定适配器/设备是否基于软件时,此标志将被忽略。它也可能在与没有概念和手段枚举适配器/设备的图形API一起时被忽略。
QRhi::EnablePipelineCacheDataSave1 << 2启用获取管道缓存内容,如果适用。当未设置时,pipelineCacheData()将始终返回一个空的blob。对于不支持检索和恢复管道缓存内容的后端,该标志没有效果,并且序列化的缓存数据始终保持为空。该标志提供了一个可选机制,因为对于某些后端而言,维护相关数据结构的成本并不小。在使用Vulkan时,此功能直接映射到VkPipelineCache、vkGetPipelineCacheData和VkPipelineCacheCreateInfo::pInitialData。在Direct3D 11中,实际上没有管道缓存,但是HLSL→DXBC编译的结果可以通过此机制进行存储和序列化/反序列化。这允许在包含HLSL源代码的着色器(而不是离线预编译的字节码)的后续应用程序运行中跳过耗时的D3DCompile()。如果有大量HLSL源代码编译发生,这可以为启动和加载时间提供巨大的提升。在使用OpenGL时,“管道缓存”通过检索和加载着色器程序二进制文件(如果驱动程序支持)来模拟。在OpenGL中,Qt提供了用于着色器/程序二进制的基于磁盘的缓存机制。当设置此标志时,可能禁用写入这些缓存,因为将程序二进制存储到多个缓存中并不合理。
QRhi::SuppressSmokeTestWarnings1 << 4表示在与此相关的后端中,某些非致命的QRhi::create()失败不应产生qWarning()调用。例如,在使用D3D11时,传递此标志将使多个警告信息(由于QRhi::create()失败而出现)在常用qt.rhi.general记录类别下成为分类的调试打印。这可以由提供了回退逻辑的引擎,如Qt Quick,使用,即它们通过使用不同的标志集(如,PreferSoftwareRenderer)重试调用create(),以隐藏当第一个create()尝试失败时要打印的无条件警告。

Flags类型是QFlags<Flag>的typedef。它存储了Flag值的或组合。

enum QRhi:FrameOpResult

描述了可能存在软失败的操作的最终结果。

常量描述
QRhi::FrameOpSuccess0成功
QRhi::FrameOpError1未指定错误
QRhi::FrameOpSwapChainOutOfDate2交换链处于不一致的状态。这可以通过稍后尝试重复操作(例如,beginFrame())来恢复。
QRhi::FrameOpDeviceLost3图形设备丢失。可以通过释放和重新初始化所有由本地图形资源支持的对象来尝试重复操作(例如,beginFrame())来恢复。请参阅isDeviceLost()。

enum QRhi:Implementation

描述了哪个特定于图形API的后端被QRhi实例使用。

常量
QRhi::Null0
QRhi::Vulkan1
QRhi::OpenGLES22
QRhi::D3D113
QRhi::D3D125
QRhi::Metal4

枚举 QRhi::ResourceLimit

描述要查询的资源限制。

常量描述
QRhi::TextureSizeMin1最小纹理宽度和高度。通常为 1。最小纹理大小能够优雅处理,意味着尝试创建一个空大小的纹理时,将创建具有最小大小的纹理。
QRhi::TextureSizeMax2最大纹理宽度和高度。这取决于图形 API,有时也取决于平台或实现。通常值为 4096 - 16384。尝试创建比这更大的纹理可能会失败。
QRhi::MaxColorAttachments3在支持多个渲染目标的情况下,QRhiTextureRenderTarget 的最大颜色附件数量。当不支持 MRT 时,值为 1。否则,这通常是 8,但请注意,OpenGL 仅强制要求最低值为 4,这确实是某些 OpenGL ES 实现提供的值。
QRhi::FramesInFlight4后端可能保持的“正在飞行”的帧数。对于类似 Vulkan 或 Metal 这样的后端,当新的帧开始时,发现 CPU 已经比 GPU 领先 N - 1 个帧(因为编号 current - N 的命令缓冲区尚未完成)时,QRhi 有责任进行阻塞。值 N 是从这里返回的,通常为 2。这对于集成图形 API 直接进行的渲染的应用程序可能很重要,因为此类渲染代码可能需要对资源,如缓冲区,执行双缓冲(如果值为 2)。当前的帧槽索引(运行 0、1、..、N-1,然后循环)可以从 QRhi::currentFrameSlot() 获取。对于图形 API 不提供此类低级控制的后端,该值为 1。请注意,即使此值为 1,仍然可能发生管线化(例如,某些后端,如 D3D11,被设计为尝试启用此功能,例如,通过使用不会阻止管线的统一缓冲区的更新策略),但这不是由 QRhi 控制的,因此在此 API 中不会反映。
QRhi::MaxAsyncReadbackFrames5在开始新帧后,异步纹理或缓冲区读取操作在提交的 已提交 帧数(包括包含读取的帧)后保证完成。
QRhi::MaxThreadGroupsPerDimension6可以调度的计算工作/线程组的最多数量。实际上,它是 QRhiCommandBuffer::dispatch() 的参数的最大值。通常值为 65535。
QRhi::MaxThreadsPerThreadGroup7单个局部工作组中调用的最大次数,或者用其他术语来说,是线程组中的最大线程数。实际上它是计算着色器中 local_size_xlocal_size_ylocal_size_z 的乘积的最大值。典型值是 128、256、512、1024 或 1536。需要注意的是,OpenGL ES 和 Vulkan 仅指定 128 为实现所需的最小极限限制。虽然对于 Vulkan 来说不常见,但某些用于移动/嵌入式设备的 OpenGL ES 3.1 实现仅支持规范规定的最小值。
QRhi::MaxThreadGroupX8工作/线程组在 X 维度的最大大小。实际上它是计算着色器中 local_size_x 的最大值。通常为 256 或 1024。
QRhi::MaxThreadGroupY9工作/线程组在 Y 维度的最大大小。实际上它是计算着色器中 local_size_y 的最大值。通常为 256 或 1024。
QRhi::MaxThreadGroupZ10工作/线程组在Z方向上的最大尺寸。实际上是在计算着色器中local_size_z的最大值。通常为64或256。
QRhi::TextureArraySizeMax11最大纹理数组大小。通常在256 - 2048的范围内。尝试创建包含更多元素的光栅纹理数组将可能会失败。
QRhi::MaxUniformBufferRange12一次可从统一缓冲区暴露的字节数。在OpenGL ES 2.0和3.0实现中,这个值可能低至3584字节(224个四分量,每个分量32位)。在其他地方,该值通常是16384(1024个vec4)或65536(4096个vec4)。
QRhi::MaxVertexInputs13传入顶点着色器的输入属性数量。在QRhiVertexInputAttribute中的位置必须在范围[0, MaxVertexInputs-1]内。在使用OpenGL ES 2.0时,这个值可能低至8。在其他地方,典型值是16、31或32。
QRhi::MaxVertexOutputs14顶点着色器输出的最大数量(4分量向量out变量)。在使用OpenGL ES 2.0时,这个值可能低至8;在使用OpenGL ES 3.0和一些Metal设备时,为15;在其他地方,典型值是32。

成员函数文档

[noexcept] QRhi::~QRhi()

析构函数。销毁后端并释放资源。

void QRhi::addCleanupCallback(const QRhi::CleanupCallback &callback)

注册一个回调,当QRhi被销毁或调用runCleanup()时将被调用。

回调将在图形资源仍然可用时运行,因此这为应用程序提供了一次干净地释放属于QRhiQRhiResource实例的机会。这对于管理存储在cache类型对象中的资源生命周期尤其有用,其中缓存保留QRhiResources或包含QRhiResources的对象。

另请参阅runCleanup()和~QRhi

void QRhi::addCleanupCallback(const void *key, const QRhi::CleanupCallback &callback)

这是一个重载函数。

注册回调,该回调将在QRhi被销毁或调用runCleanup()时被调用。这个重载函数接受一个不透明的指针key,用于确保给定的回调仅注册(并调用)一次。

另请参阅removeCleanupCallback

QRhi::Implementation QRhi::backend() const

返回此QRhi的后端类型。

const char *QRhi::backendName() const

返回此QRhi的后端类型字符串。

[static] const char *QRhi::backendName(QRhi::Implementation impl)

返回后端impl的友好名称,通常是使用中的3D API的名称。

QRhi::FrameOpResult QRhi::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags = {})

启动一个新的帧,目标是下一个可用的swapChain缓冲区。

一个帧由资源更新和一个或多个渲染和计算遍历组成。

flags可以指示某些特殊情况。

使用交换链将渲染到一个QWindow的高级模式

在成功时返回QRhi::FrameOpSuccess,或在失败时返回另一个QRhi::FrameOpResult值。其中一些应被视为软错误,即“稍后再尝试”:当返回QRhi::FrameOpSwapChainOutOfDate时,应通过调用QRhiSwapChain::createOrResize()来调整交换链的大小或更新它。然后应用程序应尝试生成一个新的帧。QRhi::FrameOpDeviceLost表示图形设备已丢失,但这也可能通过释放所有资源(包括QRhi本身)然后重新创建所有资源而恢复。有关更多信息,请参见isDeviceLost()。

另请参阅 endFrame(),beginOffscreenFrame()和isDeviceLost()。

QRhi::FrameOpResult QRhi::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags = {})

启动一个新的后台帧。提供适合在cb中记录渲染命令的命令缓冲区。flags用于指示某些特殊情况,就像在beginFrame中一样。

注意: 存储到*cb中的QRhiCommandBuffer不由调用者所有。

没有交换链的渲染也是可能的。典型的用法是在完全离屏的应用程序中使用它,例如通过渲染和读取回而不显示窗口来生成图像序列。

你也可以在屏幕应用程序中使用(所以beginFrameendFrame,beginOffscreenFrame,endOffscreenFramebeginFrame等等)也是可能的,但这会降低并行性,因此应很少这样做。

离屏帧不允许CPU在GPU仍在处理前一个帧时生成另一个帧。这有一个副作用,即如果已安排读取回,则一旦返回endOffscreenFrame(),则可以保证结果可用。而对于针对交换链的帧来说并非如此:在那里GPU的利用率可能会更高,但是与应用程序一起使用读取回操作需要更多的注意,因为与endOffscreenFrame不同,endFrame不会保证读取回的结果在那时可用。

没有交换链渲染帧并随后读取帧内容的骨架可能看起来像以下内容

QRhiReadbackResult rbResult;
QRhiCommandBuffer *cb;
rhi->beginOffscreenFrame(&cb);
cb->beginPass(rt, colorClear, dsClear);
// ...
u = nextResourceUpdateBatch();
u->readBackTexture(rb, &rbResult);
cb->endPass(u);
rhi->endOffscreenFrame();
// image data available in rbResult

另请参阅 endOffscreenFrame() 和 beginFrame()。

QMatrix4x4 QRhi::clipSpaceCorrMatrix() const

返回一个矩阵,允许应用程序在没有考虑活动 QRhi 后端的情况下继续使用针对 OpenGL 的顶点数据和你视图投影矩阵(例如,由 QMatrix4x4::perspective() 生成的那些)。

在一个典型的渲染器中,使用 this_matrix * mvp 代替仅仅使用 mvp,可以直接使用向上为Y的顶点数据以及深度范围为 0 - 1 的视口,而无需考虑运行时将使用什么后端(以及所以的图形接口)。这样就可以避免根据 isYUpInNDC() 和 isClipDepthZeroToOne() 的分支(尽管当实现某些高级图形技术时可能需要这样的逻辑)。

有关从 Vulkan 视角讨论这个主题,请参阅这篇页面

[静态] QRhi *QRhi::create(QRhi::Implementation impl, QRhiInitParams *params, QRhi::Flags flags = {}, QRhiNativeHandles *importDevice = nullptr)

返回一个新的 QRhi 实例,其图形 API 使用指定 impl 的后端,以及指定的 flags

params 必须指向 QRhiInitParams 的一个后端特定子类的实例,例如,QRhiVulkanInitParamsQRhiMetalInitParamsQRhiD3D11InitParamsQRhiD3D12InitParamsQRhiGles2InitParams。参见这些类以获取创建 QRhi 的示例。

设计上,QRhi 不实现任何回退逻辑:如果指定的 API 无法初始化,create() 会失败,后端会在调试输出中打印警告。然而,QRhi 的客户端,例如 Qt Quick,可能会提供额外的逻辑,允许回退到所请求的不同的 API,具体取决于平台。如果只是为了测试在稍后调用 create() 时的初始化是否成功,最好使用 probe() 而不是 create(),因为与 create() 不同,create() 会执行完整的基础设施初始化,如果在创建后立即丢弃该 QRhi 实例,这是一种浪费。

importDevice 允许使用已经存在的图形设备,而无需 QRhi 创建自己的。当非空时,此参数必须指向 QRhiNativeHandles 的一个子类实例:QRhiVulkanNativeHandlesQRhiD3D11NativeHandlesQRhiD3D12NativeHandlesQRhiMetalNativeHandlesQRhiGles2NativeHandles。确切细节和语义取决于后端和底层图形 API。

另请参阅probe

int QRhi::currentFrameSlot() const

在录制帧时返回当前帧槽的索引。在非活动帧中调用时未定义(也就是当 isRecordingFrame() 为 false 时)。

在像 Vulkan 或 Metal 这样的后端中,当启动新帧并发现 CPU 已经比 GPU 领先 FramesInFlight - 1 个帧时,QRhi 后端的职责是阻塞(因为帧号 current - FramesInFlight 中提交的命令缓冲区尚未完成)。

那些在帧与帧之间容易发生变化的资源(例如,类型为 QRhiBuffer::Dynamic 的原生动态缓冲区的后台对象)存在多个版本,这样每一帧都可以在处理前一个帧的同时提交,从而在自己的副本上工作,因此避免了在准备帧时阻塞管道的需求。(应该不允许访问 GPU 上可能仍在使用的资源的内容,但仅仅等待前一个帧完成会降低 GPU 利用率,最终影响性能和效率。)

从概念上看,这与某些 C++ 容器和其他类型使用的写时复制方案有些相似。这也可能与 OpenGL 或 Direct3D 11 内部针对某些类型对象执行的实现相似。

在实践中,这种双重(或三重)缓冲资源是通过在 QRhiResource 后面有固定数量的本地资源(如,VkBuffer)slot 来在 Vulkan、Metal 以及类似的 QRhi 后端中实现的。然后可以通过帧槽索引进行索引,该索引从 0 开始,到 FramesInFlight-1 结束,然后回绕。

所有这些对 QRhi 的用户都是透明的。然而,可能需要将直接使用图形 API 完成的渲染集成的应用程序也进行类似的图形资源双重或三重缓冲。这可以通过知道最大并发帧数(可通过 resourceLimit() 获取)和当前帧(槽)索引(由该函数返回)来实现。

另请参阅 isRecordingFrame(),beginFrame() 和 endFrame

QRhiDriverInfo QRhi::driverInfo() const

返回由成功初始化的 QRhi 实例使用的图形设备的元数据。

QRhi::FrameOpResult QRhi::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags = {})

结束、提交并显示在最后一个 beginFrame() 中启动的 swapChain 上的帧。

双重(或三重)缓冲由 QRhiSwapChainQRhi 内部管理。

flags 可选地用于以某些方式更改行为。传递 QRhi::SkipPresent 将跳过排队 Present 命令或调用 swapBuffers。

在成功时返回QRhi::FrameOpSuccess,或在失败时返回另一个QRhi::FrameOpResult值。其中一些应被视为软错误,即“稍后再尝试”:当返回QRhi::FrameOpSwapChainOutOfDate时,应通过调用QRhiSwapChain::createOrResize()来调整交换链的大小或更新它。然后应用程序应尝试生成一个新的帧。QRhi::FrameOpDeviceLost表示图形设备已丢失,但这也可能通过释放所有资源(包括QRhi本身)然后重新创建所有资源而恢复。有关更多信息,请参见isDeviceLost()。

另请参阅 beginFrame() 和 isDeviceLost

QRhi::FrameOpResult QRhi::endOffscreenFrame(QRhi::EndFrameFlags flags = {})

结束、提交并等待离屏帧。

flags 目前未使用。

另请参阅 beginOffscreenFrame

QRhi::FrameOpResult QRhi::finish()

等待图形队列(适用时)上的任何工作完成,然后执行所有延迟操作,如完成读取和资源释放。可以在帧内和帧外调用此函数,但不能在传递过程中调用。在帧内,它意味着将所有工作提交到命令缓冲区。

注意:避免使用此函数。可能需要它的一种情况是,当在一个基于交换链的帧中排队的读取回传的结果需要在固定的给定点时获得,因此希望等待结果。

bool QRhi::isClipDepthZeroToOne() const

如果底层图形API在裁剪空间中使用深度范围[0, 1],则返回true

实际上,这对于OpenGL来说始终为false,因为OpenGL使用投影后的深度范围[-1, 1]。(不要与由glDepthRange()控制的NDC到窗口映射混淆,它使用范围[0, 1],除非被QRhiViewport覆盖)在一些OpenGL版本中可以使用glClipControl()来更改此设置,但QRhi的OpenGL后端不使用该函数,因为它在OpenGL ES或低于4.5的OpenGL版本中不可用。

注意:clipSpaceCorrMatrix()在其返回的矩阵中包含相应的调整。因此,许多QRhi用户不需要采取任何进一步的措施,除了将他们的投影矩阵与clipSpaceCorrMatrix()预先相乘。然而,一些图形技术,例如某些类型的阴影映射,需要处理和输出着色器中的深度值。这些将需要查询并考虑到该函数的值。

bool QRhi::isDeviceLost() const

如果图形设备丢失,则返回true。

设备丢失通常在beginFrameendFrameQRhiSwapChain::createOrResize中检测到,具体取决于后端和底层原生API。最常见的是endFrame,因为在那里进行显示。在一些后端中,QRhiSwapChain::createOrResize()也可以失败,因为这可能是设备丢失。因此,此函数提供了作为一种通用方式来检查之前的操作是否检测到设备丢失。

当设备丢失时,不应通过QRhi执行任何进一步的操作。相反,应释放所有QRhi资源,然后销毁QRhi。然后可以尝试创建一个新的QRhi。如果成功,必须重新初始化所有图形资源。如果不成功,稍后再试,重复进行。

虽然简单应用程序可能决定不考虑设备丢失,但通常使用的桌面平台上,设备丢失可能由于多种原因发生,包括物理断开图形适配器、禁用设备或驱动程序、卸载或升级图形驱动程序,或由于导致图形设备重置的错误。其中一些可能在完全正常的情况下发生,例如,将图形驱动程序升级到新版本是任何Qt应用程序运行时可能发生的一项常见任务。用户完全有可能期待应用程序能够生存下来,即使应用程序正在积极使用OpenGL或Direct3D等API。

Qt 本身基于 QRhi 构建的框架,如 Qt Quick,在设备丢失发生时可以期待能够处理并采取适当的措施。如果图形资源(如纹理和缓冲区)的数据仍然在 CPU 端可用,这种事件在应用级别可能根本不明显,因为图形资源可以无缝重新初始化。然而,直接与 QRhi 共同工作的应用和库应该准备自己检查和处理设备丢失的情况。

注意:使用 OpenGL,应用程序可能需要通过在 QOpenGLContext 上设置 QSurfaceFormat::ResetNotification 来选择加入上下文重置通知。这通常通过在 QRhiGles2InitParams::format 中启用标志来完成。但是请注意,在没有设置此标志的情况下,某些系统可能会生成上下文重置的情况。

bool QRhi::isFeatureSupported(QRhi::Feature feature) const

如果指定的 feature 受支持,则返回 true

bool QRhi::isRecordingFrame() const

当存在活动帧时返回真,这意味着存在一个没有对应 endFrame() (或 endOffscreenFrame()) 的 beginFrame() (或 beginOffscreenFrame())

另请参阅:currentFrameSlot(),beginFrame() 和 endFrame()。

bool QRhi::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = {}) const

如果通过 flags 修改的指定 format 的纹理受支持,则返回 true

该查询既支持未压缩也支持压缩格式。

bool QRhi::isYUpInFramebuffer() const

如果底层图形 API 在帧缓冲区和图像中将 Y 轴指向向上,则返回 true

实际上,这对于 OpenGL 只有 true

bool QRhi::isYUpInNDC() const

如果底层图形 API 在其归一化设备坐标系统中将 Y 轴指向向上,则返回 true

实际上,这对于 Vulkan 只有 false

注意:clipSpaceCorrMatrix() 包含相应的调整(使 Y 指向上)在返回矩阵中。

bool QRhi::makeThreadLocalNativeContextCurrent()

使用 OpenGL,此函数使当前线程上的 OpenGL 上下文成为当前上下文。对于其他后端,此函数没有效果。

调用此函数通常在 Qt 框架代码中相关,当需要确保由应用程序提供的外部 OpenGL 代码还能够像先前直接使用 OpenGL 那样运行时,只要 QRhi 使用 OpenGL 后端。

当失败时返回false,类似于QOpenGLContext::makeCurrent()。当操作失败时,可以调用isDeviceLost()来确定是否发生了上下文丢失的情况。这种检查与通过QOpenGLContext::isValid()进行检查等效。

另请参阅QOpenGLContext::makeCurrent() 和 QOpenGLContext::isValid

[静态] int QRhi::mipLevelsForSize(const QSize &size)

返回给定size的米级等级数量。

const QRhiNativeHandles *QRhi::nativeHandles()

返回后端特定集合的指针,该集合包含设备、上下文以及后端使用的类似概念的原生对象。

根据需要转换为QRhiVulkanNativeHandlesQRhiD3D11NativeHandlesQRhiD3D12NativeHandlesQRhiGles2NativeHandlesQRhiMetalNativeHandles

注意:不传递所有权,既不适用于返回的指针,也不适用于任何原生对象。

QRhiBuffer *QRhi::newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)

返回指定typeusagesize 的新缓冲区。

注意:某些usagetype 组合可能不被所有后端支持。请参阅UsageFlags功能标志

注意:后端可以选择分配大于size 的缓冲区。此操作对应用程序是透明的,因此没有对size 的特殊限制。QRhiBuffer::size() 总是会报告请求的 size 值。

另请参阅QRhiResource::destroy

QRhiComputePipeline *QRhi::newComputePipeline()

返回一个新的计算管道资源。

注意:只有在报告支持Compute 功能时,计算才可用。

另请参阅QRhiResource::destroy

QRhiGraphicsPipeline *QRhi::newGraphicsPipeline()

返回一个新的图形管道资源。

另请参阅QRhiResource::destroy

QRhiRenderBuffer *QRhi::newRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount = 1, QRhiRenderBuffer::Flags flags = {}, QRhiTexture::Format backingFormatHint = QRhiTexture::UnknownFormat)

返回指定typepixelSizesampleCountflags 的新渲染缓冲区。

backingFormatHint 设置为不同于QRhiTexture::UnknownFormat 的纹理格式时,它可以由后端用作决定渲染缓冲区的存储格式。

注意:backingFormatHint通常在涉及多采样和浮点纹理格式时变得相关:将渲染到多采样QRhiRenderBuffer然后将结果解析到非RGBA8的QRhiTexture暗示(在某些图形API中)支撑QRhiRenderBuffer的存储使用匹配的非RGBA8格式。这意味着传递QRhiTexture::RGBA32F这样的格式非常重要,因为后端通常会默认选择QRhiTexture::RGBA8,这会导致在QRhiTextureRenderTarget的颜色附件中尝试设置RGBA8→RGBA32F多采样解理,从而在后期导致破坏。

另请参阅QRhiResource::destroy

QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode addressU, QRhiSampler::AddressMode addressV, QRhiSampler::AddressMode addressW = QRhiSampler::Repeat)

返回一个新的采样器,具有指定的放大滤波器magFilter、缩小滤波器minFilter、子纹理模式mipmapMode以及地址(包装)模式addressUaddressVaddressW

注意:mipmapMode设置为除None以外的值意味着将为所有相关子纹理级别提供图像,这可以通过纹理上传或通过在此采样器使用的纹理上调用generateMips()来实现。尝试使用没有所有相关子纹理级别数据的纹理作为采样器会导致渲染错误,具体行为取决于底层图形API。

另请参阅QRhiResource::destroy

QRhiShaderResourceBindings *QRhi::newShaderResourceBindings()

返回一个新的着色器资源绑定集合资源。

另请参阅QRhiResource::destroy

QRhiSwapChain *QRhi::newSwapChain()

返回一个新的交换链。

另请参阅:QRhiResource::destroy()和QRhiSwapChain::createOrResize()。

QRhiTexture *QRhi::newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount = 1, QRhiTexture::Flags flags = {})

返回一个新的1D或2D纹理,其具有指定的formatpixelSizesampleCountflags

1D纹理数组必须在flags中设置QRhiTexture::OneDimensional。如果pixelSize的高度为0,此函数会隐式设置此标志。

注意:format指定所需的内部和外部格式,这意味着要上传到纹理的数据需要具有兼容的格式,而原始纹理可能(但在OpenGL中至少不确定)使用此格式作为内部格式。

注意: 当运行时报告支持OneDimensionalTextures功能时,1D纹理才起作用。此外,当运行时报告支持OneDimensionalTextureMipmaps功能时,1D纹理上的mipmap才起作用。

另请参阅QRhiResource::destroy

QRhiTexture *QRhi::newTexture(QRhiTexture::Format format, int width, int height, int depth, int sampleCount = 1, QRhiTexture::Flags flags = {})

返回一个新的1D、2D或3D纹理,其具有指定的formatwidthheightdepthsampleCountflags

此重载适用于3D纹理,因为它允许指定depth。3D纹理必须在flags中设置QRhiTexture::ThreeDimensional,但使用此重载时可以省略,因为当depth大于0时,标志会隐式设置。对于1D、2D和立方纹理,depth应设置为0。

1D纹理必须在flags中设置QRhiTexture::OneDimensional。此重载将隐式设置此标志,如果heightdepth均为0。

注意: 当运行时报告支持ThreeDimensionalTextures功能时,3D纹理才起作用。

注意: 当运行时报告支持OneDimensionalTextures功能时,1D纹理才起作用。此外,当运行时报告支持OneDimensionalTextureMipmaps功能时,1D纹理上的mipmap才起作用。

这是一个重载函数。

QRhiTexture *QRhi::newTextureArray(QRhiTexture::Format format, int arraySize, const QSize &pixelSize, int sampleCount = 1, QRhiTexture::Flags flags = {})

返回一个新的1D或2D纹理数组,其具有指定的formatarraySizepixelSizesampleCountflags

此函数在flags中隐式设置QRhiTexture::TextureArray

1D纹理数组必须在flags中设置QRhiTexture::OneDimensional。如果pixelSize的高度为0,此函数会隐式设置此标志。

注意: 不要将纹理数组与纹理数组混淆。此函数创建的QRhiTexture可以与着色器中的1D或2D数组采样器一起使用,例如:layout(binding = 1) uniform sampler2DArray texArr;。纹理数组指的是通过QRhiShaderResourceBinding::sampledTextures()暴露给着色器的纹理列表,且计数大于1,并在着色器中声明,例如:layout(binding = 1) uniform sampler2D textures[4];

注意: 只有在运行时报告支持TextureArrays功能时,此功能才有效。

注意: 当运行时报告支持OneDimensionalTextures功能时,1D纹理才起作用。此外,当运行时报告支持OneDimensionalTextureMipmaps功能时,1D纹理上的mipmap才起作用。

另请参阅 newTexture()。

QRhiTextureRenderTarget *QRhi::newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags = {})

返回一个新的纹理渲染目标,其中包含在desc中给出的颜色和深度/模板附加,以及指定的flags

另请参阅QRhiResource::destroy

QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch()

返回一个可用的、空的批处理对象,可以将复制类型的操作记录到其中。

注意: 返回值不属于调用者,必须永远不被销毁。相反,批处理对象会被返回到池中以便重用,通过传递给 QRhiCommandBuffer::beginPass()、QRhiCommandBuffer::endPass() 或 QRhiCommandBuffer::resourceUpdate(),或者通过在该批处理对象上调用 QRhiResourceUpdateBatch::destroy() 来实现。

注意:也可以在 beginFrame() - endFrame() 之外调用它,因为批处理实例只是收集数据,不执行任何操作。

由于与记录的帧无关,以下序列是有效的,例如

rhi->beginFrame(swapchain);
QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
u->uploadStaticBuffer(buf, data);
// ... do not commit the batch
rhi->endFrame();
// u stays valid (assuming buf stays valid as well)
rhi->beginFrame(swapchain);
swapchain->currentFrameCommandBuffer()->resourceUpdate(u);
// ... draw with buf
rhi->endFrame();

警告:每个 QRhi 的批处理数量上限为 64。当达到此限制时,函数将返回 null,直到批处理对象返回池中。

QByteArray QRhi::pipelineCacheData()

返回一个二进制数据块,其中包含从 QRhi 生命周期内成功创建的 QRhiGraphicsPipelineQRhiComputePipeline 收集的数据。

通过保存这份数据,然后在同一应用的后续运行中重新加载缓存数据,可以潜在减少管道和着色器创建的时间。缓存及其序列化版本的具体内容未指定,始终与使用的后端相关,并且在某些情况下还依赖于图形 API 的特定实现。

当报告 PipelineCacheDataLoadSave 不受支持时,返回的 QByteArray 是空的。

如果在调用 create() 时没有指定 EnablePipelineCacheDataSave 标志,则返回的 QByteArray 可能是空的,即使支持 PipelineCacheDataLoadSave 功能。

当返回的数据非空时,它始终与 Qt 版本和 QRhi 后端相关。此外,在某些情况下,还依赖于特定的图形设备和使用的确切驱动程序版本。QRhi 负责添加适当的头文件,并确保数据可以安全地传递到 setPipelineCacheData(),因此尝试从其他版本的驱动程序上运行的实例加载数据将被安全且优雅地处理。

注意:调用 releaseCachedResources() 可能会根据后端清除收集的管道数据。在这种情况下,对此函数的后续调用可能不会返回任何数据。

有关此功能的更多详细信息,请参阅 EnablePipelineCacheDataSave

注意:尽量减少对该函数的调用次数。检索数据块的操作并不总是便宜的,因此此函数应仅在低频率下调用,例如,在关闭应用程序时。

另请参阅:setPipelineCacheDatacreateisFeatureSupported

[静态] bool QRhi::probe(QRhi::Implementation impl, QRhiInitParams *params)

如果使用指定的 implparams 调用 create() 预期将会成功,则返回 true。

对于某些后端,这等同于调用 create(),检查其返回值,然后销毁生成的 QRhi

对于其他场景,特别是在 Metal 中,可能存在特定的探测实现,这允许以更轻量级的方式测试,而不会因失败而污染调试输出。

另请参阅 create

void QRhi::releaseCachedResources()

尝试释放后端缓存中的资源。这可能包括 CPU 和 GPU 资源。只有内存和可以自动重建的资源才在范围内。例如,如果后端的 QRhiGraphicsPipeline 实现将着色器编译结果维护在缓存中,调用此函数则会导致清空该缓存,从而可能释放内存和图形资源。

在资源受限的环境中调用此函数是有意义的,在这些环境中,在某个阶段可能需要确保资源使用最少,即使这会牺牲性能。

void QRhi::removeCleanupCallback(const void *key)

注销带有 key 的回调。如果没有与 key 注册任何清理回调,则函数不执行任何操作。无法移除未带键注册的回调。

另请参阅 addCleanupCallback

int QRhi::resourceLimit(QRhi::ResourceLimit limit) const

返回指定资源 limit 的值。

后端在初始化时预期会查询这些值,这意味着调用此函数是一个轻量级的操作。

void QRhi::runCleanup()

调用所有已注册的清理函数。然后清除清理回调列表。通常销毁 QRhi 会自动执行此操作,但有时触发清理以释放所有缓存的非关键资源可能是有用的。

另请参阅 addCleanupCallback

void QRhi::setPipelineCacheData(const QByteArray &data)

data 加载到管道缓存中(如果适用)。

当报告 PipelineCacheDataLoadSave 不受支持时,可以安全地调用此函数,但没有效果。

pipelineCacheData() 返回的 blob 总是针对 Qt 版本、QRhi 后端以及在某些情况下图形设备和图形驱动程序的给定版本而特定。 QRhi 会负责添加适当的头文件并确保数据可以安全地传递给此函数。如果存在不匹配,例如因为驱动程序已升级到较新版本,或者是因为数据是从不同的 QRhi 后端生成的,将打印警告并且安全地忽略 data

使用 Vulkan,这直接映射到 VkPipelineCache。调用此函数创建一个新的 Vulkan 管线缓存对象,其初始数据来源于data。然后,管线缓存对象将用于所有后续创建的QRhiGraphicsPipelineQRhiComputePipeline对象,从而可能加速管线创建。

在其他 API 中,实际上没有真正的管线缓存,但它们可能提供从着色器编译(D3D)或程序二进制(OpenGL)的字节码的缓存。在运行时执行大量从源进行着色器编译的应用程序中,如果使用此函数从早期运行中预种“管线缓存”,则可以在后续运行中提供显著的提升。

注意:QRhi不能保证data对管线和着色器创建性能有影响。对于像 Vulkan 这样的 API,是否使用data取决于驱动程序来决定,或者它被忽略。

有关此功能的更多详细信息,请参阅 EnablePipelineCacheDataSave

注意:QRhi提供的这种机制独立于驱动程序自身内部缓存机制(如果有的话)。这意味着,根据图形 API 和其实施情况,检索和重新加载data的确切效果是不可预知的。在其他缓存机制(Qt 无法控制之外的)已经激活的情况下,改进的性能可能完全看不到。

注意:尽可能减少调用此函数的次数。加载 blob 操作并不总是便宜的,因此此函数应该以低频率调用,理想情况下只调用一次,例如,在启动应用程序时。

另请参阅:pipelineCacheData()和isFeatureSupported()。

[静态] QSize QRhi::sizeForMipLevel(int mipLevel, const QSize &baseLevelSize)

返回给定mipLevel的纹理图像大小,它基于在baseLevelSize中给出的级别 0 大小计算。

QRhiStats QRhi::statistics() const

收集并返回有关图形资源计时和分配的统计信息。

只有一些后端提供内存分配的数据,在这些后端中,此类操作处于 Qt 的控制之下。在没有对资源内存分配的较低级控制的图形 API 中,这永远不会得到支持,并且结果中的所有相关字段都是 0。

特别是在 Vulkan 中,值始终有效,并且是从底层内存分配器库检索的。这提供了对活动缓冲区和纹理内存需求的理解。

对于 Direct 3D 12 也是如此。除了内存分配器库的统计信息之外,这里的参数还包括 DXGI 报告的totalUsageBytes字段,该字段报告了包括不在内存分配器库控制之下(交换链缓冲区,描述符堆等)的额外资源在内的总大小。

值对应于所有类型的内存使用,合并计算。(即,在独立 GPU 的情况下,视频+系统)

大多数后端都可以提供有关图形和计算管线创建所花费时间(通常涉及着色器编译或缓存查找,以及可能昂贵的处理)的附加数据。

注意:操作如管道创建的耗时可能受到各种因素的影响。不同后端的结果不应进行比较,因为“管道”的概念以及在调用如QRhiGraphicsPipeline::create()()期间实际在内部发生的事情,在不同的图形API及其实现之间有很大的不同。

注意:此外,许多驱动程序可能会采用各种着色器和程序管道的缓存策略。(即使Qt本身也有类似的设施,如setPipelineCacheData()或OpenGL特定的程序二进制磁盘缓存)。由于此类内部行为对API客户透明,Qt和QRhi没有关于确切缓存策略、持久性、缓存数据的失效等知识或控制。在读取例如管道创建所花费的时间时,应考虑到驱动程序级别缓存机制可能的实施和未指定行为。

QList<int> QRhi::supportedSampleCounts() const

返回支持样本计数的列表。

一个典型的例子是(1、2、4、8)。

对于某些后端,此支持值列表是预先固定的,而对于其他一些后端,则(物理)设备属性指示运行时支持的内容。

另请参阅QRhiRenderBuffer::setSampleCountQRhiTexture::setSampleCountQRhiGraphicsPipeline::setSampleCountQRhiSwapChain::setSampleCount

QThread *QRhi::thread() const

返回初始化QRhi的线程。

int QRhi::ubufAligned(int v) const

返回与ubufAlignment()给出的统一缓冲区对齐(通常是一个偏移量)的值v

int QRhi::ubufAlignment() const

返回最小统一缓冲区偏移量对齐的字节数。这通常是256。

尝试绑定不与此值对齐的偏移量的统一缓冲区会将导致根据后端和底层图形API出现失败。

另请参阅ubufAligned

[静态] QRhiSwapChainProxyData QRhi::updateSwapChainProxyData(QRhi::Implementation impl, QWindow *窗口)

生成并返回一个包含与指定的后端和图形API(impl)相关的不透明数据的QRhiSwapChainProxyData结构,窗口是交换链目标的QWindow

返回的struct可以传递给QRhiSwapChain::setProxyData。这在多线程渲染系统中很有意义:这个静态函数预期在主(GUI)线程上被调用,与所有QRhi操作相反,然后传递给与QRhiQRhiSwapChain合作的线程,并传递给swapchain。这允许在主线程上调用只能安全调用的本地平台查询,例如从NSView中查询CAMetalLayer,然后将数据传递给渲染线程上的QRhiSwapChain。在Metal示例中,在专门的渲染线程上执行view.layer访问会在Xcode Thread Checker上引发警告。使用数据代理机制可以避免这种情况。

当不涉及线程时,不需要生成和传递QRhiSwapChainProxyData:后端保证能够自行查询所需的一切,并且如果一切都在主(GUI)线程上,那就足够了。

注意:impl应该与创建QRhi时使用的匹配。例如,在非Apple平台上使用QRhi::Metal调用不会生成任何有用的数据。

相关非成员

[别名,自6.7以来] QRhiShaderResourceBindingSet

QRhiShaderResourceBindings的同义词。

此typedef是在Qt 6.7中引入的。

© 2024 Qt公司有限公司。本文档中包含的文档贡献的版权归其各自的所有者所有。提供的文档是根据由自由软件基金会发布的GNU自由文档许可证版本1.3的条款授予的。Qt和相应的商标是芬兰和/或世界其他地区的Qt公司的注册商标。所有其他商标都是其各自所有者的财产。