summaryrefslogtreecommitdiffstats
path: root/examples/widgets/doc/src/simplerhiwidget.qdoc
blob: e53302218e192b5a71719cf6566b12cd299be673 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// 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::textureFormat}{texture 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}
*/