// 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 } 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} */