diff options
Diffstat (limited to 'examples/widgets/doc/src/simplerhiwidget.qdoc')
-rw-r--r-- | examples/widgets/doc/src/simplerhiwidget.qdoc | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/examples/widgets/doc/src/simplerhiwidget.qdoc b/examples/widgets/doc/src/simplerhiwidget.qdoc new file mode 100644 index 0000000000..e53302218e --- /dev/null +++ b/examples/widgets/doc/src/simplerhiwidget.qdoc @@ -0,0 +1,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} +*/ |