RHIウィンドウの例

この例では、QRhi を使用して最小限のQWindow ベースのアプリケーションを作成する方法を示します。

Qt 6.6 では、アプリケーション用にアクセラレーションされた 3D API とシェーダ抽象化レイヤーの提供を開始しました。アプリケーションは、Qt Quick scenegraph や Qt Quick 3D エンジンを実装するために Qt 自身が使用しているのと同じ 3D グラフィックスクラスを使用できるようになりました。以前のバージョンの Qt では、QRhi と関連するクラスはすべてプライベート API でした。6.6以降では、これらのクラスはQPAファミリーのクラスと同じようなカテゴリに分類されます。完全なパブリックでもプライベートでもなく、その中間的なもので、パブリックAPIに比べて互換性の約束が制限されています。一方、QRhi と関連クラスには、パブリックAPIと同様に完全なドキュメントが付属するようになった。

QRhi QWindowQt Quick、Qt Quick 3D、Widgets を一切使用せず、レンダリングとウィンドウインフラストラクチャをすべてアプリケーションにセットアップします。

これとは対照的に、Qt Quick や Qt Quick 3D を使用して QML アプリケーションを作成し、QRhi ベースのレンダリングを追加する場合、Qt Quick がすでに初期化しているウィンドウとレンダリングのインフラストラクチャに依存することになり、QQuickWindow から既存のQRhi インスタンスを照会することになります。QRhi::create() の処理、Vulkan instances などのプラットフォーム/API 特有の処理、expose とウィンドウのサイズ変更イベントの正しい処理などは、すべて Qt Quick によって管理されます。一方、この例では、アプリケーション自体によって管理され、処理されます。

注意: 特にQWidget ベースのアプリケーションでは、QWidget::createWindowContainer() を使用することで、ウィジェットベースのユーザーインターフェイスにQWindow (ネイティブウィンドウでバックアップされた) を埋め込むことができます。したがって、この例のHelloWindow クラスは、main() からの必要な初期化が適切に行われていれば、QWidget ベースのアプリケーションでも再利用可能です。

3D APIサポート

このアプリケーションは、現在のすべてのQRhi backends をサポートしています。コマンドライン引数が指定されていない場合、プラットフォーム固有のデフォルトが使用されます:Windows では Direct 3D 11、Linux では OpenGL、macOS/iOS では Metal が使用されます。

--help で実行すると、使用可能なコマンドラインオプションが表示されます:

  • Direct 3D 11の場合は-dまたは-d3d11。
  • -Dまたは-d3d12(Direct 3D 12用
  • Metalの場合は-mまたは-metal
  • -vまたは-vulkan(Vulkan用
  • -gまたは-openglはOpenGLまたはOpenGL ESを表します。
  • -n または -nullNull backend

ビルド・システム

このアプリケーションはQt GUIモジュールにのみ依存しています。Qt WidgetsやQt Quickは使用しません。

すべてのQtアプリケーションで利用可能ですが、限定的な互換性が約束されているRHI APIにアクセスするために、target_link_libraries CMakeコマンドはQt6::GuiPrivate 。これにより、#include <rhi/qrhi.h> のinclude文が正常にコンパイルできるようになります。

機能

アプリケーションの特徴

  • サイズ変更可能なQWindow
  • ウィンドウのサイズに適切に追従するスワップチェーンとデプスステンシルバッファ、
  • QExposeEventQPlatformSurfaceEvent などのイベントに基づいて、適切なタイミングで初期化、レンダリング、ティアダウンを行うロジック、
  • QImage QPainter (ラスターペイントエンジンを使用。つまり、画像のピクセルデータの生成はすべてCPUベースで行われ、そのデータはGPUテクスチャにアップロードされます)、
  • ブレンディングと深度テストを有効にした三角形を、透視投影を使用して、フレームごとに変化するモデル変換を適用しながらレンダリングします、
  • requestUpdate() を使った効率的なクロスプラットフォームのレンダリングループ。

シェーダー

このアプリケーションは、2 組の頂点シェーダとフラグメントシェーダを使用します:

  • ひとつはフルスクリーンの四角形用で、頂点入力を使わず、フラグメントシェーダがテクスチャをサンプリングします (quad.vert,quad.frag)、
  • 頂点位置と色が頂点バッファに提供され、モデルビューと投影行列が一様バッファに提供されます (color.vert,color.frag)。

シェーダは、Vulkan互換のGLSLソースコードとして記述されています。

Qt GUI モジュールのサンプルであるため、このサンプルはQt Shader Toolsモジュールに依存できません。これは、qt_add_shaders のような CMake ヘルパー関数が使用できないことを意味します。そのため、このサンプルでは、shaders/prebuilt フォルダにプリプロセスされた.qsb ファイルが含まれており、qt_add_resources を介して実行ファイルに単純にインクルードされます。qt_add_shaders を使用すると、.qsb ファイルを手動で生成して管理する必要がなくなります。

この例では、.qsb ファイルを生成するために、qsb --qt6 color.vert -o prebuilt/color.vert.qsb などのコマンドを使用しました。これによりSPIR-Vにコンパイルされ、GLSL(100 es120 )、HLSL(5.0)、MSL(1.2)にトランスパイルされます。すべてのシェーダバージョンは、QShader にまとめられ、ディスクにシリアライズされます。

API 固有の初期化

いくつかの3D APIでは、main()関数は適切なAPI固有の初期化を実行する必要があります。たとえば、Vulkanを使用する場合は、QVulkanInstance 。OpenGL では、深度バッファが利用可能であることを確認する必要があり、これはQSurfaceFormat を介して行われます。OpenGL や Vulkan 用のQRhi バックエンドは、QOpenGLContextQVulkanInstance のような既存の Qt 機能をベースにしているため、これらの手順はQRhi の範囲ではありません。

   // For OpenGL, to ensure there is a depth/stencil buffer for the window.
   // With other APIs this is under the application's control (QRhiRenderBuffer etc.)
   // and so no special setup is needed for those.
   QSurfaceFormat fmt;
   fmt.setDepthBufferSize(24);
   fmt.setStencilBufferSize(8);
   // Special case macOS to allow using OpenGL there.
   // (the default Metal is the recommended approach, though)
   // gl_VertexID is a GLSL 130 feature, and so the default OpenGL 2.1 context
   // we get on macOS is not sufficient.
#ifdef Q_OS_MACOS
   fmt.setVersion(4, 1);
   fmt.setProfile(QSurfaceFormat::CoreProfile);
#endif
   QSurfaceFormat::setDefaultFormat(fmt);

   // For Vulkan.
#if QT_CONFIG(vulkan)
   QVulkanInstance inst;
   if (graphicsApi == QRhi::Vulkan) {
       // Request validation, if available. This is completely optional
       // and has a performance impact, and should be avoided in production use.
       inst.setLayers({ "VK_LAYER_KHRONOS_validation" });
       // Play nice with QRhi.
       inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
       if (!inst.create()) {
           qWarning("Failed to create Vulkan instance, switching to OpenGL");
           graphicsApi = QRhi::OpenGLES2;
       }
   }
#endif

注意: Vulkan については、適切な拡張が有効になっていることを確認するために、QRhiVulkanInitParams::preferredInstanceExtensions() がどのように考慮されているかに注意してください。

HelloWindow は のサブクラスで、 は のサブクラスです。 は、スワップチェーン(および深度ステンシルバッファ)を持つリサイズ可能なウィンドウを管理するために必要なすべてを含んでおり、他のアプリケーションでも再利用できる可能性があります。 は、この特定のサンプルアプリケーションに固有のレンダリングロジックを含んでいます。RhiWindow QWindow RhiWindow HelloWindow

QWindow サブクラスのコンストラクタでは、選択した 3D API に基づいてサーフェス タイプが設定されます。

RhiWindow::RhiWindow(QRhi::Implementation graphicsApi)
    : m_graphicsApi(graphicsApi)
{
    switch (graphicsApi) {
    case QRhi::OpenGLES2:
        setSurfaceType(OpenGLSurface);
        break;
    case QRhi::Vulkan:
        setSurfaceType(VulkanSurface);
        break;
    case QRhi::D3D11:
    case QRhi::D3D12:
        setSurfaceType(Direct3DSurface);
        break;
    case QRhi::Metal:
        setSurfaceType(MetalSurface);
        break;
    case QRhi::Null:
        break; // RasterSurface
    }
}

QRhi オブジェクトの作成と初期化はRhiWindow::init()で実装されています。expose eventこれはウィンドウがrenderable のときのみ呼び出されることに注意してください。

どの3D APIを使用するかによって、適切なInitParams構造体をQRhi::create()に渡す必要があります。たとえばOpenGLでは、QOffscreenSurface (または他のQSurface )がアプリケーションによって作成され、QRhi に使用するために提供されなければなりません。Vulkanでは、正常に初期化されたQVulkanInstance が必要です。その他、Direct 3D や Metal などは、初期化するための追加情報を必要としません。

void RhiWindow::init()
{
    if (m_graphicsApi == QRhi::Null) {
        QRhiNullInitParams params;
        m_rhi.reset(QRhi::create(QRhi::Null, &params));
    }

#if QT_CONFIG(opengl)
    if (m_graphicsApi == QRhi::OpenGLES2) {
        m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
        QRhiGles2InitParams params;
        params.fallbackSurface = m_fallbackSurface.get();
        params.window = this;
        m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params));
    }
#endif

#if QT_CONFIG(vulkan)
    if (m_graphicsApi == QRhi::Vulkan) {
        QRhiVulkanInitParams params;
        params.inst = vulkanInstance();
        params.window = this;
        m_rhi.reset(QRhi::create(QRhi::Vulkan, &params));
    }
#endif

#ifdef Q_OS_WIN
    if (m_graphicsApi == QRhi::D3D11) {
        QRhiD3D11InitParams params;
        // Enable the debug layer, if available. This is optional
        // and should be avoided in production builds.
        params.enableDebugLayer = true;
        m_rhi.reset(QRhi::create(QRhi::D3D11, &params));
    } else if (m_graphicsApi == QRhi::D3D12) {
        QRhiD3D12InitParams params;
        // Enable the debug layer, if available. This is optional
        // and should be avoided in production builds.
        params.enableDebugLayer = true;
        m_rhi.reset(QRhi::create(QRhi::D3D12, &params));
    }
#endif

#if QT_CONFIG(metal)
    if (m_graphicsApi == QRhi::Metal) {
        QRhiMetalInitParams params;
        m_rhi.reset(QRhi::create(QRhi::Metal, &params));
    }
#endif

    if (!m_rhi)
        qFatal("Failed to create RHI backend");

これ以外のすべてのレンダリングコードは、完全にクロスプラットフォームであり、3D API特有の分岐や条件はありません。

イベントの公開

renderable が具体的に何を意味するかは、プラットフォームによって異なる。たとえば、macOSでは、完全に隠されている(他のウィンドウの後ろに完全に隠れている)ウィンドウはレンダリングできませんが、Windowsでは隠されていることに意味はありません。幸いなことに、アプリケーションはこれについて特別な知識を必要としません:Qtのプラットフォーム・プラグインは、exposeイベントの背後にある違いを抽象化します。しかし、exposeEvent ()の再実装は、空の出力サイズ(例えば、幅と高さが0)もレンダリング不可能な状況として扱われるべきものであることを認識する必要があります。たとえばWindowsでは、ウィンドウを最小化するときにこのようなことが起こります。そのため、QRhiSwapChain::surfacePixelSize ()に基づいたチェックが行われる。

このexposureイベント処理の実装は、堅牢で、安全で、ポータブルであることを試みています。Qt Quick自体もレンダリングループで非常に似たロジックを実装しています。

void RhiWindow::exposeEvent(QExposeEvent *)
{
    // initialize and start rendering when the window becomes usable for graphics purposes
    if (isExposed() && !m_initialized) {
        init();
        resizeSwapChain();
        m_initialized = true;
    }

    const QSize surfaceSize = m_hasSwapChain ? m_sc->surfacePixelSize() : QSize();

    // stop pushing frames when not exposed (or size is 0)
    if ((!isExposed() || (m_hasSwapChain && surfaceSize.isEmpty())) && m_initialized && !m_notExposed)
        m_notExposed = true;

    // Continue when exposed again and the surface has a valid size. Note that
    // surfaceSize can be (0, 0) even though size() reports a valid one, hence
    // trusting surfacePixelSize() and not QWindow.
    if (isExposed() && m_initialized && m_notExposed && !surfaceSize.isEmpty()) {
        m_notExposed = false;
        m_newlyExposed = true;
    }

    // always render a frame on exposeEvent() (when exposed) in order to update
    // immediately on window resize.
    if (isExposed() && !surfaceSize.isEmpty())
        render();
}

requestUpdate() によって生成されたUpdateRequest イベントに応答して呼び出される RhiWindow::render() では、スワップチェーンの初期化に失敗したときや、ウィンドウがレンダリング不可能になったときにレンダリングしようとしないように、次のようなチェックが行われています。

void RhiWindow::render()
{
    if (!m_hasSwapChain || m_notExposed)
        return;

スワップチェーン、デプス鉛筆バッファ、およびリサイズ

QWindow にレンダリングするには、QRhiSwapChain が必要である。さらに、このアプリケーションは、グラフィックスパイプラインでデプステ ストを有効にする方法を示すため、デプステンシルバッファとして動作するQRhiRenderBuffer も作成されます。いくつかのレガシー 3D API では、ウィンドウのための深度/ステンシルバッファの管理は、対応するウィンドウシステムインタフェース API の一部です(EGL、WGL、GLX など、つまり深度/ステンシルバッファは暗黙的にwindow surface とともに管理されます)。一方、最新の API では、ウィンドウベースのレンダーターゲットのための深度/ステンシルバッファの管理は、オフスクリーンレンダーターゲットと変わりません。QRhi はこれを抽象化しますが、最良のパフォーマンスのためには、QRhiRenderBufferused with together with a QRhiSwapChain であることを示す必要があります。

QRhiSwapChain は、QWindow と深度/ステンシルバッファに関連付けられています。

    std::unique_ptr<QRhiSwapChain> m_sc;
    std::unique_ptr<QRhiRenderBuffer> m_ds;
    std::unique_ptr<QRhiRenderPassDescriptor> m_rp;

    m_sc.reset(m_rhi->newSwapChain());
    m_ds.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
                                      QSize(), // no need to set the size here, due to UsedWithSwapChainOnly
                                      1,
                                      QRhiRenderBuffer::UsedWithSwapChainOnly));
    m_sc->setWindow(this);
    m_sc->setDepthStencil(m_ds.get());
    m_rp.reset(m_sc->newCompatibleRenderPassDescriptor());
    m_sc->setRenderPassDescriptor(m_rp.get());

ウィンドウ・サイズが変更されると、スワップチェーンもサイズ変更される必要がある。これは resizeSwapChain() で実装される。

void RhiWindow::resizeSwapChain()
{
    m_hasSwapChain = m_sc->createOrResize(); // also handles m_ds

    const QSize outputSize = m_sc->currentPixelSize();
    m_viewProjection = m_rhi->clipSpaceCorrMatrix();
    m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
    m_viewProjection.translate(0, 0, -4);
}

他のQRhiResource サブクラスとは異なり、QRhiSwapChain は、create 関数に関して若干異なるセマンティクスを備えている。createOrResize() という名前が示すように、これは、出力ウィンドウ・サイズがスワップチェーンが最後に初期化されたものと同期していない可能性があることが分かっているときはいつでも呼び出される必要がある。関連するdepth-stencil用のQRhiRenderBuffer は自動的にsize が設定され、create() はswapchainのcreateOrResize()から暗黙的に呼び出される。

設定した透視投影は出力アスペクト比に依存するため、ここは投影とビュー行列を(再)計算するのに便利な場所でもあります。

注意: 座標系の違いをなくすために、QRhi からa backend/API-specific "correction" matrix を取得し、投影行列にベイクインしています。これにより、原点が左下にある座標系を想定したOpenGLスタイルの頂点データでアプリケーションを動作させることができます。

resizeSwapChain()関数はRhiWindow::render()から、現在報告されているサイズがswapchainが最後に初期化されたものと同じでないことが判明したときに呼び出されます。

詳細はQRhiSwapChain::currentPixelSize() とQRhiSwapChain::surfacePixelSize() を参照。

高DPIサポートはビルトインされています。ウィンドウ固有のscale factor を考慮した上で、名前が示すようにサイズは常にピクセル単位です。QRhi (および3D API)レベルでは、高DPIスケーリングの概念はなく、すべてが常にピクセル単位です。つまり、size() が 1280x720 で devicePixelRatio() が 2 のQWindow は、(ピクセル) サイズが 2560x1440 のレンダーターゲット (swapchain) になります。

    // If the window got resized or newly exposed, resize the swapchain. (the
    // newly-exposed case is not actually required by some platforms, but is
    // here for robustness and portability)
    //
    // This (exposeEvent + the logic here) is the only safe way to perform
    // resize handling. Note the usage of the RHI's surfacePixelSize(), and
    // never QWindow::size(). (the two may or may not be the same under the hood,
    // depending on the backend and platform)
    //
    if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) {
        resizeSwapChain();
        if (!m_hasSwapChain)
            return;
        m_newlyExposed = false;
    }

レンダリングループ

アプリケーションは、プレゼンテーションレート(vsync)によってスロットルされながら、連続的にレンダリングします。これは、現在記録されているフレームが送信されたときにRhiWindow::render()からrequestUpdate()を呼び出すことによって保証されます。

    m_rhi->endFrame(m_sc.get());

    // Always request the next frame via requestUpdate(). On some platforms this is backed
    // by a platform-specific solution, e.g. CVDisplayLink on macOS, which is potentially
    // more efficient than a timer, queued metacalls, etc.
    requestUpdate();
}

これは最終的にUpdateRequest イベントを取得することにつながります。これはevent()の再実装で処理されます。

bool RhiWindow::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::UpdateRequest:
        render();
        break;

    case QEvent::PlatformSurface:
        // this is the proper time to tear down the swapchain (while the native window and surface are still around)
        if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
            releaseSwapChain();
        break;

    default:
        break;
    }

    return QWindow::event(e);
}

リソースとパイプラインのセットアップ

アプリケーションは、2つの異なるグラフィックス・パイプラインで、2つの描画呼び出しを発行する単一のレンダー・パスを記録します。1つは "背景 "で、QPainter-生成された画像を含むテクスチャがあり、その上に単一の三角形がブレンディングを有効にしてレンダリングされます。

三角形で使用される頂点とユニフォームバッファはこのように作成されます。ユニフォーム・バッファのサイズは、シェーダがユニフォーム・ブロックのmat4float メンバを指定しているため、68 バイトです。std140のレイアウト規則に注意してください。この例では、mat4 の後に続くfloat メンバが、追加のパディングなしで正しいアライメントを持っているため、これは驚くようなことではありませんが、他のアプリケーション、特にvec2vec3 のような型を扱うときに関連する可能性があります。疑問がある場合は、QShaderQShaderDescription をチェックするか、-d 引数を指定して.qsb ファイルでqsb ツールを実行し、人間が読める形式でメタデータを検査する方が便利です。出力される情報には、特に、ユニフォーム・ブロック・メンバーのオフセット、サイズ、および各ユニフォーム・ブロックのバイト単位の合計サイズが含まれます。

void HelloWindow::customInit()
{
    m_initialUpdates = m_rhi->nextResourceUpdateBatch();

    m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
    m_vbuf->create();
    m_initialUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);

    static const quint32 UBUF_SIZE = 68;
    m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE));
    m_ubuf->create();

頂点シェーダとフラグメントシェーダの両方が、バインディングポイント0にユニフォームバッファを必要とします。これは、QRhiShaderResourceBindings オブジェクトによって保証されます。グラフィックスパイプラインは、シェーダと多くの追加情報でセットアップされます。例えば、プリミティブのトポロジーはTriangles ですが、これはデフォルトであるため、明示的に設定されていません。詳しくはQRhiGraphicsPipeline を参照のこと。

トポロジーとさまざまな状態を指定することに加えて、パイプラインも関連付ける必要があります:

  • 頂点入力レイアウトは、QRhiVertexInputLayout の形式で指定します。これは、各頂点入力位置のタイプとコンポーネント数、頂点ごとのバイト単位の合計ストライド、およびその他の関連データを指定します。QRhiVertexInputLayout はデータを保持するだけで、実際のネイティブ・リソースを保持するわけではなく、コピー可能です。
  • 有効で正常に初期化されたQRhiShaderResourceBindings オブジェクト。これは、シェーダーが期待するリソースバインディング(ユニフォームバッファ、テクスチャ、サンプラー)のレイアウトを記述します。これは、描画コールを記録するときに使用されるQRhiShaderResourceBindings か、またはlayout-compatible with it である別のものでなければなりません。この単純なアプリケーションは、前者のアプローチをとります。
  • 有効なQRhiRenderPassDescriptor オブジェクト。これはレンダーターゲットから取得するか、be compatible with 。この例では、QRhiSwapChain::newCompatibleRenderPassDescriptor() を介してQRhiRenderPassDescriptor オブジェクトを作成することで、前者を使用しています。
   m_colorTriSrb.reset(m_rhi->newShaderResourceBindings());
   static const QRhiShaderResourceBinding::StageFlags visibility =
           QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
   m_colorTriSrb->setBindings({
           QRhiShaderResourceBinding::uniformBuffer(0, visibility, m_ubuf.get())
   });
   m_colorTriSrb->create();

   m_colorPipeline.reset(m_rhi->newGraphicsPipeline());
   // Enable depth testing; not quite needed for a simple triangle, but we
   // have a depth-stencil buffer so why not.
   m_colorPipeline->setDepthTest(true);
   m_colorPipeline->setDepthWrite(true);
   // Blend factors default to One, OneOneMinusSrcAlpha, which is convenient.
   QRhiGraphicsPipeline::TargetBlend premulAlphaBlend;
   premulAlphaBlend.enable = true;
   m_colorPipeline->setTargetBlends({ premulAlphaBlend });
   m_colorPipeline->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) }
   });
   m_colorPipeline->setVertexInputLayout(inputLayout);
   m_colorPipeline->setShaderResourceBindings(m_colorTriSrb.get());
   m_colorPipeline->setRenderPassDescriptor(m_rp.get());
   m_colorPipeline->create();

getShader() は、.qsb ファイルをロードし、そこからQShader をデシリアライズするヘルパー関数です。

static QShader getShader(const QString &name)
{
    QFile f(name);
    if (f.open(QIODevice::ReadOnly))
        return QShader::fromSerialized(f.readAll());

    return QShader();
}

color.vert シェーダは、頂点入力として以下を指定します:

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

ただし、C++のコードでは、頂点データは位置用に2つの浮動小数点数、色用に3つの浮動小数点数がインターリーブされています。(x,y,r,g,b 各頂点に対応) このため、ストライドは5 * sizeof(float) となり、位置 0 と 1 の入力はそれぞれFloat2Float3 と指定されます。これは有効で、vec4 位置のzw は自動的に設定されます。

レンダリング

フレームの記録は、QRhi::beginFrame ()を呼び出すことで開始され、QRhi::endFrame ()を呼び出すことで終了する。

    QRhi::FrameOpResult result = m_rhi->beginFrame(m_sc.get());
    if (result == QRhi::FrameOpSwapChainOutOfDate) {
        resizeSwapChain();
        if (!m_hasSwapChain)
            return;
        result = m_rhi->beginFrame(m_sc.get());
    }
    if (result != QRhi::FrameOpSuccess) {
        qWarning("beginFrame failed with %d, will retry", result);
        requestUpdate();
        return;
    }

    customRender();

一部のリソース(バッファ、テクスチャ)は、アプリケーション内で静的なデータを持っています。例えば、頂点バッファの内容は初期化ステップで提供され、その後は変更されません。これらのデータ更新操作はm_initialUpdates に記録されます。まだ実行されていない場合、このリソース更新バッチのコマンドはフレーム単位のバッチにマージされます。

void HelloWindow::customRender()
{
    QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();

    if (m_initialUpdates) {
        resourceUpdates->merge(m_initialUpdates);
        m_initialUpdates->release();
        m_initialUpdates = nullptr;
    }

フレームごとのリソース更新バッチを持つことは、モデルビュープロジェクションマトリックスと不透明度を持つユニフォームバッファの内容がフレームごとに変化するため、必要です。

    m_rotation += 1.0f;
    QMatrix4x4 modelViewProjection = m_viewProjection;
    modelViewProjection.rotate(m_rotation, 0, 1, 0);
    resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());
    m_opacity += m_opacityDir * 0.005f;
    if (m_opacity < 0.0f || m_opacity > 1.0f) {
        m_opacityDir *= -1;
        m_opacity = qBound(0.0f, m_opacity, 1.0f);
    }
    resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 64, 4, &m_opacity);

レンダーパスの記録を開始するために、QRhiCommandBuffer がクエリされ、出力サイズが決定されます。これは、必要に応じて、ビューポートを設定し、フルスクリーンテクスチャのサイズを変更するのに役立ちます。

    QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
    const QSize outputSizeInPixels = m_sc->currentPixelSize();

レンダーパスの開始は、レンダーターゲットのカラーバッファとデプスバッファのクリアを意味します(レンダーターゲットフラグがそうでないことを示さない限り、これはテクスチャベースのレンダーターゲットのみのオプションです)。ここでは、colorにblack、depthに1.0f、stencilに0(未使用)を指定します。最後の引数、resourceUpdates は、バッチに記録されたデータ更新コマンドがコミットされることを保証するものです。代わりに、QRhiCommandBuffer::resourceUpdate ()を使うこともできる。レンダーパスはスワップチェーンをターゲットとしているため、有効なQRhiRenderTarget を取得するためにcurrentFrameRenderTarget() を呼び出します。

    cb->beginPass(m_sc->currentFrameRenderTarget(), Qt::black, { 1.0f, 0 }, resourceUpdates);

三角形の描画コールの記録は簡単です。パイプラインを設定し、シェーダリソースを 設定し、頂点/インデックスバッファを設定し、描画コールを記録します。ここでは、頂点が 3 つだけの非インデックス描画を使用します。

    cb->setGraphicsPipeline(m_colorPipeline.get());
    cb->setShaderResources();
    const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
    cb->setVertexInput(0, 1, &vbufBinding);
    cb->draw(3);

    cb->endPass();

setShaderResources() 呼び出しには引数が与えられていません。これは、アクティブなQRhiGraphicsPipeline (m_colorPipeline) に関連付けられていたため、m_colorTriSrb を使用することを意味します。

フルスクリーンの背景画像のレンダリングの詳細については触れません。サンプルのソースコードを参照してください。しかし、テクスチャやバッファリソースの「サイズ変更」の一般的なパターンに注目する価値はあります。既存のネイティブリソースのサイズを変更することはできないため、テクスチャやバッファのサイズを変更するには、create()を呼び出して、基盤となるネイティブリソースを解放して再作成する必要があります。QRhiTexture 、常に必要なサイズを確保するために、アプリケーションは以下のロジックを実装します。m_texture はウィンドウのライフタイム全体にわたって有効であることに注意してください。これは、QRhiShaderResourceBindings などのオブジェクト参照が常に有効であることを意味します。つまり、例えば、それへのオブジェクト参照は常に有効であり続けるということです。時間の経過とともに出入りするのは、基礎となるネイティブ・リソースだけです。

また、描画するウィンドウに合わせて、画像にデバイスのピクセル比率を設定していることにも注意してください。これにより、描画コードはDPRに依存せず、DPRに関係なく同じレイアウトを作成することができます。

void HelloWindow::ensureFullscreenTexture(const QSize &pixelSize, QRhiResourceUpdateBatch *u)
{
    if (m_texture && m_texture->pixelSize() == pixelSize)
        return;

    if (!m_texture)
        m_texture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, pixelSize));
    else
        m_texture->setPixelSize(pixelSize);

    m_texture->create();

    QImage image(pixelSize, QImage::Format_RGBA8888_Premultiplied);
    image.setDevicePixelRatio(devicePixelRatio());

QImage が生成され、QPainter に基づく描画が終了したら、uploadTexture() を使用してリソース更新バッチにテクスチャのアップロードを記録します:

    u->uploadTexture(m_texture.get(), image);

プロジェクト例 @ code.qt.io

QRhi,QRhiSwapChain,QWindow,QRhiCommandBuffer,QRhiResourceUpdateBatch,QRhiBuffer,QRhiTextureも参照して ください。

本ドキュメントに含まれる文書の著作権は、それぞれの所有者に帰属します 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。