171 lines
8.5 KiB
Plaintext
171 lines
8.5 KiB
Plaintext
// 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
|
|
\examplecategory {Graphics}
|
|
\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::sampleCount()}{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}
|
|
*/
|