summaryrefslogtreecommitdiffstats
path: root/examples/widgets/doc
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2023-06-13 13:10:40 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2023-08-14 17:05:36 +0200
commit72a453c6a8b02c08c5c09842f468459d6a51c387 (patch)
treed97d633203823990c523e208bbdeb4a16c1ab00c /examples/widgets/doc
parent85e2f79e9eb4b55ba57abf1df9315e7e4bebde84 (diff)
Add QRhiWidget
Task-number: QTBUG-113331 Change-Id: I8baa697b4997b05f52acdee0e08d3c368fde5bc2 Reviewed-by: Andy Nichols <andy.nichols@qt.io>
Diffstat (limited to 'examples/widgets/doc')
-rw-r--r--examples/widgets/doc/src/cuberhiwidget.qdoc169
-rw-r--r--examples/widgets/doc/src/simplerhiwidget.qdoc203
2 files changed, 372 insertions, 0 deletions
diff --git a/examples/widgets/doc/src/cuberhiwidget.qdoc b/examples/widgets/doc/src/cuberhiwidget.qdoc
new file mode 100644
index 0000000000..84be1d0942
--- /dev/null
+++ b/examples/widgets/doc/src/cuberhiwidget.qdoc
@@ -0,0 +1,169 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example rhi/cuberhiwidget
+ \title Cube RHI Widget Example
+ \ingroup examples-widgets
+ \brief Shows how to render a textured cube and integrate with QPainter and widgets, using QRhi Qt's 3D API and shading language abstraction layer.
+
+ \image cuberhiwidget-example.jpg
+ \caption Screenshot of the Cube RHI Widget example
+
+ This example builds on the \l{Simple RHI Widget Example}. While the simple
+ example is intentionally minimal and as compact as possible, rendering only
+ a single triangle with no additional widgets in the window, this
+ application demonstrates:
+
+ \list
+
+ \li Having various widgets in the window, some of them controlling data
+ that is consumed by the QRhiWidget subclass.
+
+ \li Instead of continuously requesting updates, the QRhiWidget here only
+ updates the content in its backing texture when some related data changes.
+
+ \li The cube is textured using a \l QRhiTexture that sources its content
+ from a \l QImage that contains software-based rendering performed with
+ \l QPainter.
+
+ \li The contents of the QRhiWidget \l{QRhiWidget::grab()}{can be
+ read back} and saved to an image file (e.g. a PNG file).
+
+ \li 4x multisample antialiasing \l{QRhiWidget::sampleConut}{can be toggled}
+ at run time. The QRhiWidget subclass is prepared to handle the changing
+ sample count correctly.
+
+ \li Forcing an \l{QRhiWidget::explicitSize}{explicitly specified backing
+ texture size} can be toggled dynamically and controlled with a slider
+ between 16x16 up to 512x512 pixels.
+
+ \li The QRhiWidget subclass deals with a changing \l QRhi correctly. This
+ can be seen in action when making the widget top-level (no parent; becomes
+ a separate window) and then reparenting it again into the main window's
+ child hierarchy.
+
+ \li Most importantly, some widgets, with semi-transparency even, can be
+ placed on top of the QRhiWidget, proving that correct stacking and blending
+ is feasible. This is a case where QRhiWidget is superior to embedding a
+ native window, i.e. a QRhi-based QWindow using
+ QWidget::createWindowContainer(), because it allows stacking and clipping
+ the same way as any ordinary, software-rendered QWidget, whereas native
+ window embedding may, depending on the platform, have various limitations,
+ e.g. often it can be difficult or inefficient to place additional controls
+ on top.
+
+ \endlist
+
+ In the reimplementation of \l{QRhiWidget::initialize()}{initialize()}, the
+ first thing to do is to check if the QRhi we last worked with is still
+ up-to-date, and if the sample count (for multisample antialiasing) has
+ changed. The former is important because all graphics resources must be
+ released when the QRhi changes, whereas with a dynamically changing sample
+ count a similar problem arises specifically for QRhiGraphicsPipeline
+ objects as those bake the sample count in. For simplicity, the application
+ handles all such changes the same way, by resetting its \c scene struct to
+ a default constructed one, which conveniently drops all graphics resources.
+ All resources are then recreated.
+
+ When the backing texture size (so the render target size) changes, no
+ special action is needed, but a signal is emitted for convenience, just so
+ that main() can reposition the overlay label. The 3D API name is also
+ exposed via a signal by querying \l QRhi::backendName() whenever the QRhi
+ changes.
+
+ The implementation has to be aware that multisample antialiasing implies
+ that \l{QRhiWidget::colorTexture()}{colorTexture()} is \nullptr, while
+ \l{QRhiWidget::msaaColorBuffer()}{msaaColorBuffer()} is valid. This is
+ the opposite of when MSAA is not in use. The reason for differentiating
+ and using different types (QRhiTexture, QRhiRenderBuffer) is to allow
+ using MSAA with 3D graphics APIs that do not have support for
+ multisample textures, but have support for multisample renderbuffers.
+ An example of this is OpenGL ES 3.0.
+
+ When checking the up-to-date pixel size and sample count, a convenient and
+ compact solution is to query via the QRhiRenderTarget, because this way one
+ does not need to check which of colorTexture() and msaaColorBuffer() are
+ valid.
+
+ \snippet rhi/cuberhiwidget/examplewidget.cpp init-1
+
+ The rest is quite self-explanatory. The buffers and pipelines are
+ (re)created, if necessary. The contents of the texture that is used to
+ texture the cube mesh is updated. The scene is rendered using a perspective
+ projection. The view is just a simple translation for now.
+
+ \snippet rhi/cuberhiwidget/examplewidget.cpp init-2
+
+ The function that performs the actual enqueuing of the uniform buffer write
+ is also taking the user-provided rotation into account, thus generating the
+ final modelview-projection matrix.
+
+ \snippet rhi/cuberhiwidget/examplewidget.cpp rotation-update
+
+ Updating the \l QRhiTexture that is sampled in the fragment shader when
+ rendering the cube, is quite simple, even though a lot is happening in
+ there: first a QPainter-based drawing is generated within a QImage. This
+ uses the user-provided text. Then the CPU-side pixel data is uploaded to a
+ texture (more precisely, the upload operation is recorded on a \l
+ QRhiResourceUpdateBatch, which is then submitted later in render()).
+
+ \snippet rhi/cuberhiwidget/examplewidget.cpp texture-update
+
+ The graphics resource initialization is simple. There is only a vertex
+ buffer, no index buffer, and a uniform buffer with only a 4x4 matrix in it
+ (16 floats).
+
+ The texture that contains the QPainter-generated drawing has a size of
+ 512x512. Note that all sizes (texture sizes, viewports, scissors, texture
+ upload regions, etc.) are always in pixels when working with QRhi. To
+ sample this texture in the shader, a \l{QRhiSampler}{sampler object} is
+ needed (irrespective of the fact that QRhi-based applications will
+ typically use combined image samplers in the GLSL shader code, which then
+ may be transpiled to separate texture and sampler objects with some shading
+ languages, or may stay a combined texture-sampler object with others,
+ meaning there may not actually be a native sampler object under the hood at
+ run time, depending on the 3D API, but this is all transparent to the
+ application)
+
+ The vertex shader reads from the uniform buffer at binding point 0,
+ therefore
+ \c{scene.ubuf} is exposed at that binding location. The fragment shader
+ samples a texture provided at binding point 1,
+ therefore a combined texture-sampler pair is specified for that binding location.
+
+ The QRhiGraphicsPipeline enables depth test/write, and culls backfaces. It
+ also relies on a number of defaults, e.g. the depth comparison function
+ defaults to \c Less, which is fine for us, and the front face mode is
+ counter-clockwise, which is also good as-is so does not need to be set
+ again.
+
+ \snippet rhi/cuberhiwidget/examplewidget.cpp setup-scene
+
+ In the reimplementation of \l{QRhiWidget::render()}{render()}, first the
+ user-provided data is checked. If the \l QSlider controlling the rotation
+ has provided a new value, or the \l QTextEdit with the cube text has
+ changed its text, the graphics resources the contents of which depend on
+ such data get updated.
+
+ Then, a single render pass with a single draw call is recorded. The cube
+ mesh data is provided in a non-interleaved format, hence the need for two
+ vertex input bindings, one is the positions (x, y, z) the other is the UVs
+ (u, v), with a start offset that corresponds to 36 x-y-z float pairs.
+
+ \snippet rhi/cuberhiwidget/examplewidget.cpp render
+
+ How is the user-provided data sent? Take the rotation for example. main()
+ connects to the QSlider's \l{QSlider::valueChanged}{valueChanged} signal.
+ When emitted, the connected lamda calls setCubeRotation() on the
+ ExampleRhiWidget. Here, if the value is different from before, it is
+ stored, and a dirty flag is set. Then, most importantly,
+ \l{QWidget::update()}{update()} is called on the ExampleRhiWidget. This is
+ what triggers rendering a new frame into the QRhiWidget's backing texture.
+ Without this the content of the ExampleRhiWidget would not update when
+ dragging the slider.
+
+ \snippet rhi/cuberhiwidget/examplewidget.h data-setters
+
+ \sa QRhi, {Simple RHI Widget Example}, {RHI Window Example}
+*/
diff --git a/examples/widgets/doc/src/simplerhiwidget.qdoc b/examples/widgets/doc/src/simplerhiwidget.qdoc
new file mode 100644
index 0000000000..5db4f2d583
--- /dev/null
+++ b/examples/widgets/doc/src/simplerhiwidget.qdoc
@@ -0,0 +1,203 @@
+// 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
+ \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}
+*/