Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example scenegraph/rhitextureitem
\title Scene Graph - RHI Texture Item
\examplecategory {Graphics}
\ingroup qtquickexamples
\brief Shows how to implement a custom QQuickItem that displays a QRhi-rendered texture.
\image rhitextureitem-example.jpg {RGB triangle rendered using QRhi
displayed as a QQuickItem, with settings panel for MSAA and texture options}
This example shows how to implement an item that performs cross-platform,
portable 3D rendering into a texture using the QRhi APIs and then displays
that image.
\note This example demonstrates advanced, low-level functionality performing
portable, cross-platform 3D rendering, while relying on APIs with limited
compatibility guarantee from the Qt Gui module. To be able to use the QRhi
APIs, the application links to \c{Qt::GuiPrivate} and includes
\c{<rhi/qrhi.h>}.
\section2 Comparison with other approaches
The \l{Scene Graph - RHI Under QML}{RHI Under QML} example shows how to
implement portable, cross-platform 3D rendering with the \l QRhi APIs in a
manner where the custom rendering is issued before the Qt Quick scene
graph's own rendering, effectively providing an "underlay". That approach
is efficient since now additional render targets and render passes are
needed, the custom rendering is injected in the main render pass before the
scene graph's own draw calls.
In contrast, this example involves a separate render target, a QRhiTexture,
the \l{QRhiTexture::pixelSize()}{dimensions} of which match the
QQuickItem's size in the scene, and a whole render pass that is used to
clear and then draw into that texture. The texture is then sampled in the
main render pass and is used to texture a quad, effectively displaying a 2D
image.
Compared to the underlay/overlay approach, this allows displaying,
blending, and transforming the flattened 2D image of the 3D rendering
anywhere in the Qt Quick scene since here we have a true QQuickItem. This
comes at the expense of being more expensive in terms of resources and
performance since it involves rendering to a texture first.
\section2 Overview
The example is implemented using \l QQuickRhiItem and
\l QQuickRhiItemRenderer. QQuickRhiItem is a convenience class that can be
subclassed to easily and quickly get a fully featured, custom QQuickItem that
displays the contents of a QRhiTexture by using QSGSimpleTextureNode under the
hood. The contents of the texture is generated by the application-provided
logic implemented in its QQuickRhiItemRenderer subclass.
\c ExampleRhiItem is a QQuickRhiItem subclass that offers a few properties,
such as \c angle and \c backgroundAlpha. These are going to be read,
written, and animated from QML. In order to support Qt Quick's threaded
rendering model, the QQQuickRhiItemRenderer has a virtual
\l{QQuickRhiItemRenderer::synchronize()}{synchronize()} function that can
be reimplemented to safely perform copying of data between the
QQuickRhiItem (belonging to the main/GUI thread) and the
QQuickRhiItemRenderer (belonging to the render thread, if there is one).
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp 0
\l{QQuickRhiItemRenderer::initialize()}{initialize()} is called at least
once before the first call to render(), but may in practice be invoked
multiple times: if the QQuickItem geometry changes (due to some layout
change, resizing the window, etc.), if QQuickRhiItem setting such as the
sample count and texture format change, or if the item is reparented so
that is belong to a new QQuickWindow, these all trigger calling
initialize() again because they imply that one or more of the resources
QQuickRhiItem-managed resources change, which is going to have implications
on the subclass as well. The example code here is prepared to handle these
special situations (changing QRhi, changing sample count, changing texture
format). (as it does not hold on to the texture used as the color buffer,
the case when the texture is recreated due to a different size needs no
special handling)
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp 1
The rest if initialize() is straightforward QRhi-based code.
The 3D scene uses a perspective projection, which is calculated based on
the output size, queried from the QRhiRenderTarget for convenience (because
this works regardless of using multisampling or not, whereas accessing
\l{QQuickRhiItemRenderer::colorTexture()}{colorTexture()} and
\l{QQuickRhiItemRenderer::msaaColorBuffer()}{msaaColorBuffer()} would need
branching logic based on which of the objects happens to be valid)
Note the usage of \l QRhi::clipSpaceCorrMatrix() to cater for the
coordinate system differences between 3D graphics APIs.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp 2
The implementation of \l{QQuickRhiItemRenderer::render()}{render()} records
the drawing of a single triangle. The uniform buffer with the 4x4 matrix is
updated every time since we expect the rotation angle to change. The clear
color has the item-provided background alpha baked in. Remember the need to
premultiply the alpha value in the red, green, and blue components as well.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp 3
\sa QQuickRhiItem, {Scene Graph - RHI Under QML}, {Scene Graph - Custom QSGRenderNode}
*/