diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2023-06-13 13:10:40 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2023-08-14 17:05:36 +0200 |
commit | 72a453c6a8b02c08c5c09842f468459d6a51c387 (patch) | |
tree | d97d633203823990c523e208bbdeb4a16c1ab00c /examples/widgets/doc | |
parent | 85e2f79e9eb4b55ba57abf1df9315e7e4bebde84 (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.qdoc | 169 | ||||
-rw-r--r-- | examples/widgets/doc/src/simplerhiwidget.qdoc | 203 |
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} +*/ |