rhi examples: expand docs

Change-Id: I88991ffe3612b76aaa687f86acd59e24c5543a76
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2023-06-01 14:39:48 +02:00
parent 9982f16ead
commit 040189e8e2
6 changed files with 157 additions and 4 deletions

View File

@ -29,4 +29,15 @@
APIs, the application links to \c{Qt::GuiPrivate} and includes
\c{<rhi/qrhi.h>}.
QSGRenderNode is the enabler for one of the three ways to integrate custom
2D/3D rendering into a Qt Quick scene. The other two options are to perform
the rendering \c before or \c after the Qt Quick scene's own rendering,
or to generate a whole separate render pass targeting a dedicated render
target (a texture) and then have an item in the scene display the texture.
The QSGRenderNode-based approach is similar to the former, in the sense
that no additional render passes or render targets are involved, and allows
injecting custom rendering commands "inline" with the Qt Quick scene's
own rendering.
\sa {Scene Graph - RHI Under QML}, {Scene Graph - RHI Texture Item}
*/

View File

@ -35,6 +35,12 @@
comes at the expense of being more expensive in terms of resources and
performance since it involves rendering to a texture first.
\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>}.
\section1 Walkthrough
\c ExampleRhiItem is the QQuickItem subclass that is exposed to QML
@ -49,13 +55,119 @@
\c ExampleRhiItem drives from \c RhiItem, which contains the generic
implementation of a \l QQuickItem that maintains and displays a \l
QRhiTexture.
QRhiTexture. The design is somewhat similar to the legacy \l
QQuickFramebufferObject and its inner Renderer class. In essence what is
implemented here offers the core functionality of \l
QQuickFramebufferObject, but without being tied to OpenGL. To support the
threaded rendering model of the Qt Quick scene graph, the a separate \c
RhiItemRenderer object is instantiated that then lives on the Qt Quick
render thread, if there is one. \c RhiItem, being a \l QQuickItem, lives
and operates on the main (gui) thread.
\snippet scenegraph/rhitextureitem/rhitextureitem.h itembase
The corresponding scene graph node is implemented using \l
QSGSimpleTextureNode.
\c RhiItemRenderer has three pure virtual functions expected to be
reimplemented in subclasses. \c initialize() is called to let the
application-provided renderer to know the \l QRhi and \l QRhiTexture
instances to use. The example also handles the resizing of the item, which
leads to having to use a new texture with a size different than before.
This means \c initialize() may be called multiple times during the lifetime
of a \c RhiItemRenderer.
\c synchronize() is called from the scene graph's synchronizing phase, i.e.
from the \c RhiItem's \l{QQuickItem::updatePaintNode()}{updatePaintNode()}.
That implies that, if the threaded rendering model is used, that it is safe
to copy data between the main and the render thread since the main thread is
blocked.
\c render() is the function that is called every time the \c RhiItem's
texture's content needs updating. This function is expected to record a
render pass onto the provided \l QRhiCommandBuffer, targeting a \l
QRhiTextureRenderTarget associated with the \l QRhiTexture passed to \c
initialize().
\snippet scenegraph/rhitextureitem/rhitextureitem.h rendererbase
The scene graph node that is instantied by \c RhiItem is implemented using
\l QSGSimpleTextureNode.
\snippet scenegraph/rhitextureitem/rhitextureitem.h itemnode
\c RhiItemNode connects to the window's
\l{QQuickWindow::beforeRendering()}{beforeRendering()} signal. This signal
is emitted on the render thread, if there is one, every time the Qt Quick
scene graph has started to prepare a new frame.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp nodector
The slot connected to this signal retrieves the \l QRhiCommandBuffer used by
the \l QQuickWindow, while also providing an example of what to do if there
is no on-screen window, and so no \l QRhiSwapChain associated with the \l
QQuickWindow. Then the \c RhiItemRenderer's \c render() function is invoked.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp noderender
The application-provided \c initialize and \c synchronize steps are invoked
from the \c RhiItemNode's sync() function which in turn is called from \c
RhiItem's \c updatePaintNode().
Once the \l QRhi is retrieved from the \l QQuickWindow, the need for a new
\l QRhiTexture is examined. If there is no texture yet, or it looks like the
item's size (in pixels, note the multiplication with the device pixel ratio)
has changed, a new texture is created. The \l QRhiTexture is then wrapped in
a \l QSGTexture, which also involves passing ownership. The wrapping \l
QSGTexture is created using a \l QQuickWindow helper function,
\l{QQuickWindow::createTextureFromRhiTexture()}{createTextureFromRhiTexture()}. In
spirit this is similar to the
\l{QQuickWindow::createTextureFromImage()}{createTextureFromImage()}, but
while the traditional QImage-based function creates a new \l QRhiTexture
under the hood, this variant takes an existing \l QRhiTexture.
Finally, the application-provided \c synchronize() function is invoked.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp nodesync
The example's implementation makes a copy of the angle value, meaning the
renderer's copy of the value is updated based on the current value of the
property in \c ExampleRhiItem.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp examplesync
The example's implementation of the initialization step stores the \l QRhi
for future use. This example does not handle the case of the \l QRhi
changing over the lifetime of the item. If moving (reparenting) the item
between \l QQuickWindow instances is involved, then that would need to be
handled as well. What is handled however, is the case of the \c
outputTexture changing. With the implementation of \c RhiItem and \c
RhiItemNode, the \l QRhiTexture is different whenever the window, and so the
item in the scene, is resized.
If not yet done, a \l QRhiTextureRenderTarget is created. The example also
demonstrates rendering with a depth buffer present. Care must be taken to
correctly resize this buffer whenever its size no longer matches the \c
outputTexture's size in the previous invocation of the function.
Finally, if not yet done, the resources needed for rendering the scene are
prepared: vertex buffer, uniform buffer, graphics pipeline.
The traditional Qt logo renderer, that has been ported from the OpenGL-based
examples of Qt 4 and 5, provides vertex positions and normals in two
separate chunks, hence using a non-interleaved layout for the vertex buffer.
The vertex and fragment shaders are loaded from \c{.qsb} files generated at
build time (if using CMake).
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp exampleinit
In the \c render step, the uniform buffer is updated. Note how the
\l{QRhi::clipSpaceCorrMatrix()}{QRhi-provided correction matrix} is
multiplied in. This allows ignoring the 3D API specific differences when it
comes to coordinate systems, and continuing to work with OpenGL-style
vertices and normals.
A single render pass is recorded, containing a single draw call.
\snippet scenegraph/rhitextureitem/rhitextureitem.cpp examplerender
\sa {Scene Graph - RHI Under QML}, {Scene Graph - Custom QSGRenderNode}
*/

View File

@ -4,12 +4,14 @@
#include "rhitextureitem.h"
#include <QFile>
//! [nodector]
RhiItemNode::RhiItemNode(RhiItem *item)
: m_item(item)
{
m_window = m_item->window();
connect(m_window, &QQuickWindow::beforeRendering, this, &RhiItemNode::render,
Qt::DirectConnection);
//! [nodector]
connect(m_window, &QQuickWindow::screenChanged, this, [this]() {
if (m_window->effectiveDevicePixelRatio() != m_dpr)
m_item->update();
@ -21,6 +23,7 @@ QSGTexture *RhiItemNode::texture() const
return m_sgTexture.get();
}
//! [nodesync]
void RhiItemNode::sync()
{
if (!m_rhi) {
@ -56,7 +59,9 @@ void RhiItemNode::sync()
m_renderer->synchronize(m_item);
}
//! [nodesync]
//! [noderender]
void RhiItemNode::render()
{
// called before Qt Quick starts recording its main render pass
@ -86,6 +91,7 @@ void RhiItemNode::render()
markDirty(QSGNode::DirtyMaterial);
emit textureChanged();
}
//! [noderender]
void RhiItemNode::scheduleUpdate()
{
@ -213,6 +219,7 @@ static QShader getShader(const QString &name)
return QShader();
}
//! [exampleinit]
void ExampleRhiItemRenderer::initialize(QRhi *rhi, QRhiTexture *outputTexture)
{
m_rhi = rhi;
@ -247,6 +254,7 @@ void ExampleRhiItemRenderer::initialize(QRhi *rhi, QRhiTexture *outputTexture)
const quint32 vbufSize = vsize + nsize;
scene.vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, vbufSize));
scene.vbuf->create();
//! [exampleinit]
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
scene.resourceUpdates->uploadStaticBuffer(scene.vbuf.get(), 0, vsize, m_vertices.constData());
@ -284,6 +292,7 @@ void ExampleRhiItemRenderer::initialize(QRhi *rhi, QRhiTexture *outputTexture)
}
}
//! [examplesync]
void ExampleRhiItemRenderer::synchronize(RhiItem *rhiItem)
{
// called on the render thread (if there is one), while the main (gui) thread is blocked
@ -292,7 +301,9 @@ void ExampleRhiItemRenderer::synchronize(RhiItem *rhiItem)
if (item->angle() != scene.logoAngle)
scene.logoAngle = item->angle();
}
//! [examplesync]
//! [examplerender]
void ExampleRhiItemRenderer::render(QRhiCommandBuffer *cb)
{
QRhiResourceUpdateBatch *rub = scene.resourceUpdates;
@ -320,6 +331,7 @@ void ExampleRhiItemRenderer::render(QRhiCommandBuffer *cb)
cb->endPass();
}
//! [examplerender]
void ExampleRhiItemRenderer::createGeometry()
{

View File

@ -15,6 +15,7 @@ class RhiItemNode;
QT_FORWARD_DECLARE_CLASS(QSGPlainTexture)
//! [rendererbase]
class RhiItemRenderer
{
public:
@ -22,7 +23,7 @@ public:
virtual void initialize(QRhi *rhi, QRhiTexture *outputTexture) = 0;
virtual void synchronize(RhiItem *item) = 0;
virtual void render(QRhiCommandBuffer *cb) = 0;
//! [rendererbase]
void update();
private:

View File

@ -34,6 +34,20 @@
with all the 3D APIs supported by QRhi (such as, OpenGL, Vulkan, Metal,
Direct 3D 11 and 12).
\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>}.
Adding custom rendering as an underlay/overlay is one of the three ways to integrate
custom 2D/3D rendering into a Qt Quick scene. The other two options are to perform
the rendering "inline" with the Qt Quick scene's own rendering using QSGRenderNode,
or to generate a whole separate render pass targeting a dedicated render target
(a texture) and then have an item in the scene display the texture.
Refer to the \l{Scene Graph - RHI Texture Item} and the
\l{Scene Graph - Custom QSGRenderNode} examples regarding those approaches.
\section1 Core Concepts
The beforeRendering() signal is emitted at the start of every frame, before
@ -216,4 +230,6 @@
normally be generated at build time, and lists them in the qrc file. This
approach is however not recommended for new applications that use CMake as
the build system.
\sa {Scene Graph - RHI Texture Item}, {Scene Graph - Custom QSGRenderNode}
*/

View File

@ -151,6 +151,7 @@ Creator.
\li \l{Scene Graph - Custom Material}{Custom Material}
\li \l{Scene Graph - RHI Under QML}{Portable QRhi-based 3D rendering as a scene underlay}
\li \l{Scene Graph - RHI Texture Item}{Displaying a QRhi-rendered image in a QQuickItem}
\li \l{Scene Graph - Custom QSGRenderNode}{Implementing a QRhi-based QSGRenderNode}
\li \l{Scene Graph - Two Texture Providers}{Texture Providers and Materials}
\li \l{Scene Graph - Custom Geometry}{Custom Geometry}
\li \l{Scene Graph - Graph}{Graph}