RHIウィンドウの例

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

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

QRhi QWindow をターゲットとし、 、 3D、ウィジェットは一切使用せず、レンダリングとウィンドウのインフラをすべてアプリケーションに設定します。Qt Quick Qt Quick

これとは対照的に、Qt QuickQt 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用
  • -mまたは-metal(メタル
  • -vまたは-vulkan(Vulkan用
  • OpenGL または OpenGL ES の場合は -g または -opengl
  • -n または -nullNull backend

ビルド・システム・ノート

このアプリケーションはQt GUI モジュールのみに依存しています。Qt WidgetsQt 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 モジュールのサンプルであるため、このサンプルはQtShader 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 の範囲外です。

  // OpenGLでは、ウィンドウの深度/ステンシルバッファを確保します。 // 他のAPIでは、これはアプリケーションの制御下に あるので (QRhiRenderBufferなど) 、 // 特別な設定は必要ありません。  QSurfaceFormatfmt; fmt.setDepthBufferSize(24); fmt.setStencilBufferSize(8);// macOSでOpenGLを使用できるようにするための特別なケース // (デフォルトのMetalが推奨されるアプローチですが) // gl_VertexIDはGLSL 130の機能であるため、 macOSで得られる デフォルトのOpenGL 2.1のコンテキストでは 不十分です。QSurfaceFormat::CoreProfile);#endif  QSurfaceFormat::setDefaultFormat(fmt);// Vulkanの場合#if QT_CONFIG(vulkan)  QVulkanInstanceインスタンス;if(graphicsApi==QRhi::Vulkan) {// 利用可能であれば、検証を要求します。これは完全にオプションで あり、 // パフォーマンスに影響があるため、実運用では避けるべきです QRhiVulkanInitParams::preferredInstanceExtensions());if(!inst.create()) { // QRhiとうまくやる。           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のような他のアプリケーションでは、初期化するための追加情報は必要ありません。

voidRhiWindow::init() {if(m_graphicsApi==! QRhi::Null)       QRhiNullInitParamsparams; 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) { params; . QRhiVulkanInitParamsparams; 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;// デバッグレイヤーがあれば有効にします。params.enableDebugLayer= true; m_rhi.reset(QRhi::create(QRhi::D3D11, &params)); }else if(m_graphicsApi==) QRhi::D3D12) { QRhiD3D12InitParams params;// デバッグレイヤーがあれば有効にします。params.enableDebugLayer= true; m_rhi.reset(QRhi::create(QRhi::D3D12, &params)); }#endif#if QT_CONFIG(metal) if(m_graphicsApi== ==)QRhi::Metal) { params; m_rhi.D3D12        QRhiMetalInitParamsparams; 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() はスワップチェーンの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-生成された画像を含むテクスチャがあり、その上にブレンディングを有効にして1つの三角形がレンダリングされます。

三角形で使用される頂点とユニフォームバッファはこのように作成されます。ユニフォーム・バッファのサイズは、シェーダがユニフォーム・ブロックの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も参照の こと。

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