mirror of https://github.com/qt/qtbase.git
205 lines
11 KiB
Plaintext
205 lines
11 KiB
Plaintext
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
|
|
|
/*!
|
|
\example rhi/simplerhiwidget
|
|
\title Simple RHI Widget Example
|
|
\examplecategory {Graphics}
|
|
\ingroup examples-widgets
|
|
\brief Shows how to render a triangle using QRhi, Qt's 3D API and shading language abstraction layer.
|
|
|
|
\image simplerhiwidget-example.jpg
|
|
\caption Screenshot of the Simple RHI Widget example
|
|
|
|
This example is, in many ways, the counterpart of the \l{RHI Window
|
|
Example} in the \l QWidget world. The \l QRhiWidget subclass in this
|
|
applications renders a single triangle, using a simple graphics pipeline
|
|
with basic vertex and fragment shaders. Unlike the plain QWindow-based
|
|
application, this example does not need to worry about lower level details,
|
|
such as setting up the window and the QRhi, or dealing with swapchain and
|
|
window events, as that is taken care of by the QWidget framework here. The
|
|
instance of the \l QRhiWidget subclass is added to a QVBoxLayout. To keep
|
|
the example minimal and compact, there are no further widgets or 3D content
|
|
introduced.
|
|
|
|
Once an instance of \c ExampleRhiWidget, a \l QRhiWidget subclass, is added
|
|
to a top-level widget's child hierarchy, the corresponding window
|
|
automatically becomes a Direct 3D, Vulkan, Metal, or OpenGL-rendered
|
|
window. The QPainter-rendered widget content, i.e. everything that is not a
|
|
QRhiWidget, QOpenGLWidget, or QQuickWidget, is then uploaded to a texture,
|
|
whereas the mentioned special widgets each render to a texture. The
|
|
resulting set of \l{QRhiTexture}{textures} is composited together by the
|
|
top-level widget's backingstore.
|
|
|
|
\section1 Structure and main()
|
|
|
|
The \c{main()} function is quite simple. The top-level widget defaults to a
|
|
size of 720p (this size is in logical units, the actual pixel size may be
|
|
different, depending on the \l{QWidget::devicePixelRatio()}{scale factor}.
|
|
The window is resizable. QRhiWidget makes it simple to implement subclasses
|
|
that correctly deal with the resizing of the widget due to window size or
|
|
layout changes.
|
|
|
|
\snippet rhi/simplerhiwidget/main.cpp 0
|
|
|
|
The QRhiWidget subclass reimplements the two virtuals:
|
|
\l{QRhiWidget::initialize()}{initialize()} and
|
|
\l{QRhiWidget::render()}{render()}.
|
|
initialize() is called at least once before render(),
|
|
but is also invoked upon a number of important changes, such as when the
|
|
widget's backing texture is recreated due to a changing widget size, when
|
|
render target parameters change, or when the widget changes to a new QRhi
|
|
due to moving to a new top-level window.
|
|
|
|
\note Unlike QOpenGLWidget's legacy \c initializeGL - \c resizeGL - \c
|
|
paintGL model, there are only two virtuals in QRhiWidget. This is because
|
|
there are more special events that possible need taking care of than just
|
|
resizing, e.g. when reparenting to a different top-level window. (robust
|
|
QOpenGLWidget implementations had to deal with this by performing
|
|
additional bookkeeping, e.g. by tracking the associated QOpenGLContext
|
|
lifetime, meaning the three virtuals were not actually sufficient) A
|
|
simpler pair of \c initialize - \c render, where \c initialize is
|
|
re-invoked upon important changes is better suited for this.
|
|
|
|
The \l QRhi instance is not owned by the widget. It is going to be queried
|
|
in \c initialize() \l{QRhiWidget::rhi()}{from the base class}. Storing it
|
|
as a member allows recognizing changes when \c initialize() is invoked
|
|
again. Graphics resources, such as the vertex and uniform buffers, or the
|
|
graphics pipeline are however under the control of \c ExampleRhiWidget.
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.h 0
|
|
|
|
For the \c{#include <rhi/qrhi.h>} statement to work, the application must
|
|
link to \c GuiPrivate (or \c{gui-private} with qmake). See \l QRhi for more
|
|
details about the compatibility promise of the QRhi family of APIs.
|
|
|
|
\c CMakeLists.txt
|
|
|
|
\badcode
|
|
target_link_libraries(simplerhiwidget PRIVATE
|
|
Qt6::Core
|
|
Qt6::Gui
|
|
Qt6::GuiPrivate
|
|
Qt6::Widgets
|
|
)
|
|
\endcode
|
|
|
|
\section1 Rendering Setup
|
|
|
|
In \c examplewidget.cpp the widget implementation uses a helper function to
|
|
load up a \l QShader object from a \c{.qsb} file. This application ships
|
|
pre-conditioned \c{.qsb} files embedded in to the executable via the Qt
|
|
Resource System. Due to module dependencies (and due to still supporting
|
|
qmake), this example does not use the convenient CMake function
|
|
\c{qt_add_shaders()}, but rather comes with the \c{.qsb} files as part of
|
|
the source tree. Real world applications are encouraged to avoid this and
|
|
rather use the Qt Shader Tools module's CMake integration features (\c
|
|
qt_add_shaders). Regardless of the approach, in the C++ code the loading
|
|
of the bundled/generated \c{.qsb} files is the same.
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp get-shader
|
|
|
|
Let's look at the initialize() implementation. First, the \l QRhi object is
|
|
queried and stored for later use, and also to allow comparison in future
|
|
invocations of the function. When there is a mismatch (e.g. when the widget
|
|
is moved between windows), recreation of graphics resources need to be
|
|
recreated is triggered by destroying and nulling out a suitable object, in
|
|
this case the \c m_pipeline. The example does not actively demonstrate
|
|
reparenting between windows, but it is prepared to handle it. It is also
|
|
prepared to handle a changing widget size that can happen when resizing the
|
|
window. That needs no special handling since \c{initialize()} is invoked
|
|
every time that happens, and so querying
|
|
\c{renderTarget()->pixelSize()} or \c{colorTexture()->pixelSize()}
|
|
always gives the latest, up-to-date size in pixels. What this example is
|
|
not prepared for is changing
|
|
\l{QRhiWidget::colorBufferFormat}{color buffer formats} and
|
|
\l{QRhiWidget::sampleCount}{multisample settings}
|
|
since it only ever uses the defaults (RGBA8 and no multisample antialiasing).
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp init-1
|
|
|
|
When the graphics resources need to be (re)created, \c{initialize()} does
|
|
this using quite typical QRhi-based code. A single vertex buffer with the
|
|
interleaved position - color vertex data is sufficient, whereas the
|
|
modelview-projection matrix is exposed via a uniform buffer of 64 bytes (16
|
|
floats). The uniform buffer is the only shader visible resource, and it is
|
|
only used in the vertex shader. The graphics pipeline relies on a lot of
|
|
defaults (for example, depth test off, blending disabled, color write
|
|
enabled, face culling disabled, the default topology of triangles, etc.)
|
|
The vertex data layout is \c x, \c y, \c r, \c g, \c b, hence the stride is
|
|
5 floats, whereas the second vertex input attribute (the color) has an
|
|
offset of 2 floats (skipping \c x and \c y). Each graphics pipeline has to
|
|
be associated with a \l QRhiRenderPassDescriptor. This can be retrieved
|
|
from the \l QRhiRenderTarget managed by the base class.
|
|
|
|
\note This example relies on the QRhiWidget's default of
|
|
\l{QRhiWidget::autoRenderTarget}{autoRenderTarget} set to \c true.
|
|
That is why it does not need to manage the render target, but can just
|
|
query the existing one by calling
|
|
\l{QRhiWidget::renderTarget()}{renderTarget()}.
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp init-pipeline
|
|
|
|
Finally, the projection matrix is calculated. This depends on the widget
|
|
size and is thus done unconditionally in every invocation of the functions.
|
|
|
|
\note Any size and viewport calculations should only ever rely on the pixel
|
|
size queried from the resource serving as the color buffer since that is
|
|
the actual render target. Avoid manually calculating sizes, viewports,
|
|
scissors, etc. based on the QWidget-reported size or device pixel ratio.
|
|
|
|
\note The projection matrix includes the
|
|
\l{QRhi::clipSpaceCorrMatrix()}{correction matrix} from QRhi in order to
|
|
cater for 3D API differences in normalized device coordinates.
|
|
(for example, Y down vs. Y up)
|
|
|
|
A translation of \c{-4} is applied just to make sure the triangle with \c z
|
|
values of 0 will be visible.
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp init-matrix
|
|
|
|
\section1 Rendering
|
|
|
|
The widget records a single render pass, which contains a single draw call.
|
|
|
|
The view-projection matrix calculated in the initialize step gets combined
|
|
with the model matrix, which in this case happens to be a simple rotation.
|
|
The resulting matrix is then written to the uniform buffer. Note how
|
|
\c resourceUpdates is passed to
|
|
\l{QRhiCommandBuffer::beginPass()}{beginPass()}, which is a shortcut to not
|
|
having to invoke \l{QRhiCommandBuffer::resourceUpdate()}{resourceUpdate()}
|
|
manually.
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp render-1
|
|
|
|
In the render pass, a single draw call with 3 vertices is recorded. The
|
|
graphics pipeline created in the initialize step is bound on the command
|
|
buffer, and the viewport is set to cover the entire widget. To make the
|
|
uniform buffer visible to the (vertex) shader,
|
|
\l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()} is called
|
|
with no argument, which means using the \c m_srb since that was associated
|
|
with the pipeline at pipeline creation time. In more complex renderers it
|
|
is not unusual to pass in a different \l QRhiShaderResourceBindings object,
|
|
as long as that is
|
|
\l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible}
|
|
with the one given at pipeline creation time.
|
|
There is no index buffer, and there is a single vertex buffer binding (the
|
|
single element in \c vbufBinding refers to the single entry in the binding
|
|
list of the \l QRhiVertexInputLayout that was specified when creating
|
|
pipeline).
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp render-pass
|
|
|
|
Once the render pass is recorded, \l{QWidget::update()}{update()} is
|
|
called. This requests a new frame, and is used to ensure the widget
|
|
continuously updates, and the triangle appears rotating. The rendering
|
|
thread (the main thread in this case) is throttled by the presentation rate
|
|
by default. There is no proper animation system in this example, and so the
|
|
rotation will increase in every frame, meaning the triangle will rotate at
|
|
different speeds on displays with different refresh rates.
|
|
|
|
\snippet rhi/simplerhiwidget/examplewidget.cpp render-2
|
|
|
|
\sa QRhi, {Cube RHI Widget Example}, {RHI Window Example}
|
|
*/
|