QRhi Class

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

头文件: #include <rhi/qrhi.h>
CMake: find_package(Qt6 REQUIRED COMPONENTS Gui)
target_link_libraries(mytarget PRIVATE Qt6::GuiPrivate)
qmake: QT += gui-private
Qt 6.6

公共类型

enum BeginFrameFlag { }
flags BeginFrameFlags
enum EndFrameFlag { SkipPresent }
flags EndFrameFlags
enum Feature { MultisampleTexture, MultisampleRenderBuffer, DebugMarkers, Timestamps, Instancing, …, SampleVariables }
enum Flag { EnableDebugMarkers, EnableTimestamps, PreferSoftwareRenderer, EnablePipelineCacheDataSave, SuppressSmokeTestWarnings }
flags Flags
enum FrameOpResult { FrameOpSuccess, FrameOpError, FrameOpSwapChainOutOfDate, FrameOpDeviceLost }
enum Implementation { Null, Vulkan, OpenGLES2, D3D11, D3D12, Metal }
enum ResourceLimit { TextureSizeMin, TextureSizeMax, MaxColorAttachments, FramesInFlight, MaxAsyncReadbackFrames, …, ShadingRateImageTileSize }

公共函数

~QRhi()
void addCleanupCallback(const QRhi::CleanupCallback &callback)
void addCleanupCallback(const void *key, const QRhi::CleanupCallback &callback)
QRhi::Implementation backend() const
const char *backendName() const
QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags = {})
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags = {})
QMatrix4x4 clipSpaceCorrMatrix() const
int currentFrameSlot() const
QRhiDriverInfo driverInfo() const
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags = {})
QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags = {})
QRhi::FrameOpResult finish()
bool isClipDepthZeroToOne() const
bool isDeviceLost() const
bool isFeatureSupported(QRhi::Feature feature) const
bool isRecordingFrame() const
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = {}) const
bool isYUpInFramebuffer() const
bool isYUpInNDC() const
bool makeThreadLocalNativeContextCurrent()
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()
(since 6.9) QRhiShadingRateMap *newShadingRateMap()
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()
QByteArray pipelineCacheData()
void releaseCachedResources()
void removeCleanupCallback(const void *key)
int resourceLimit(QRhi::ResourceLimit limit) const
void runCleanup()
void setPipelineCacheData(const QByteArray &data)
(since 6.9) void setQueueSubmitParams(QRhiNativeHandles *params)
QRhiStats statistics() const
QList<int> supportedSampleCounts() const
(since 6.9) QList<QSize> supportedShadingRates(int sampleCount) const
QThread *thread() const
int ubufAligned(int v) const
int ubufAlignment() const

静态公共成员

const char *backendName(QRhi::Implementation impl)
QRhi *create(QRhi::Implementation impl, QRhiInitParams *params, QRhi::Flags flags = {}, QRhiNativeHandles *importDevice = nullptr)
int mipLevelsForSize(const QSize &size)
bool probe(QRhi::Implementation impl, QRhiInitParams *params)
QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize)
QRhiSwapChainProxyData updateSwapChainProxyData(QRhi::Implementation impl, QWindow *window)

详细说明

Qt 渲染硬件接口(Qt Rendering Hardware Interface)是硬件加速图形 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 仅在相关平台上运行时可用(macOS/iOS 上的 Metal、Windows 上的 Direct3D)。

目前可用的后端包括

  • OpenGL 2.1 / OpenGL ES 2.0 或更新版本。某些扩展和较新的核心规范功能会在出现时使用,例如启用多采样帧缓冲器或计算着色器。此外,还支持在核心配置文件上下文中运行。如有必要,应用程序可在运行时查询feature flags ,以检查 QRhi 所支持的 OpenGL 上下文中不支持的功能。OpenGL 后端基于QOpenGLContextQOpenGLFunctions 以及Qt GUI 模块的相关跨平台基础架构。
  • Direct3D 11.2 及更新版本(使用 DXGI 1.3 及更新版本),使用着色器模型 5.0 或更新版本。当 D3D 运行时不支持 11.2 功能或着色器模型 5.0 时,使用加速图形设备的初始化将失败,但仍可使用软件适配器
  • Direct3D 12 适用于 Windows 10 1703 及更新版本,Shader Model 5.0 或更新版本。Qt 要求 ID3D12Device2 存在,因此至少需要 Windows 10 的 1703 版本。D3D12 设备默认创建时指定的最低功能级别为D3D_FEATURE_LEVEL_11_0.NET 1.2 或更新版本。
  • Metal 1.2 或更新版本。
  • Vulkan 1.0 或更新版本,可选择使用某些 Vulkan 1.1 级别的功能。
  • Null:"虚拟 "后端,完全不发出图形调用。

为了让着色器代码能在 Qt 应用程序和库中一次编写完成,所有着色器都应使用一种语言编写,然后编译到 SPIR-V 中。各种着色语言的版本以及反射信息(输入、输出、着色器资源)将由此生成。然后将这些信息打包成易于高效序列化的QShader 实例。生成此类着色器的编译器和工具不属于 QRhi 和Qt GUI 模块,但使用此类着色器的核心类QShaderQShaderDescription 却属于 QRhi 和 模块。执行编译和翻译的 API 和工具是 Qt XMLShader Tools 模块的一部分。

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

API 印象

下面是一个完整的、可运行的跨平台应用程序,它在屏幕外渲染 20 帧图像,然后在从 GPU 读回纹理内容后将生成的图像保存到文件中。有关在屏幕上渲染的示例,其中涉及QWindow 和交换链的设置,请参阅RHI 窗口示例

为简洁起见,QRhi 的初始化根据平台进行:此处的示例代码在 Windows 上选择 Direct 3D 12,在 macOS 和 iOS 上选择 Metal,在其他平台上选择 Vulkan。本应用程序从未使用过 OpenGL 和 Direct 3D 11,但只需增加几行代码即可引入对它们的支持。

#include <QGuiApplication>#include <QImage> #include<QFile> #include<rhi/qrhi.h>intmain(intargc, char **argv) { QGuiApplicationapp(argc,argv);#if QT_CONFIG(vulkan)   QVulkanInstanceinst;#endifstd::unique_ptr<QRhi>rhi;#if defined(Q_OS_WIN)QRhiD3D12InitParams params; rhi.reset(QRhi::create(QRhi::D3D12, &params));#elif QT_CONFIG(metal)    QRhiMetalInitParams参数; rhi.reset(QRhi::create(QRhi::Metal, &params));#elif QT_CONFIG(vulkan)inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());if(inst.create()) { QRhiVulkanInitParams参数;params. inst = &instrhi.reset(QRhi::create(QRhi::Vulkan, &params)); }else{        qFatal("Failed to create Vulkan instance");
    }#endif if(rhi)        qDebug() << rhi->backendName() << rhi->driverInfo();
   不然        qFatal("Failed to initialize RHI");

   floatrotation= 0.0f;floatopacity= 1.0f;intopacityDir= 1; std::unique_ptr<QRhiTexture>tex(rhi->newTexture(QRhiTexture::RGBA8          QSize(1280, 720), 1,  QRhiTexture::渲染目标 QRhiTexture::UsedAsTransferSource)); tex->create(); std::unique_ptr<QRhiTextureRenderTarget>rt(rhi->newTextureRenderTarget({ tex.get() })); std::unique_ptr<QRhiRenderPassDescriptorRt(rhi->newTextureRenderTarget({ tex.get() }); std::unique_ptr<>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 floatvertexData[] ={// Y up,  CCW0.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, 0.0f, 1.0f,}; std::unique_ptr<QRhiBuffer>vbuf(rhi->newBuffer(QRhiBuffer::ImmutableQRhiBuffer::VertexBuffer, sizeof(vertexData))); vbuf->create(); std::unique_ptr<QRhiBuffer>ubuf(rhi->newBuffer(QRhiBuffer::DynamicQRhiBuffer::UniformBuffer, 64 + 4)); ubuf->create(); std::unique_ptr<QRhiShaderResourceBindings>srb(rhi->newShaderResourceBindings()); srb->setBindings({        QRhiShaderResourceBinding::uniformBuffer(0QRhiShaderResourceBinding: :顶点阶段 QRhiShaderResourceBinding::FragmentStage, ubuf.get()) }); srb->create(); std::unique_ptr<QRhiGraphicsPipeline>ps(rhi->newGraphicsPipeline());    QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; premulAlphaBlend.enable= true; ps->setTargetBlends({ premulAlphaBlend });static autogetShader= [](constQString名称){        QFilef(name);returnf.open(QIODevice::ReadOnly)?QShader::fromSerialized(f.readAll()) : QShader(); }; ps->setShaderStages({ { QRhiShaderStage::Vertex,getShader(QLatin1String("color.vert.qsb"))},{ QRhiShaderStage::Fragment,getShader(QLatin1String("color.frag.qsb"))} });   QRhiVertexInputLayoutinputLayout; inputLayout.setBindings({ {5 * sizeof(float) } }); inputLayout.setAttributes({ {0, 0QRhiVertexInputAttribute::Float2, 0},{0, 1QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }); ps->setVertexInputLayout(inputLayout); ps->setShaderResourceBindings(srb.get()); ps->setRenderPassDescriptor(rp.get()); ps->create();    QRhiCommandBuffer*cb;for(intframe= 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();constQRhiCommandBuffer顶点输入 vbufBinding(vbuf.get(), 0); cb->setVertexInput(0, 1, &vbufBinding); cb->draw(3);        QRhiReadbackResultreadbackResult; u=  rhi->nextResourceUpdateBatch(); u->readBackTexture({ tex.get() }, &readbackResult); cb->endPass(u); rhi->endOffscreenFrame();        QImageimage(reinterpret_cast<constuchar*>(readbackResult.data.constData()),readbackResult.pixelSize.width(),readbackResult.pixelSize.height()QImage::Format_RGBA8888_Premultiplied);if(rhi->isYUpInFramebuffer()) image.flip(); 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 XMLShader Tools 模块提供了 CMake 的构建系统集成,即qt_add_shaders() CMake 函数,可以在构建时实现同样的功能。

安全考虑

QRhi 和相关类(如QShader )使用的所有数据都被视为可信内容。

警告: 建议应用程序开发人员在允许输入用户提供的不属于应用程序且不受开发人员控制的内容之前,仔细考虑潜在的影响。(这包括所有顶点/索引数据、着色器、管道和绘制调用参数等)。

设计基础

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;

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

无论底层图形应用程序接口的设计和功能如何,所有 QRhi 后端都实现了一定程度的命令缓冲区。QRhiCommandBuffer 函数不会直接发出任何本地绑定或绘制命令(如glDrawElements )。命令总是记录在队列中,队列可以是本地队列,也可以是 QRhi 后端提供的队列。命令缓冲区已提交,因此只有在QRhi::endFrame() 或QRhi::finish() 时才开始执行。

延迟特性会对某些类型的对象产生影响。例如,在一个帧内多次向动态缓冲区写入(如果此类缓冲区由主机可见内存支持),将导致帧的命令缓冲区中的所有绘制调用都能看到所有写入的结果,而不管动态缓冲区更新是何时相对于绘制调用记录的。

此外,QRhiResource 子类的实例必须在以任何方式引用它们的帧中保持不变。在开始为下一帧记录命令之前,应预先创建所有资源。应避免在帧内重复使用QRhiResource 实例(通过调用create() ,然后在同一beginFrame - endFrame 部分再次引用该实例),因为这可能会导致意想不到的结果,具体取决于后端。

一般来说,在调用endFrame() 提交框架之前,所有引用的QRhiResource 对象都必须保持有效且未修改。另一方面,一旦帧提交,调用destroy() 或删除QRhiResource 始终是安全的,无论底层本地资源的状态如何(GPU 可能仍在使用这些资源,但内部会对此进行处理)。

与 OpenGL 等应用程序接口不同,上传和复制类型的命令不能与绘制命令混合使用。典型的渲染器会涉及类似以下的序列:

  • (重新)创建资源
  • 开始帧
  • 记录/发布上传和复制
  • 开始记录渲染过程
  • 记录绘制调用
  • 结束渲染过程
  • 结束帧

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

在使用为 OpenGL 设计的传统渲染引擎时,向 QRhi 的迁移通常需要重新设计,从单一的render 步骤(执行复制和上传、清除缓冲区和发出绘制调用,所有这些操作都混合在一起)转变为明确分离的两阶段prepare -render 设置,其中render 步骤仅启动渲染通路和记录绘制调用,而所有资源创建以及更新、上传和复制的队列都事先在prepare 步骤中进行。

QRhi 目前不允许自由创建和提交命令缓冲区。这在某种程度上可能会在未来被取消,尤其是在引入计算支持的情况下,但定义明确的frame-startframe-end 点模型,结合专用的 "帧 "命令缓冲区(frame-end 意味着呈现),仍将是主要的操作方式,因为这最适合 Qt 的各种 UI 技术。

线程

QRhi 实例和相关资源可在任何线程上创建和使用,但所有使用必须仅限于该单线程。当在应用程序中向多个 QWindows 呈现时,通常建议为每个窗口设置一个专用线程和 QRhi 实例,因为这样可以消除向多个窗口呈现时出现的意外节流问题。从概念上讲,这与Qt Quick 场景图的线程渲染循环在直接使用 OpenGL 时的操作方式相同:每个窗口一个线程,每个线程一个QOpenGLContext 。当迁移到 QRhi 时,QOpenGLContext 将被 QRhi 所取代,因此迁移过程非常简单。

当涉及到外部创建的本地对象(如通过QRhiGles2NativeHandles 传入的 OpenGL 上下文)时,应用程序必须确保它们不会被其他线程滥用。

QRhi 实例之间不能共享资源。这是有意为之,因为 QRhi 隐藏了大部分队列、命令缓冲区和资源同步相关任务,也没有为它们提供 API。不过,多个线程安全、高效地并发使用图形资源与这些概念息息相关,因此目前不在讨论范围内,但将来可能会引入。

注: Metal 后端要求在渲染线程上有一个自动释放池,最好是在渲染循环的每次迭代中都有。在主(gui)线程上进行渲染时,QRhi 的用户无需进行任何操作,但在使用单独的专用渲染线程时,这一点就变得非常重要。

资源同步

QRhi 没有公开用于资源障碍或图像布局转换的 API。在适用情况下(例如 Vulkan),此类同步由后端通过跟踪资源使用情况隐式完成。缓冲区和图像屏障会在呈现或计算传递之前插入,对应用程序透明。

注: 渲染或计算通道中的资源应在该通道中绑定到单一用途。例如,缓冲区可用作顶点缓冲区、索引缓冲区、均匀缓冲区或存储缓冲区,但不能在单个传递中组合使用。不过,在计算传递中将缓冲区用作存储缓冲区,然后在渲染传递中将其用作顶点缓冲区也是完全可以的,例如,假设缓冲区在创建时声明了这两种用途。

注: 在某些情况下,纹理的这一规则可以放宽,因为即使在同一传递中,也支持将同一纹理的两个子资源(通常是两个不同的 mip 级别)用于不同的访问(一个用于加载,一个用于存储)。

资源重用

从用户的角度来看,调用QRhiResource::destroy() 后,QRhiResource 即可重复使用。除了 swapchain 外,在一个已创建的对象上调用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 提供相同的契约:即使在创建呈现目标对象后,呈现目标对象的关联纹理或呈现缓冲区之一已被重建(通过调用 ),调用 () 也是安全的。这样,应用程序就可以通过在 上设置新的像素大小并调用 () 来调整纹理的大小,从而在下方创建全新的本地纹理资源,而无需更新 ,因为这将在 beginPass() 中隐式完成。create() QRhiCommandBuffer::beginPass QRhiTexture create QRhiTextureRenderTarget

池对象

除了资源,还有池对象,如QRhiResourceUpdateBatch 。一个实例可通过next 函数(如nextResourceUpdateBatch() 函数)获取。在这种情况下,调用者并不拥有返回的实例。唯一有效的操作方法是调用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 。不应推迟释放交换链,因为当本机窗口不再存在时,释放交换链可能会产生问题(对于某些应用程序接口,如 Vulkan,明确禁止释放交换链),例如因为 QPlatformWindow 在获取QWindow::close() 时被销毁。因此,只要目标QWindow 发送QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed 事件,就必须释放交换链。如果该事件没有在QWindow 销毁前到达(使用QCoreApplication::quit() 时可能会出现这种情况),则应在事件循环退出后检查 QWindow::handle(),并在非空时(即底层本地窗口仍然存在)调用交换链释放。

所有权

一般规则是不转移所有权。使用已存在的图形设备创建 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 backend namegraphics device information 。如果需要,这些信息可以打印给用户或存储在应用程序日志中,即使在生产构建中也是如此。

调查呈现问题

当渲染结果与预期不符或应用程序遇到问题时,请务必考虑使用本地 3D API 的调试和验证工具进行检查。QRhi 本身的错误检查功能有限,因为复制底层中已有的大量功能是不合理的。

  • 对于 Vulkan,控制Vulkan 验证层并不在 QRhi 的范围内,而是可以通过配置QVulkanInstance 与相应层来实现。例如,在调用QVulkanInstance 上的create() 之前,先调用instance.setLayers({ "VK_LAYER_KHRONOS_validation" }); 。(注意,这假定验证层已实际安装并可用,例如从 Vulkan SDK 中安装)默认情况下,QVulkanInstance 会方便地将 Vulkan 调试消息重定向到qDebug ,这意味着验证消息会像其他 Qt 警告一样被打印出来。
  • 在 Direct 3D 11 和 12 中,可以通过在相应的init params struct 中切换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 QuickQt Quick 基于 3D 的项目:

  • 对于使用 OpenGL、Vulkan、D3D11 或 D3D12 的应用程序,RenderDoc可在 Windows 和 Linux 上捕获帧并反观记录的命令和管道状态。当试图找出 3D 场景的某些部分为何未按预期显示时,RenderDoc 通常是检查管道阶段和相关状态并发现缺失或不正确值的快速高效方法。在开发 Active Qt 本身时,它也是一个被积极使用的工具。
  • 对于基于英伟达™(NVIDIA®)的系统,Nsight Graphics在 Windows 和 Linux 上提供了图形调试器工具。除了调查帧和流水线中的命令外,供应商特定的工具还允许查看定时和硬件性能信息,而这不是简单的帧捕获所能提供的。
  • 对于基于 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 的应用程序进行性能分析。

捕获帧时,请记住,只要debug markers were enabled for the QRhi 和使用的图形 API 支持,就可以通过调试标记来命名对象和命令组。要注释命令流,可调用debugMarkBegin(),debugMarkEnd() 和/或debugMarkMsg() 。这对于需要多次渲染的大型画面尤其有用。通过在create() 之前调用setName() 来命名资源。

要在应用程序内对 CPU 和 GPU 端执行基本的时序测量,可以使用QElapsedTimerQRhiCommandBuffer::lastCompletedGpuTime()。后者目前仅适用于部分图形应用程序接口,需要通过QRhi::EnableTimestamps 标志进行选择。

资源泄漏检查

在销毁 QRhi 对象时,如果没有正确销毁所有缓冲区、纹理和从该对象创建的其他资源,只要应用程序是调试构建,或者QT_RHI_LEAK_CHECK 环境变量设置为非零值,就会在调试输出中打印相关警告。这是一种发现应用程序呈现逻辑中资源处理设计问题的简单方法。但请注意,某些平台和底层图形应用程序接口可能会执行自己的分配和资源泄漏检测,Qt 对此无法直接控制。例如,在使用 Vulkan 时,如果拥有图形内存分配的资源没有在 QRhi 之前销毁,内存分配器可能会在调试构建中提出失败断言。此外,启用 Vulkan 验证层后,会对未释放的本地图形资源发出警告。同样,在使用 Direct 3D 时,如果应用程序未按正确顺序销毁 QRhi 及其资源,则可能会对未释放的 COM 对象发出警告。

另请参阅 RHI 窗口示例QRhiCommandBuffer,QRhiResourceUpdateBatch,QRhiShaderResourceBindings,QShader,QRhiBuffer,QRhiTexture,QRhiRenderBuffer,QRhiSampler,QRhiTextureRenderTarget,QRhiGraphicsPipeline,QRhiComputePipelineQRhiSwapChain

成员类型文档

枚举 QRhi::BeginFrameFlag
标志 QRhi::BeginFrameFlags

用于QRhi::beginFrame() 的标志值

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

枚举 QRhi::EndFrameFlag
flags QRhi::EndFrameFlags

QRhi::endFrame() 的标志值

常量说明
QRhi::SkipPresent1 << 0指定不队列当前命令或不调用 swapBuffers。这样就不会显示图像。不建议在生成多个帧时都设置此标记(例如,用于基准测试的情况除外--但请记住,后端在等待命令完成而不显示图像时的行为可能不同,因此它们之间的结果不具有可比性。)

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

enum QRhi::Feature

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

常量说明
QRhi::MultisampleTexture1表示支持样本数大于 1 的纹理。实际上,在 OpenGL ES 版本高于 3.1 和 OpenGL 版本高于 3.0 的情况下,将不支持此功能。
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 上下文将支持此功能。不过,对于其中一些应用程序接口,对时间戳查询的支持在技术上是可选的,因此不能保证它们的每个实现都支持该功能。
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 会尝试在所有后端启用此功能,但在某些情况下不支持。动态控制基元重启是不可能的,因为某些应用程序接口会始终开启固定索引的基元重启。应用程序必须假定,只要该功能被报告为支持,上述索引值may 就会根据拓扑结构被特殊处理。只有两种拓扑结构,即LineStripTriangleStrip ,在这两种拓扑结构中,只要该功能被报告为受支持,原始重启在不同后端中的表现都是相同的。
QRhi::NonDynamicUniformBuffers8表示支持使用UniformBufferImmutableStatic 类型创建缓冲区。当报告为不支持时,统一(常量)缓冲区必须以Dynamic 的方式创建(无论如何都建议这样做)。
QRhi::NonFourAlignedEffectiveIndexBufferOffset9表示支持非 4 字节对齐的有效索引缓冲区偏移 (indexOffset + firstIndex * indexComponentSize)。如果不支持,尝试使用未对齐的有效偏移量发布drawIndexed() 可能会导致不明行为。这一点与 Metal 尤为相关,因为 Metal 会将此报告为不支持。
QRhi::NPOTTextureRepeat10表示Repeat 换行模式和 mipmap 过滤模式支持非二进制大小的纹理。在实践中,只有在不使用GL_OES_texture_npot 的 OpenGL ES 2.0 实现中才可能是假的。
QRhi::RedOrAlpha8IsRed11表示RED_OR_ALPHA8 格式映射为单组分 8 位red 格式。在使用 OpenGL ES 或非核心配置文件上下文时,除 OpenGL 外,所有后端都是这种情况。在这种情况下,GL_ALPHA ,即使用单分量 8 位alpha 格式。使用特殊纹理格式可以为创建纹理提供单一代码路径,由后端决定实际格式,而特征标志可用于选择适当的着色器变体对纹理进行采样。
QRhi::ElementIndexUint12表示索引缓冲区支持 32 位无符号整数元素。实际上,除了在没有必要扩展的普通 OpenGL ES 2.0 实现上运行外,在其他地方都是如此。为假时,索引缓冲区中只支持 16 位无符号元素。
QRhi::Compute13表示支持计算着色器、图像加载/存储和存储缓冲区。早于 4.3 版的 OpenGL 和早于 3.1 版的 OpenGL ES 不支持计算。
QRhi::WideLines14表示支持宽度不为 1 的线条。当报告为不支持时,图形流水线状态中设置的线宽将被忽略。对于某些后端(D3D11、D3D12、Metal),该值可能始终为假。对于 Vulkan,该值取决于实现。对于 OpenGL,在核心配置文件上下文中不支持宽线条。
QRhi::VertexShaderPointSize15表示考虑了顶点着色器中通过gl_PointSize 设置的光栅化点的大小。如果报告为不支持,则不支持绘制尺寸大于 1 的点。此时,在着色器中设置gl_PointSize 仍然有效,但会被忽略。(例如,在生成 HLSL 时,该赋值会从生成的代码中默默删除)请注意,某些 API(Metal、Vulkan)要求在绘制点时明确在着色器中设置点的大小,即使大小为 1,因为它们不会自动默认为 1。
QRhi::BaseVertex16表示drawIndexed() 支持vertexOffset 参数。当报告为不支持时,索引绘制中的 vertexOffset 值将被忽略。实际上,低于 3.2 版本的 OpenGL 和 OpenGL ES 以及旧版 iOS 设备(包括 iOS 模拟器)上的 Metal 都不支持此功能。
QRhi::BaseInstance17表示实例绘制命令支持firstInstance 参数。当报告为不支持时,firstInstance 值将被忽略,实例 ID 将从 0 开始。实际上,OpenGL 和旧版 iOS 设备(包括 iOS 模拟器)上的 Metal 都不支持该功能。
QRhi::TriangleFanTopology18表示QRhiGraphicsPipeline::setTopology() 支持QRhiGraphicsPipeline::TriangleFan 。实际上,Metal 和 Direct 3D 11/12 将不支持此功能。
QRhi::ReadBackNonUniformBuffer19表示reading buffer contents 支持与 UniformBuffer 用途不同的QRhiBuffer 实例。实际上,OpenGL ES 2.0 将不支持此功能。
QRhi::ReadBackNonBaseMipLevel20表示在回读纹理内容时支持指定 0 以外的 mip 级别。不支持时,在QRhiReadbackDescription 中指定非零级别会导致返回全为零的图像。实际上,OpenGL ES 2.0 将不支持这一功能。
QRhi::TexelFetch21表示在着色器中可以使用 texelFetch() 和 textureLod()。实际上,在 OpenGL ES 2.0 和 OpenGL 2.x 上下文中,这将被报告为不支持,因为 GLSL 100 es 和 130 之前的版本不支持这些函数。
QRhi::RenderToNonBaseMipLevel22表示在创建以QRhiTexture 作为颜色附件的QRhiTextureRenderTarget 时,支持指定 0 以外的 mip 级别。不支持时,只要目标 mip 级别不为 0,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 。回读浮点格式QRhiTexture::RGBA16F 和 RGBA32F 在 OpenGL 中也可以正常工作,只要实现提供了对这些格式的支持,但QRhi 无法提供任何保证,本标记即表明了这一点。
QRhi::PipelineCacheDataLoadSave26表示pipelineCacheData() 和setPipelineCacheData() 函数有效。不支持时,函数将不执行任何操作,检索到的 blob 总是空的,因此检索管道缓存内容并在随后运行应用程序时重新加载管道缓存内容不会带来任何好处。
QRhi::ImageDataStride27表示支持在纹理上传中为原始图像数据指定自定义跨距(行长)。如果不支持(底层 API 是 OpenGL ES 2.0,但不支持 GL_UNPACK_ROW_LENGTH),则不得使用QRhiTextureSubresourceUploadDescription::setDataStride()。
QRhi::RenderBufferImport28表示支持QRhiRenderBuffer::createFrom()。对于大多数图形应用程序接口来说,这样做是不合理的,因为QRhiRenderBuffer 在内部封装了纹理对象,就像QRhiTexture 一样。然而,对于 OpenGL,渲染缓冲区对象在 API 中是作为一个单独的对象类型存在的,在某些环境中(例如,可能需要将渲染缓冲区对象与 EGLImage 对象关联起来),必须允许使用QRhiRenderBuffer 封装现有的 OpenGL 渲染缓冲区对象。
QRhi::ThreeDimensionalTextures29表示支持 3D 纹理。实际上,低于 3.0 的 OpenGL 和 OpenGL ES 版本将不支持此功能。
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 之间的细分着色器存在可移植性问题(例如,由于船体着色器的结构方式,将 GLSL/SPIR-V 转换为 HLSL 存在问题,而 Metal 使用的细分管道与其他着色器略有不同),因此,尽管所有底层 API 都实现了基本功能,但仍可能出现意想不到的问题。特别是对于 Direct 3D,由于 qsb 无法从 SPIR-V 生成 HLSL 船体着色器和域着色器,因此必须将手写的 HLSL 船体着色器和域着色器注入每个QShader ,分别用于细分控制和评估阶段。请注意,应避免使用隔离细分,因为并非所有后端都支持隔离细分。后端之间可移植的最大补丁控制点数为 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表示对于texture arrays ,可以指定一个暴露给着色器的范围。通常情况下,所有数组层都会暴露出来,由着色器选择层(通过采样sampler2DArray 时传递给纹理()的第三个坐标)。如果支持,在buildingimporting 本地纹理之前调用 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)。将模式更改为 Line 的常见用例是获得线框渲染。但这并不是 OpenGL ES 的核心功能,在 Vulkan 中也是可选功能,因为某些移动 GPU 可能不提供该功能。
QRhi::OneDimensionalTextures36表示支持 1D 纹理。实际上,OpenGL ES 不支持此功能。
QRhi::OneDimensionalTextureMipmaps37表示支持生成 1D 纹理贴图。实际上,不支持 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 纹理贴图。实际上,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 通常支持多视图。当报告为支持时,使用引用纹理数组并设置了multiViewCountQRhiColorAttachment 创建QRhiTextureRenderTarget ,可以记录使用多视图渲染的渲染传递。此外,该渲染过程中使用的任何QRhiGraphicsPipeline 都必须具有the same view count set 。请注意,多视图只能与二维纹理阵列结合使用。它不能用于将渲染优化为单个纹理(如两个纹理,分别用于左眼和右眼)。相反,多视图渲染传递的目标始终是一个纹理数组,它会自动渲染到每个视图对应的图层(数组元素)。因此,该功能也意味着纹理阵列。多视图渲染不支持与细分或几何着色器结合使用。有关多视图渲染的更多详情,请参阅QRhiColorAttachment::setMultiViewCount() 。该枚举值在 Qt 6.7 中引入。
QRhi::TextureViewFormat42表示在QRhiTexture 上设置view format 是否有效。当报告为支持时,设置读取(采样)或写入(渲染目标/图像加载存储)视图模式会更改纹理的查看格式。在不支持的情况下,设置视图格式没有任何作用。请注意,Qt 无法了解或控制底层 3D API 及其实现中的格式兼容性或资源视图规则。传入不合适、不兼容的格式可能会导致错误和未指定的行为。提供此功能主要是为了允许将以 sRGB 格式创建的纹理渲染 "转换 "为非 sRGB 格式,以避免在着色器写入时进行不必要的线性->sRGB 转换。其他类型的渲染可能有效,也可能无效,这取决于底层应用程序接口。目前针对 Vulkan 和 Direct 3D 12 实现。对于 D3D12,只有在支持CastingFullyTypedFormatSupported 的情况下该功能才可用,请参见https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html(请注意QRhi 始终使用全类型格式的纹理。)该枚举值已在 Qt 6.8 中引入。
QRhi::ResolveDepthStencil43表示支持解析多采样深度或深度模版纹理。否则,setting a depth resolve texture 无法运行,必须避免。Direct 3D 11 和 12 不支持解析深度/深度模版格式,因此这两种格式永远不支持此功能。Vulkan 1.0 没有请求解析深度模版附件的 API。因此,对于 Vulkan,只有在 Vulkan 1.2 及以上版本,以及具有相应扩展的 1.1 实现中才能支持此功能。该功能是为极少数需要解析到非多重采样深度纹理的情况而提供的,例如在渲染到 OpenXR 提供的深度纹理(XR_KHR_composition_layer_depth)时。该枚举值在 Qt 6.8 中引入。
QRhi::VariableRateShading44表示支持每绘制(每管道)可变速率着色。当报告为支持时,QRhiCommandBuffer::setShadingRate() 是有效的,并对在其标志中声明QRhiGraphicsPipeline::UsesShadingRateQRhiGraphicsPipeline 对象有效。调用QRhi::supportedShadingRates() 检查支持哪些速率。(始终支持 1x1,其他典型值为 2x2、1x2、2x1、2x4、4x2、4x4)。如果运行时使用的实现和 GPU 支持 VRS,那么 Direct 3D 12 和 Vulkan 将支持该功能。此枚举值在 Qt 6.9 中引入。
QRhi::VariableRateShadingMap45表示可以基于图像指定着色率。图像 "不一定是纹理,也可能是本地 3D API 对象,这取决于运行时的底层后端和图形 API。实际上,Direct 3D 12、Vulkan 和 Metal 都支持这一功能,前提是 GPU 足够先进,可以支持 VRS。要检查是否支持 D3D12/Vulkan 风格的基于图像的 VRS,请使用 VariableRateShadingMapWithTexture 代替。当该功能被报告为支持时,有两种可能性:当 VariableRateShadingMapWithTexture 也为 true 时,QRhiShadingRateMap 通过 createFrom() 重载消耗QRhiTexture 对象,该重载使用QRhiTexture 参数。当 VariableRateShadingMapWithTexture 为假时,QRhiShadingRateMap 将使用其他类型的本地对象,例如在使用 Metal 时使用 MTLRasterizationRateMap。在这种情况下,请使用 createFrom() 重载获取 NativeShadingRateMap。该枚举值在 Qt 6.9 中引入。
QRhi::VariableRateShadingMapWithTexture46表示通过常规纹理支持基于图像的着色率指定。在实践中,Direct 3D 12 和 Vulkan 可能支持这种情况。该枚举值已在 Qt 6.9 中引入。
QRhi::PerRenderTargetBlending47表示支持每个渲染目标混合,即 MRT 帧缓冲中的不同渲染目标可以有不同的混合模式。在实践中,除了 OpenGL ES(仅在 GLES 3.2 实现中可用)外,其他地方都支持这种模式。该枚举值已在 Qt 6.9 中引入。
QRhi::SampleVariables48表示 gl_SampleID、gl_SamplePosition、gl_SampleMaskIn 和 gl_SampleMask 变量在片段着色器中可用。在实践中,除了 OpenGL ES(仅在 GLES 3.2 实现中可用)外,其他地方都支持该变量。该枚举值已在 Qt 6.9 中引入。

枚举 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,通常有一个 "Basic Render Driver(基本渲染驱动程序)"适配器(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,"管道缓存 "是通过检索和加载着色器程序二进制文件(如果驱动程序支持)来模拟的。Qt OpenGL 还为着色器/程序二进制文件提供了额外的、基于磁盘的缓存机制。由于将程序二进制文件存储到多个缓存中并不合理,因此只要设置此标记,写入这些缓存的操作就会被禁用。
QRhi::SuppressSmokeTestWarnings1 << 4表示在与此相关的后端中,某些非致命的QRhi::create() 故障不应产生qWarning() 调用。例如,在 D3D11 中,通过此标记可使大量警告信息(因QRhi::create() 失败而出现)变成常用的qt.rhi.general 日志类别下的调试打印分类。这可以被具有回退逻辑的引擎(如Qt Quick )使用,即使用一组不同的标志(如 PreferSoftwareRenderer)重试调用create() ,以便在第一次create() 尝试失败时,将无条件警告信息从输出中隐藏起来。

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

enum QRhi::FrameOpResult

描述可能出现软故障的操作结果。

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

enum QRhi::Implementation

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

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

enum QRhi::ResourceLimit

描述要查询的资源限制。

常数说明
QRhi::TextureSizeMin1纹理的最小宽度和高度。这通常是 1。最小纹理尺寸会以优雅的方式处理,也就是说,如果尝试创建一个空尺寸的纹理,则会创建一个最小尺寸的纹理。
QRhi::TextureSizeMax2最大纹理宽度和高度。这取决于图形 API,有时也取决于平台或实现。通常情况下,该值范围在 4096 - 16384 之间。如果试图创建大于此值的纹理,则会失败。
QRhi::MaxColorAttachments3QRhiTextureRenderTarget 的最大颜色附件数,以防支持多个渲染目标。不支持 MRT 时,该值为 1。否则,该值通常为 8,但要注意的是,OpenGL 仅规定 4 为最小值,而这正是某些 OpenGL ES 实现所提供的。
QRhi::FramesInFlight4后端可保持 "飞行中 "的帧数:对于 Vulkan 或 Metal 等后端,每当启动一个新帧并发现 CPU 已经比 GPU 超前N - 1 帧(因为在current -N 帧中提交的命令缓冲区尚未完成)时,QRhi 有责任阻塞。N 值是从这里返回的,通常为 2。这可能与直接使用图形 API 完成渲染的应用程序有关,因为此类渲染代码可能希望对资源(如缓冲区)执行双倍(如果值为 2)缓冲,这与QRhi 后端本身类似。当前帧槽索引(数值依次为 0、1、......、N-1,然后绕一圈)可从QRhi::currentFrameSlot() 中获取。对于图形 API 无法对命令提交过程进行低级控制的后端,该值为 1。请注意,即使该值为 1,流水线操作仍有可能发生(某些后端(如 D3D11)的设计目的是尝试启用流水线操作,例如,对统一缓冲区使用一种不会使流水线停滞的更新策略),但这不受QRhi 的控制,因此不会反映在 API 中。
QRhi::MaxAsyncReadbackFrames5保证异步纹理或缓冲区回读在starting a new frame 后完成的submitted 帧数(包括包含回读的帧数)。
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::MaxThreadGroupZ10Z 维度工作/线程组的最大大小。即计算着色器中local_size_z 的最大值。通常为 64 或 256。
QRhi::TextureArraySizeMax11最大纹理阵列大小。通常在 256 - 2048 之间。尝试使用更多元素的create a texture array 可能会失败。
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。
QRhi::ShadingRateImageTileSize15着色率纹理的平铺大小。如果不支持QRhi::VariableRateShadingMapWithTexture 功能,则为 0。否则为 0,例如 16,表示磁贴大小为 16x16。(R8UI) 着色率纹理中的每个字节都定义了 16x16 像素平铺的着色率。详见QRhiShadingRateMap

成员函数文档

[noexcept] QRhi::~QRhi()

销毁器销毁后台并释放资源。

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

注册一个callback ,在QRhi 销毁或调用runCleanup() 时调用。

回调将在图形资源仍然可用的情况下运行,因此这就为应用程序提供了一个机会,可以干净利落地释放属于QRhiQRhiResource 实例。这对于管理存储在cache 类型对象中的资源的生命周期特别有用,在这种情况下,缓存会保存 QRhiResources 或包含 QRhiResources 的对象。

另请参阅 runCleanup() 和~QRhi()。

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

这是一个重载函数。

它注册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 并不归调用者所有。

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

在屏幕应用程序中也可以使用(如beginFrame,endFrame, beginOffscreenFrame,endOffscreenFrame,beginFrame, ......),但这样会降低并行性,因此只能偶尔使用。

当 GPU 仍在处理前一个帧时,屏幕外帧不会让 CPU 产生另一个帧。这样做的副作用是,如果安排了回读,那么一旦endOffscreenFrame() 返回,就能保证结果可用。以交换链为目标的帧则不是这种情况:在这种情况下,GPU 有可能得到更好的利用,但在处理回读操作时,应用程序需要更加小心,因为endFrame() 与endOffscreenFrame() 不同,不能保证回读的结果在此时可用。

在没有交换链的情况下渲染帧,然后回读帧内容的骨架如下所示:

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

返回一个矩阵,允许应用程序继续使用以 OpenGL 为目标的顶点数据和透视投影矩阵(例如,由QMatrix4x4::perspective()) 生成的矩阵),而无需考虑活动的QRhi 后端。

在典型的渲染器中,一旦使用了this_matrix * mvp 而不仅仅是mvp ,就可以使用 Y 向上的顶点数据和深度范围为 0 - 1 的视口,而无需考虑运行时将使用哪个后端(以及图形 API)。这样就可以避免基于isYUpInNDC() 和isClipDepthZeroToOne() 的分支(尽管在实施某些高级图形技术时可能仍然需要这种逻辑)。

有关从 Vulkan 角度讨论该主题的内容,请参见本页

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

Returns a newQRhi instance with a backend for the graphics API specified byimpl with the specifiedflags 。如果函数失败,则返回nullptr

params 必须指向 后端特定子类之一的实例,如 , , , , 。有关创建 的示例,请参阅这些类。QRhiInitParams QRhiVulkanInitParams QRhiMetalInitParams QRhiD3D11InitParams QRhiD3D12InitParams QRhiGles2InitParams QRhi

QRhi 如果指定的 API 无法初始化,create() 就会失败,后端会在调试输出中打印警告。 的客户端(例如 )可能会根据平台提供额外的逻辑,允许回退到与请求不同的 API。如果只是为了在以后调用 create() 时测试初始化是否成功,最好使用 () 而不是 create(),因为对于某些后端,探测可以以更轻量级的方式实现,而 create()则会执行基础设施的完全初始化,如果 实例被立即丢弃,则会造成浪费。QRhi Qt Quick probe QRhi

importDevice 允许使用已有的图形设备,而无需 创建自己的图形设备。该参数不为空时,必须指向 的子类之一的实例:, , , , 。具体细节和语义取决于后台和底层图形 API。QRhi QRhiNativeHandles QRhiVulkanNativeHandles QRhiD3D11NativeHandles QRhiD3D12NativeHandles QRhiMetalNativeHandles QRhiGles2NativeHandles

另请参阅 probe() 。

int QRhi::currentFrameSlot() const

返回录制帧时的当前帧槽索引。在活动帧之外调用时(即isRecordingFrame() 为false 时)未指定。

对于 Vulkan 或 Metal 等后端,每当开始新的帧并发现 CPU 已领先 GPUFramesInFlight - 1 帧(因为在current -FramesInFlight 帧中提交的命令缓冲尚未完成)时,QRhi 后端就有责任阻塞。

帧与帧之间可能会发生变化的资源(例如,支持QRhiBuffer 的本地缓冲区对象,其类型为QRhiBuffer::Dynamic )会有多个版本,这样,当前一帧仍在处理时提交的每一帧都可以使用自己的副本,从而避免了在准备帧时停滞流水线的需要。(GPU 中可能仍在使用的资源内容不应被触及,但总是等待前一帧完成会降低 GPU 的利用率,并最终降低性能和效率)。

从概念上讲,这有点类似于某些 C++ 容器和其他类型所使用的写时复制(copy-on-write)方案。它也可能类似于 OpenGL 或 Direct 3D 11 实现对某些类型对象的内部执行。

在实践中,Vulkan、Metal 和类似的QRhi 后端通过在QRhiResource 后面设置固定数量的本地资源(如 VkBuffer)slots 来实现这种双重(或三重)缓冲资源。然后,可以通过帧槽索引 0、1...、FramesInFlight-1 进行索引,然后再缠绕一圈。

所有这些对QRhi 的用户来说都是透明的。不过,直接使用图形 API 进行渲染的应用程序可能希望对自己的图形资源执行类似的双重或三重缓冲。如果知道最大飞行帧数(可通过resourceLimit() 获取)和当前帧(槽)索引(由该函数返回)的值,就可以轻松实现这一目的。

另请参见 isRecordingFrame()、beginFrame() 和endFrame()。

QRhiDriverInfo QRhi::driverInfo() const

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

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

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

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

flags 在某些情况下,可以选择使用"...... "来改变行为。传递 会跳过 Present 命令队列或调用 swapBuffers。QRhi::SkipPresent

成功时返回QRhi::FrameOpSuccess ,失败时返回另一个QRhi::FrameOpResult 值。其中一些错误应视为 "稍后再试 "类型的软错误:当返回QRhi::FrameOpSwapChainOutOfDate 时,应通过调用QRhiSwapChain::createOrResize() 来调整或更新 swapchain。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

实际上,false 只适用于 OpenGL,因为 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。

设备丢失通常在beginFrame(),endFrame() 或QRhiSwapChain::createOrResize() 中检测到,具体取决于后端和底层本地应用程序接口。最常见的是endFrame() ,因为这就是出现的位置。对于某些后端,QRhiSwapChain::createOrResize() 也可能因设备丢失而失败。因此,我们提供了这个函数,作为检查之前的操作是否检测到设备丢失的通用方法。

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

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

建立在QRhi 之上的 Qt 自身框架,如Qt Quick ,可望在发生设备丢失时进行处理并采取适当措施。如果图形资源(如纹理和缓冲区)的数据在 CPU 端仍然可用,应用程序可能根本不会注意到此类事件,因为图形资源可以无缝地重新初始化。不过,直接使用QRhi 的应用程序和程序库应做好准备,自行检查和处理设备丢失情况。

注: 对于 OpenGL,应用程序可能需要通过在QOpenGLContext 上设置QSurfaceFormat::ResetNotification 来选择接受上下文重置通知。这通常是通过启用QRhiGles2InitParams::format 中的标志来实现的。但请注意,即使未设置该标志,某些系统也可能会产生上下文重置情况。

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

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

bool QRhi::isRecordingFrame() const

当有一个活动帧时返回 true,这意味着有一个beginFrame() (或beginOffscreenFrame() ),但还没有相应的endFrame() (或endOffscreenFrame() )。

另请参阅 currentFrameSlot()、beginFrame() 和endFrame()。

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

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

非压缩格式和压缩格式都支持该查询。

bool QRhi::isYUpInFramebuffer() const

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

实际上,true 只适用于 OpenGL。

bool QRhi::isYUpInNDC() const

如果底层图形 API 的 Y 轴在其规范化设备坐标系中指向上方,则返回true

实际上,false 只适用于 Vulkan。

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

bool QRhi::makeThreadLocalNativeContextCurrent()

在使用 OpenGL 时,该函数将使 OpenGL 上下文成为当前线程上的当前上下文。该函数对其他后端没有影响。

调用此函数通常与 Qt Framework 代码相关,因为只要QRhi 使用 OpenGL 后端,就必须确保应用程序提供的外部 OpenGL 代码仍能像以前直接使用 OpenGL 时一样运行。

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

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

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

返回给定size 的 mip 级别数。

const QRhiNativeHandles *QRhi::nativeHandles()

返回指向后端特定本地对象集合的指针,该集合包含后端使用的设备、上下文和类似概念。

可根据情况转换为QRhiVulkanNativeHandles,QRhiD3D11NativeHandles,QRhiD3D12NativeHandles,QRhiGles2NativeHandlesQRhiMetalNativeHandles

注意: 无论是返回的指针还是任何本地对象,其所有权都不会转移。

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

用指定的typeusagesize 返回一个新的缓冲区。

注: 某些usagetype 组合可能不受所有后端支持。请参阅UsageFlagsthe feature flags

注: 后端可选择分配比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 ,然后解析到非 RGBA8QRhiTexture ,这意味着(对于某些图形 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 、mipmapping 模式mipmapMode 以及寻址(换行)模式addressU addressVaddressW

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

另请参阅 QRhiResource::destroy()。

QRhiShaderResourceBindings *QRhi::newShaderResourceBindings()

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

另请参见 QRhiResource::destroy().

[since 6.9] QRhiShadingRateMap *QRhi::newShadingRateMap()

返回一个新的着色率贴图对象。

此函数在 Qt 6.9 中引入。

QRhiSwapChain *QRhi::newSwapChain()

返回新的 swapchain。

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

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

使用指定的format,pixelSize,sampleCountflags 返回新的一维或二维纹理。

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

注: format 指定了所需的内部和外部格式,这意味着要上传到纹理的数据必须是兼容格式,而本地纹理可能(但不保证,至少在 OpenGL 的情况下)在内部使用这种格式。

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

另请参阅 QRhiResource::destroy().

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

使用指定的format,width,height,depth,sampleCountflags 返回新的一维、二维或三维纹理。

此重载适用于三维纹理,因为它允许指定depth 。三维纹理必须在flags 中设置QRhiTexture::ThreeDimensional ,但使用此重载时可以省略,因为只要depth 大于 0,就会隐式设置该标志。对于一维、二维和立方体纹理,depth 应设置为 0。

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

注: 只有在运行时报告支持ThreeDimensionalTextures 功能时,三维纹理才会起作用。

注: 只有在运行时报告支持OneDimensionalTextures 功能时,1D 纹理才会起作用。此外,只有在运行时报告支持OneDimensionalTextureMipmaps 功能时,一维纹理上的 mipmaps 才会起作用。

这是一个重载函数。

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

使用指定的format,arraySize,pixelSize,sampleCountflags 返回新的一维或二维纹理数组。

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

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

注意: 不要混淆纹理数组和纹理数组。此函数创建的QRhiTexture 可用于着色器中的一维或二维数组采样器,例如:layout(binding = 1) uniform sampler2DArray texArr; 。纹理数组指的是通过QRhiShaderResourceBinding::sampledTextures() 和计数 > 1 显示在着色器中的纹理列表,并在着色器中声明,例如像这样:layout(binding = 1) uniform sampler2D textures[4];

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

注: 只有在运行时报告支持OneDimensionalTextures 功能时,1D 纹理才会起作用。此外,只有在运行时报告支持OneDimensionalTextureMipmaps 功能时,1D 纹理上的 mipmaps 才会起作用。

另请参阅 newTexture().

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

Returns a new texture render target with color and depth/stencil attachments given indesc, and with the specifiedflags

另请参见 QRhiResource::destroy().

QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch()

返回一个可用的空批次,可记录复制类型的操作。

注意: 返回值不归调用者所有,绝对不能销毁。相反,通过将该批次传递给QRhiCommandBuffer::beginPass(),QRhiCommandBuffer::endPass(), 或QRhiCommandBuffer::resourceUpdate(), 或调用QRhiResourceUpdateBatch::release(), 该批次将返回到池中供重复使用。

注: 也可在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。当达到此限制时,函数将返回空值,直到有批次返回到池中。

QByteArray QRhi::pipelineCacheData()

返回二进制数据 Blob,其中包含从QRhiGraphicsPipelineQRhiComputePipeline 收集到的数据,这些数据是在QRhi 的生命周期内成功创建的。

通过保存缓存数据,然后在同一应用程序的后续运行中重新加载缓存数据,有可能缩短流水线和着色器的创建时间。缓存及其序列化版本具体包括哪些内容没有明确规定,总是与所使用的后端有关,在某些情况下还取决于图形 API 的特定实现。

PipelineCacheDataLoadSave 被报告为不支持时,返回的QByteArray 将是空的。

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

当返回的数据为非空时,它总是特定于 Qt XML 版本和QRhi 后端。此外,在某些情况下,图形设备和所使用的确切驱动程序版本之间存在很强的依赖性。QRhi 会添加适当的头和保障措施,确保数据始终能安全地传递到setPipelineCacheData() ,因此,尝试从另一版本的驱动程序上运行的数据中加载数据时,将得到安全、优雅的处理。

注意: 调用releaseCachedResources() 可能会清除收集到的管道数据,具体取决于后端。随后对该函数的调用可能不会返回任何数据。

有关此功能的更多详情,请参阅EnablePipelineCacheDataSave

注意: 尽量减少调用此函数的次数。读取 blob 并不总是便宜的操作,因此调用该函数的频率不应过高,最好只调用一次,例如在关闭程序时。

另请参见 setPipelineCacheData()、create() 和isFeatureSupported()。

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

如果create() 在调用给定的implparams 时有望成功,则返回 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 对象都将使用该管道缓存对象,从而有可能加快管道创建速度。

其他应用程序接口没有真正的管道缓存,但它们可能会提供一个来自着色器编译(D3D)或程序二进制文件(OpenGL)的字节码缓存。在运行时从源代码执行大量着色器编译的应用程序中,如果 "流水线缓存 "是从使用此功能的早期运行中预置的,那么在后续运行中就能显著提高性能。

注意: QRhi 不能保证data 会对管道和着色器创建性能产生影响。对于 Vulkan 等应用程序接口,驱动程序可自行决定是否出于某种目的使用data ,或者是否将其忽略。

有关此功能的更多详情,请参阅EnablePipelineCacheDataSave

注意: QRhi 提供的这一机制与驱动程序自身的内部缓存机制(如果有的话)无关。这意味着,根据图形应用程序接口及其实现的不同,检索和重新加载data 的确切效果无法预测。如果 Qt 控制之外的其他缓存机制已经激活,那么性能的提升可能根本无法显现。

注意: 尽量减少调用此函数的次数。加载 blob 并不总是一个便宜的操作,因此只应在较低频率下调用此函数,最好只调用一次,例如在启动应用程序时。

警告: 序列化管道缓存数据被假定为可信内容。Qt XML 会对data 中包含的标头和元数据进行稳健的解析,但建议应用程序开发人员切勿从不可信的来源传递数据。

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

[since 6.9] void QRhi::setQueueSubmitParams(QRhiNativeHandles *params)

对于适用的后端和图形 API,该函数允许为下一次向图形命令队列提交命令提供额外参数。

特别是在使用 Vulkan 时,该函数允许传入一个 Vulkan semaphore 对象列表,供vkQueueSubmit() 发送信号和等待。params 必须是QRhiVulkanQueueSubmitParams 。这在某些高级用例中非常重要,例如在执行原生 Vulkan 调用时,需要等待应用程序自定义 Vulkan 渲染或计算代码管理的 VkSemaphores 并向其发送信号。此外,这还允许在下一个vkQueuePresentKHR() 中指定额外的 semaphores 等待。

注意: 此函数仅影响下一次队列提交,这将发生在endFrame(),endOffscreenFrame() 或finish() 中。当前队列的排序发生在endFrame() 中。

在许多其他后端中,该函数的实现是无操作的。

此函数在 Qt 6.9 中引入。

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

返回给定mipLevel 的纹理图像尺寸,该尺寸根据baseLevelSize 中给定的第 0 级尺寸计算得出。

QRhiStats QRhi::statistics() const

收集并返回有关图形资源定时和分配的统计数据。

有关内存分配的数据仅适用于某些后端,在这些后端中,此类操作由 Qt 控制。如果图形应用程序接口对资源内存分配没有下级控制,则永远不支持这种操作,结果中的所有相关字段都为 0。

特别是对于 Vulkan,这些值始终有效,并从底层内存分配器库中查询。这样就能了解活动缓冲区和纹理的内存需求。

Direct 3D 12 也是如此。除了内存分配器库的统计数据外,这里的结果还包括一个totalUsageBytes 字段,该字段报告的总大小包括 DXGI 报告的不受内存分配器库控制的其他资源(交换链缓冲区、描述符堆等)。

这些值对应的是所使用的所有内存类型的总和。(在独立 GPU 的情况下,即视频 + 系统)。

大多数后端都能提供其他数据,如创建图形和计算流水线(通常涉及着色器编译或缓存查找,以及潜在的昂贵处理)所花费的总时间(以毫秒为单位)。

注意: 流水线创建等操作的耗时可能会受到各种因素的影响。不同后端之间的结果不应进行比较,因为 "流水线 "的概念以及在调用QRhiGraphicsPipeline::create() 等操作过程中发生的具体情况,在图形应用程序接口及其实现之间存在很大差异。

注: 此外,许多驱动程序可能会对着色器、程序和管道采用不同的缓存策略。(独立于 Qt XML 自身的类似设施,如setPipelineCacheData() 或 OpenGL 专用的程序二进制磁盘缓存)。由于此类内部行为对 API 客户端是透明的,因此 Qt 和QRhi 对具体的缓存策略、持久性、缓存数据的失效等一无所知,也无法控制。在读取时间(如创建管道所花费的时间)时,应注意驱动程序级缓存机制的潜在存在和未指定行为。

QList<int> QRhi::supportedSampleCounts() const

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

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

对于某些后端,该支持值列表是预先确定的,而对于其他一些后端,(物理)设备属性会在运行时显示支持的值。

另请参见 QRhiRenderBuffer::setSampleCount()、QRhiTexture::setSampleCount()、QRhiGraphicsPipeline::setSampleCount() 和QRhiSwapChain::setSampleCount()。

[since 6.9] QList<QSize> QRhi::supportedShadingRates(int sampleCount) const

返回指定sampleCount 支持的可变阴影率列表。

始终支持 1x1。

此函数在 Qt 6.9 中引入。

QThread *QRhi::thread() const

返回QRhi 所在的线程initialized

int QRhi::ubufAligned(int v) const

返回vubufAlignment() 给出的统一缓冲区对齐方式对齐的值(通常是偏移量)。

int QRhi::ubufAlignment() const

返回以字节为单位的最小统一缓冲区偏移对齐方式。通常为 256。

尝试绑定偏移量与此值不一致的统一缓冲区会导致失败,具体取决于后端和底层图形 API。

另请参阅 ubufAligned().

[static] QRhiSwapChainProxyData QRhi::updateSwapChainProxyData(QRhi::Implementation impl, QWindow *window)

生成并返回一个QRhiSwapChainProxyData 结构,其中包含由impl 指定的后端和图形 API 特有的不透明数据。window 是交换链针对的QWindow

返回的结构体可传递给QRhiSwapChain::setProxyData() 。这在线程渲染系统中是合理的:与所有QRhi 操作不同,该静态函数预计将在主(gui)线程上调用,然后转给处理QRhiQRhiSwapChain 的线程,并传递给 swapchain。这样就可以在主线程上安全调用本地平台查询,例如从 NSView 查询 CAMetalLayer,然后将数据传递给渲染线程上的QRhiSwapChain 。在 Metal 示例中,在专用渲染线程上进行 view.layer 访问会导致 Xcode 线程检查器发出警告。而使用数据代理机制则可以避免这种情况。

当不涉及线程时,不需要生成和传递QRhiSwapChainProxyData :后端保证能够自行查询所需的任何内容,如果所有内容都在主(gui)线程上,那就足够了。

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

相关非会员

[alias, since 6.7] QRhiShaderResourceBindingSet

QRhiShaderResourceBindings 的同义词。

该类型定义在 Qt 6.7 中引入。

© 2025 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.