Add QRhiWidget
Task-number: QTBUG-113331 Change-Id: I8baa697b4997b05f52acdee0e08d3c368fde5bc2 Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
parent
85e2f79e9e
commit
72a453c6a8
169
examples/widgets/doc/src/cuberhiwidget.qdoc
Normal file
169
examples/widgets/doc/src/cuberhiwidget.qdoc
Normal file
@ -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}
|
||||||
|
*/
|
203
examples/widgets/doc/src/simplerhiwidget.qdoc
Normal file
203
examples/widgets/doc/src/simplerhiwidget.qdoc
Normal file
@ -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}
|
||||||
|
*/
|
8
examples/widgets/rhi/CMakeLists.txt
Normal file
8
examples/widgets/rhi/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
if(NOT TARGET Qt6::Widgets)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
qt_internal_add_example(simplerhiwidget)
|
||||||
|
qt_internal_add_example(cuberhiwidget)
|
47
examples/widgets/rhi/cuberhiwidget/CMakeLists.txt
Normal file
47
examples/widgets/rhi/cuberhiwidget/CMakeLists.txt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(cuberhiwidget LANGUAGES CXX)
|
||||||
|
|
||||||
|
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||||
|
set(INSTALL_EXAMPLESDIR "examples")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/rhi/cuberhiwidget")
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||||
|
|
||||||
|
qt_standard_project_setup()
|
||||||
|
|
||||||
|
qt_add_executable(cuberhiwidget
|
||||||
|
examplewidget.cpp examplewidget.h cube.h
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(cuberhiwidget PROPERTIES
|
||||||
|
WIN32_EXECUTABLE TRUE
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
# needs GuiPrivate to be able to include <rhi/qrhi.h>
|
||||||
|
target_link_libraries(cuberhiwidget PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Gui
|
||||||
|
Qt6::GuiPrivate
|
||||||
|
Qt6::Widgets
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_resources(cuberhiwidget "cuberhiwidget"
|
||||||
|
PREFIX
|
||||||
|
"/"
|
||||||
|
FILES
|
||||||
|
"shader_assets/texture.vert.qsb"
|
||||||
|
"shader_assets/texture.frag.qsb"
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS cuberhiwidget
|
||||||
|
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
|
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
|
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
|
)
|
139
examples/widgets/rhi/cuberhiwidget/cube.h
Normal file
139
examples/widgets/rhi/cuberhiwidget/cube.h
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#ifndef CUBE_H
|
||||||
|
#define CUBE_H
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const float cube[] = {
|
||||||
|
-1.0f, -1.0f, -1.0f, // -X
|
||||||
|
-1.0f, -1.0f, 1.0f,
|
||||||
|
-1.0f, 1.0f, 1.0f,
|
||||||
|
-1.0f, 1.0f, 1.0f,
|
||||||
|
-1.0f, 1.0f, -1.0f,
|
||||||
|
-1.0f, -1.0f, -1.0f,
|
||||||
|
|
||||||
|
-1.0f, -1.0f, -1.0f, // -Z
|
||||||
|
1.0f, 1.0f, -1.0f,
|
||||||
|
1.0f, -1.0f, -1.0f,
|
||||||
|
-1.0f, -1.0f, -1.0f,
|
||||||
|
-1.0f, 1.0f, -1.0f,
|
||||||
|
1.0f, 1.0f, -1.0f,
|
||||||
|
|
||||||
|
-1.0f, -1.0f, -1.0f, // -Y
|
||||||
|
1.0f, -1.0f, -1.0f,
|
||||||
|
1.0f, -1.0f, 1.0f,
|
||||||
|
-1.0f, -1.0f, -1.0f,
|
||||||
|
1.0f, -1.0f, 1.0f,
|
||||||
|
-1.0f, -1.0f, 1.0f,
|
||||||
|
|
||||||
|
-1.0f, 1.0f, -1.0f, // +Y
|
||||||
|
-1.0f, 1.0f, 1.0f,
|
||||||
|
1.0f, 1.0f, 1.0f,
|
||||||
|
-1.0f, 1.0f, -1.0f,
|
||||||
|
1.0f, 1.0f, 1.0f,
|
||||||
|
1.0f, 1.0f, -1.0f,
|
||||||
|
|
||||||
|
1.0f, 1.0f, -1.0f, // +X
|
||||||
|
1.0f, 1.0f, 1.0f,
|
||||||
|
1.0f, -1.0f, 1.0f,
|
||||||
|
1.0f, -1.0f, 1.0f,
|
||||||
|
1.0f, -1.0f, -1.0f,
|
||||||
|
1.0f, 1.0f, -1.0f,
|
||||||
|
|
||||||
|
-1.0f, 1.0f, 1.0f, // +Z
|
||||||
|
-1.0f, -1.0f, 1.0f,
|
||||||
|
1.0f, 1.0f, 1.0f,
|
||||||
|
-1.0f, -1.0f, 1.0f,
|
||||||
|
1.0f, -1.0f, 1.0f,
|
||||||
|
1.0f, 1.0f, 1.0f,
|
||||||
|
|
||||||
|
// UVs
|
||||||
|
0.0f, 1.0f, // -X
|
||||||
|
1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
|
||||||
|
1.0f, 1.0f, // -Z
|
||||||
|
0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 0.0f,
|
||||||
|
|
||||||
|
1.0f, 0.0f, // -Y
|
||||||
|
1.0f, 1.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
0.0f, 0.0f,
|
||||||
|
|
||||||
|
1.0f, 0.0f, // +Y
|
||||||
|
0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
|
||||||
|
1.0f, 0.0f, // +X
|
||||||
|
0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
|
||||||
|
0.0f, 0.0f, // +Z
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
|
||||||
|
// normals
|
||||||
|
-1.0, 0.0, 0.0, // -X
|
||||||
|
-1.0, 0.0, 0.0,
|
||||||
|
-1.0, 0.0, 0.0,
|
||||||
|
-1.0, 0.0, 0.0,
|
||||||
|
-1.0, 0.0, 0.0,
|
||||||
|
-1.0, 0.0, 0.0,
|
||||||
|
|
||||||
|
0.0, 0.0, -1.0, // -Z
|
||||||
|
0.0, 0.0, -1.0,
|
||||||
|
0.0, 0.0, -1.0,
|
||||||
|
0.0, 0.0, -1.0,
|
||||||
|
0.0, 0.0, -1.0,
|
||||||
|
0.0, 0.0, -1.0,
|
||||||
|
|
||||||
|
0.0, -1.0, 0.0, // -Y
|
||||||
|
0.0, -1.0, 0.0,
|
||||||
|
0.0, -1.0, 0.0,
|
||||||
|
0.0, -1.0, 0.0,
|
||||||
|
0.0, -1.0, 0.0,
|
||||||
|
0.0, -1.0, 0.0,
|
||||||
|
|
||||||
|
0.0, 1.0, 0.0, // +Y
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
|
||||||
|
1.0, 0.0, 0.0, // +X
|
||||||
|
1.0, 0.0, 0.0,
|
||||||
|
1.0, 0.0, 0.0,
|
||||||
|
1.0, 0.0, 0.0,
|
||||||
|
1.0, 0.0, 0.0,
|
||||||
|
1.0, 0.0, 0.0,
|
||||||
|
|
||||||
|
0.0, 0.0, 1.0, // +Z
|
||||||
|
0.0, 0.0, 1.0,
|
||||||
|
0.0, 0.0, 1.0,
|
||||||
|
0.0, 0.0, 1.0,
|
||||||
|
0.0, 0.0, 1.0,
|
||||||
|
0.0, 0.0, 1.0
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#endif
|
12
examples/widgets/rhi/cuberhiwidget/cuberhiwidget.pro
Normal file
12
examples/widgets/rhi/cuberhiwidget/cuberhiwidget.pro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
# needs gui-private to be able to include <rhi/qrhi.h>
|
||||||
|
QT += gui-private widgets
|
||||||
|
|
||||||
|
HEADERS += examplewidget.h
|
||||||
|
SOURCES += examplewidget.cpp main.cpp
|
||||||
|
|
||||||
|
RESOURCES += cuberhiwidget.qrc
|
||||||
|
|
||||||
|
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/rhi/cuberhiwidget
|
||||||
|
INSTALLS += target
|
6
examples/widgets/rhi/cuberhiwidget/cuberhiwidget.qrc
Normal file
6
examples/widgets/rhi/cuberhiwidget/cuberhiwidget.qrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>shader_assets/texture.vert.qsb</file>
|
||||||
|
<file>shader_assets/texture.frag.qsb</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
172
examples/widgets/rhi/cuberhiwidget/examplewidget.cpp
Normal file
172
examples/widgets/rhi/cuberhiwidget/examplewidget.cpp
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#include "examplewidget.h"
|
||||||
|
#include "cube.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
static const QSize CUBE_TEX_SIZE(512, 512);
|
||||||
|
|
||||||
|
ExampleRhiWidget::ExampleRhiWidget(QWidget *parent)
|
||||||
|
: QRhiWidget(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//![init-1]
|
||||||
|
void ExampleRhiWidget::initialize(QRhiCommandBuffer *)
|
||||||
|
{
|
||||||
|
if (m_rhi != rhi()) {
|
||||||
|
m_rhi = rhi();
|
||||||
|
scene = {};
|
||||||
|
emit rhiChanged(QString::fromUtf8(m_rhi->backendName()));
|
||||||
|
}
|
||||||
|
if (m_pixelSize != renderTarget()->pixelSize()) {
|
||||||
|
m_pixelSize = renderTarget()->pixelSize();
|
||||||
|
emit resized();
|
||||||
|
}
|
||||||
|
if (m_sampleCount != renderTarget()->sampleCount()) {
|
||||||
|
m_sampleCount = renderTarget()->sampleCount();
|
||||||
|
scene = {};
|
||||||
|
}
|
||||||
|
//![init-1]
|
||||||
|
|
||||||
|
//![init-2]
|
||||||
|
if (!scene.vbuf) {
|
||||||
|
initScene();
|
||||||
|
updateCubeTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.mvp = m_rhi->clipSpaceCorrMatrix();
|
||||||
|
scene.mvp.perspective(45.0f, m_pixelSize.width() / (float) m_pixelSize.height(), 0.01f, 1000.0f);
|
||||||
|
scene.mvp.translate(0, 0, -4);
|
||||||
|
updateMvp();
|
||||||
|
}
|
||||||
|
//![init-2]
|
||||||
|
|
||||||
|
//![rotation-update]
|
||||||
|
void ExampleRhiWidget::updateMvp()
|
||||||
|
{
|
||||||
|
QMatrix4x4 mvp = scene.mvp * QMatrix4x4(QQuaternion::fromEulerAngles(QVector3D(30, itemData.cubeRotation, 0)).toRotationMatrix());
|
||||||
|
if (!scene.resourceUpdates)
|
||||||
|
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.get(), 0, 64, mvp.constData());
|
||||||
|
}
|
||||||
|
//![rotation-update]
|
||||||
|
|
||||||
|
//![texture-update]
|
||||||
|
void ExampleRhiWidget::updateCubeTexture()
|
||||||
|
{
|
||||||
|
QImage image(CUBE_TEX_SIZE, QImage::Format_RGBA8888);
|
||||||
|
const QRect r(QPoint(0, 0), CUBE_TEX_SIZE);
|
||||||
|
QPainter p(&image);
|
||||||
|
p.fillRect(r, QGradient::DeepBlue);
|
||||||
|
QFont font;
|
||||||
|
font.setPointSize(24);
|
||||||
|
p.setFont(font);
|
||||||
|
p.drawText(r, itemData.cubeText);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
if (!scene.resourceUpdates)
|
||||||
|
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
scene.resourceUpdates->uploadTexture(scene.cubeTex.get(), image);
|
||||||
|
}
|
||||||
|
//![texture-update]
|
||||||
|
|
||||||
|
static QShader getShader(const QString &name)
|
||||||
|
{
|
||||||
|
QFile f(name);
|
||||||
|
return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExampleRhiWidget::initScene()
|
||||||
|
{
|
||||||
|
//![setup-scene]
|
||||||
|
scene.vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)));
|
||||||
|
scene.vbuf->create();
|
||||||
|
|
||||||
|
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
scene.resourceUpdates->uploadStaticBuffer(scene.vbuf.get(), cube);
|
||||||
|
|
||||||
|
scene.ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
|
||||||
|
scene.ubuf->create();
|
||||||
|
|
||||||
|
scene.cubeTex.reset(m_rhi->newTexture(QRhiTexture::RGBA8, CUBE_TEX_SIZE));
|
||||||
|
scene.cubeTex->create();
|
||||||
|
|
||||||
|
scene.sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
|
||||||
|
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
|
||||||
|
scene.sampler->create();
|
||||||
|
|
||||||
|
scene.srb.reset(m_rhi->newShaderResourceBindings());
|
||||||
|
scene.srb->setBindings({
|
||||||
|
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, scene.ubuf.get()),
|
||||||
|
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.cubeTex.get(), scene.sampler.get())
|
||||||
|
});
|
||||||
|
scene.srb->create();
|
||||||
|
|
||||||
|
scene.ps.reset(m_rhi->newGraphicsPipeline());
|
||||||
|
scene.ps->setDepthTest(true);
|
||||||
|
scene.ps->setDepthWrite(true);
|
||||||
|
scene.ps->setCullMode(QRhiGraphicsPipeline::Back);
|
||||||
|
scene.ps->setShaderStages({
|
||||||
|
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/shader_assets/texture.vert.qsb")) },
|
||||||
|
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/shader_assets/texture.frag.qsb")) }
|
||||||
|
});
|
||||||
|
QRhiVertexInputLayout inputLayout;
|
||||||
|
// The cube is provided as non-interleaved sets of positions, UVs, normals.
|
||||||
|
// Normals are not interesting here, only need the positions and UVs.
|
||||||
|
inputLayout.setBindings({
|
||||||
|
{ 3 * sizeof(float) },
|
||||||
|
{ 2 * sizeof(float) }
|
||||||
|
});
|
||||||
|
inputLayout.setAttributes({
|
||||||
|
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
|
||||||
|
{ 1, 1, QRhiVertexInputAttribute::Float2, 0 }
|
||||||
|
});
|
||||||
|
scene.ps->setSampleCount(m_sampleCount);
|
||||||
|
scene.ps->setVertexInputLayout(inputLayout);
|
||||||
|
scene.ps->setShaderResourceBindings(scene.srb.get());
|
||||||
|
scene.ps->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
|
||||||
|
scene.ps->create();
|
||||||
|
//![setup-scene]
|
||||||
|
}
|
||||||
|
|
||||||
|
//![render]
|
||||||
|
void ExampleRhiWidget::render(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
if (itemData.cubeRotationDirty) {
|
||||||
|
itemData.cubeRotationDirty = false;
|
||||||
|
updateMvp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemData.cubeTextDirty) {
|
||||||
|
itemData.cubeTextDirty = false;
|
||||||
|
updateCubeTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = scene.resourceUpdates;
|
||||||
|
if (resourceUpdates)
|
||||||
|
scene.resourceUpdates = nullptr;
|
||||||
|
|
||||||
|
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
|
||||||
|
cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
|
||||||
|
|
||||||
|
cb->setGraphicsPipeline(scene.ps.get());
|
||||||
|
cb->setViewport(QRhiViewport(0, 0, m_pixelSize.width(), m_pixelSize.height()));
|
||||||
|
cb->setShaderResources();
|
||||||
|
const QRhiCommandBuffer::VertexInput vbufBindings[] = {
|
||||||
|
{ scene.vbuf.get(), 0 },
|
||||||
|
{ scene.vbuf.get(), quint32(36 * 3 * sizeof(float)) }
|
||||||
|
};
|
||||||
|
cb->setVertexInput(0, 2, vbufBindings);
|
||||||
|
cb->draw(36);
|
||||||
|
|
||||||
|
cb->endPass();
|
||||||
|
}
|
||||||
|
//![render]
|
||||||
|
|
||||||
|
void ExampleRhiWidget::releaseResources()
|
||||||
|
{
|
||||||
|
scene = {}; // a subsequent initialize() will recreate everything
|
||||||
|
}
|
72
examples/widgets/rhi/cuberhiwidget/examplewidget.h
Normal file
72
examples/widgets/rhi/cuberhiwidget/examplewidget.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#ifndef EXAMPLEWIDGET_H
|
||||||
|
#define EXAMPLEWIDGET_H
|
||||||
|
|
||||||
|
#include <QRhiWidget>
|
||||||
|
#include <rhi/qrhi.h>
|
||||||
|
|
||||||
|
class ExampleRhiWidget : public QRhiWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ExampleRhiWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void initialize(QRhiCommandBuffer *cb) override;
|
||||||
|
void render(QRhiCommandBuffer *cb) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
//![data-setters]
|
||||||
|
void setCubeTextureText(const QString &s)
|
||||||
|
{
|
||||||
|
if (itemData.cubeText == s)
|
||||||
|
return;
|
||||||
|
itemData.cubeText = s;
|
||||||
|
itemData.cubeTextDirty = true;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCubeRotation(float r)
|
||||||
|
{
|
||||||
|
if (itemData.cubeRotation == r)
|
||||||
|
return;
|
||||||
|
itemData.cubeRotation = r;
|
||||||
|
itemData.cubeRotationDirty = true;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
//![data-setters]
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void resized();
|
||||||
|
void rhiChanged(const QString &apiName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QRhi *m_rhi = nullptr;
|
||||||
|
int m_sampleCount = 1;
|
||||||
|
QSize m_pixelSize;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = nullptr;
|
||||||
|
std::unique_ptr<QRhiBuffer> vbuf;
|
||||||
|
std::unique_ptr<QRhiBuffer> ubuf;
|
||||||
|
std::unique_ptr<QRhiShaderResourceBindings> srb;
|
||||||
|
std::unique_ptr<QRhiGraphicsPipeline> ps;
|
||||||
|
std::unique_ptr<QRhiSampler> sampler;
|
||||||
|
std::unique_ptr<QRhiTexture> cubeTex;
|
||||||
|
QMatrix4x4 mvp;
|
||||||
|
} scene;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
QString cubeText;
|
||||||
|
bool cubeTextDirty = false;
|
||||||
|
float cubeRotation = 0.0f;
|
||||||
|
bool cubeRotationDirty = false;
|
||||||
|
} itemData;
|
||||||
|
|
||||||
|
void initScene();
|
||||||
|
void updateMvp();
|
||||||
|
void updateCubeTexture();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
166
examples/widgets/rhi/cuberhiwidget/main.cpp
Normal file
166
examples/widgets/rhi/cuberhiwidget/main.cpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFontInfo>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QDrag>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include "examplewidget.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
ExampleRhiWidget *rhiWidget = new ExampleRhiWidget;
|
||||||
|
QLabel *overlayLabel = new QLabel(rhiWidget);
|
||||||
|
overlayLabel->setText(QLatin1String("This is a\nsemi-transparent\n overlay widget\n"
|
||||||
|
"placed on top of\nthe QRhiWidget."));
|
||||||
|
overlayLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||||
|
overlayLabel->setAutoFillBackground(true);
|
||||||
|
QPalette semiTransparent(QColor(255, 0, 0, 64));
|
||||||
|
semiTransparent.setBrush(QPalette::Text, Qt::white);
|
||||||
|
semiTransparent.setBrush(QPalette::WindowText, Qt::white);
|
||||||
|
overlayLabel->setPalette(semiTransparent);
|
||||||
|
QFont f = overlayLabel->font();
|
||||||
|
f.setPixelSize(QFontInfo(f).pixelSize() * 2);
|
||||||
|
f.setWeight(QFont::Bold);
|
||||||
|
overlayLabel->setFont(f);
|
||||||
|
overlayLabel->resize(320, 320);
|
||||||
|
overlayLabel->hide();
|
||||||
|
QObject::connect(rhiWidget, &ExampleRhiWidget::resized, rhiWidget, [rhiWidget, overlayLabel] {
|
||||||
|
const int w = overlayLabel->width();
|
||||||
|
const int h = overlayLabel->height();
|
||||||
|
overlayLabel->setGeometry(rhiWidget->width() / 2 - w / 2, rhiWidget->height() / 2 - h / 2, w, h);
|
||||||
|
});
|
||||||
|
|
||||||
|
QTextEdit *edit = new QTextEdit(QLatin1String("QRhiWidget!<br><br>"
|
||||||
|
"The cube is textured with QPainter-generated content.<br><br>"
|
||||||
|
"Regular, non-native widgets on top work just fine."));
|
||||||
|
QObject::connect(edit, &QTextEdit::textChanged, edit, [edit, rhiWidget] {
|
||||||
|
rhiWidget->setCubeTextureText(edit->toPlainText());
|
||||||
|
});
|
||||||
|
edit->setMaximumHeight(100);
|
||||||
|
layout->addWidget(edit);
|
||||||
|
|
||||||
|
QSlider *slider = new QSlider(Qt::Horizontal);
|
||||||
|
slider->setMinimum(0);
|
||||||
|
slider->setMaximum(360);
|
||||||
|
QObject::connect(slider, &QSlider::valueChanged, slider, [slider, rhiWidget] {
|
||||||
|
rhiWidget->setCubeRotation(slider->value());
|
||||||
|
});
|
||||||
|
|
||||||
|
QHBoxLayout *sliderLayout = new QHBoxLayout;
|
||||||
|
sliderLayout->addWidget(new QLabel(QLatin1String("Cube rotation")));
|
||||||
|
sliderLayout->addWidget(slider);
|
||||||
|
layout->addLayout(sliderLayout);
|
||||||
|
|
||||||
|
QHBoxLayout *btnLayout = new QHBoxLayout;
|
||||||
|
|
||||||
|
QLabel *apiLabel = new QLabel;
|
||||||
|
btnLayout->addWidget(apiLabel);
|
||||||
|
QObject::connect(rhiWidget, &ExampleRhiWidget::rhiChanged, rhiWidget, [apiLabel](const QString &apiName) {
|
||||||
|
apiLabel->setText(QLatin1String("Using QRhi on ") + apiName);
|
||||||
|
});
|
||||||
|
|
||||||
|
QPushButton *btnMakeWindow = new QPushButton(QLatin1String("Make top-level window"));
|
||||||
|
QObject::connect(btnMakeWindow, &QPushButton::clicked, btnMakeWindow, [rhiWidget, btnMakeWindow, layout] {
|
||||||
|
if (rhiWidget->parentWidget()) {
|
||||||
|
rhiWidget->setParent(nullptr);
|
||||||
|
rhiWidget->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
|
rhiWidget->show();
|
||||||
|
btnMakeWindow->setText(QLatin1String("Make child widget"));
|
||||||
|
} else {
|
||||||
|
rhiWidget->setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
btnMakeWindow->setText(QLatin1String("Make top-level window"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnLayout->addWidget(btnMakeWindow);
|
||||||
|
|
||||||
|
QPushButton *btn = new QPushButton(QLatin1String("Grab to image"));
|
||||||
|
QObject::connect(btn, &QPushButton::clicked, btn, [rhiWidget] {
|
||||||
|
QImage image = rhiWidget->grab();
|
||||||
|
qDebug() << "Got image" << image;
|
||||||
|
if (!image.isNull()) {
|
||||||
|
QFileDialog fd(rhiWidget->parentWidget());
|
||||||
|
fd.setAcceptMode(QFileDialog::AcceptSave);
|
||||||
|
fd.setDefaultSuffix("png");
|
||||||
|
fd.selectFile("test.png");
|
||||||
|
if (fd.exec() == QDialog::Accepted)
|
||||||
|
image.save(fd.selectedFiles().first());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnLayout->addWidget(btn);
|
||||||
|
|
||||||
|
QCheckBox *cbMsaa = new QCheckBox(QLatin1String("Use 4x MSAA"));
|
||||||
|
QObject::connect(cbMsaa, &QCheckBox::stateChanged, cbMsaa, [cbMsaa, rhiWidget] {
|
||||||
|
if (cbMsaa->isChecked())
|
||||||
|
rhiWidget->setSampleCount(4);
|
||||||
|
else
|
||||||
|
rhiWidget->setSampleCount(1);
|
||||||
|
});
|
||||||
|
btnLayout->addWidget(cbMsaa);
|
||||||
|
|
||||||
|
QCheckBox *cbOvberlay = new QCheckBox(QLatin1String("Show overlay widget"));
|
||||||
|
QObject::connect(cbOvberlay, &QCheckBox::stateChanged, cbOvberlay, [cbOvberlay, overlayLabel] {
|
||||||
|
if (cbOvberlay->isChecked())
|
||||||
|
overlayLabel->setVisible(true);
|
||||||
|
else
|
||||||
|
overlayLabel->setVisible(false);
|
||||||
|
});
|
||||||
|
btnLayout->addWidget(cbOvberlay);
|
||||||
|
|
||||||
|
QCheckBox *cbFlip = new QCheckBox(QLatin1String("Flip"));
|
||||||
|
QObject::connect(cbFlip, &QCheckBox::stateChanged, cbOvberlay, [cbFlip, rhiWidget] {
|
||||||
|
rhiWidget->setMirrorVertically(cbFlip->isChecked());
|
||||||
|
});
|
||||||
|
btnLayout->addWidget(cbFlip);
|
||||||
|
|
||||||
|
QCheckBox *cbExplicitSize = new QCheckBox(QLatin1String("Use explicit size"));
|
||||||
|
btnLayout->addWidget(cbExplicitSize);
|
||||||
|
QSlider *explicitSizeSlider = new QSlider(Qt::Horizontal);
|
||||||
|
explicitSizeSlider->setMinimum(16);
|
||||||
|
explicitSizeSlider->setMaximum(512);
|
||||||
|
btnLayout->addWidget(explicitSizeSlider);
|
||||||
|
|
||||||
|
QObject::connect(cbExplicitSize, &QCheckBox::stateChanged, cbExplicitSize, [cbExplicitSize, explicitSizeSlider, rhiWidget] {
|
||||||
|
if (cbExplicitSize->isChecked())
|
||||||
|
rhiWidget->setExplicitSize(QSize(explicitSizeSlider->value(), explicitSizeSlider->value()));
|
||||||
|
else
|
||||||
|
rhiWidget->setExplicitSize(QSize());
|
||||||
|
});
|
||||||
|
QObject::connect(explicitSizeSlider, &QSlider::valueChanged, explicitSizeSlider, [explicitSizeSlider, cbExplicitSize, rhiWidget] {
|
||||||
|
if (cbExplicitSize->isChecked())
|
||||||
|
rhiWidget->setExplicitSize(QSize(explicitSizeSlider->value(), explicitSizeSlider->value()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exit when the detached window is closed; there is not much we can do
|
||||||
|
// with the controls in the main window then.
|
||||||
|
QObject::connect(rhiWidget, &QObject::destroyed, rhiWidget, [rhiWidget] {
|
||||||
|
if (!rhiWidget->parentWidget())
|
||||||
|
qGuiApp->quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
layout->addLayout(btnLayout);
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
|
||||||
|
rhiWidget->setCubeTextureText(edit->toPlainText());
|
||||||
|
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
12
examples/widgets/rhi/cuberhiwidget/shaders/texture.frag
Normal file
12
examples/widgets/rhi/cuberhiwidget/shaders/texture.frag
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_texcoord;
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
layout(binding = 1) uniform sampler2D tex;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 c = texture(tex, v_texcoord);
|
||||||
|
fragColor = vec4(c.rgb * c.a, c.a);
|
||||||
|
}
|
15
examples/widgets/rhi/cuberhiwidget/shaders/texture.vert
Normal file
15
examples/widgets/rhi/cuberhiwidget/shaders/texture.vert
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 position;
|
||||||
|
layout(location = 1) in vec2 texcoord;
|
||||||
|
layout(location = 0) out vec2 v_texcoord;
|
||||||
|
|
||||||
|
layout(std140, binding = 0) uniform buf {
|
||||||
|
mat4 mvp;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
v_texcoord = vec2(texcoord.x, texcoord.y);
|
||||||
|
gl_Position = mvp * position;
|
||||||
|
}
|
4
examples/widgets/rhi/rhi.pro
Normal file
4
examples/widgets/rhi/rhi.pro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
requires(qtHaveModule(widgets))
|
||||||
|
TEMPLATE = subdirs
|
||||||
|
SUBDIRS = simplerhiwidget \
|
||||||
|
cuberhiwidget
|
47
examples/widgets/rhi/simplerhiwidget/CMakeLists.txt
Normal file
47
examples/widgets/rhi/simplerhiwidget/CMakeLists.txt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(simplerhiwidget LANGUAGES CXX)
|
||||||
|
|
||||||
|
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||||
|
set(INSTALL_EXAMPLESDIR "examples")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/rhi/simplerhiwidget")
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||||
|
|
||||||
|
qt_standard_project_setup()
|
||||||
|
|
||||||
|
qt_add_executable(simplerhiwidget
|
||||||
|
examplewidget.cpp examplewidget.h
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(simplerhiwidget PROPERTIES
|
||||||
|
WIN32_EXECUTABLE TRUE
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
# needs GuiPrivate to be able to include <rhi/qrhi.h>
|
||||||
|
target_link_libraries(simplerhiwidget PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Gui
|
||||||
|
Qt6::GuiPrivate
|
||||||
|
Qt6::Widgets
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_resources(simplerhiwidget "simplerhiwidget"
|
||||||
|
PREFIX
|
||||||
|
"/"
|
||||||
|
FILES
|
||||||
|
"shader_assets/color.vert.qsb"
|
||||||
|
"shader_assets/color.frag.qsb"
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS simplerhiwidget
|
||||||
|
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
|
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
|
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
|
)
|
102
examples/widgets/rhi/simplerhiwidget/examplewidget.cpp
Normal file
102
examples/widgets/rhi/simplerhiwidget/examplewidget.cpp
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#include "examplewidget.h"
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
static float vertexData[] = {
|
||||||
|
0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
|
||||||
|
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
|
||||||
|
0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
//![get-shader]
|
||||||
|
static QShader getShader(const QString &name)
|
||||||
|
{
|
||||||
|
QFile f(name);
|
||||||
|
return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
|
||||||
|
}
|
||||||
|
//![get-shader]
|
||||||
|
|
||||||
|
//![init-1]
|
||||||
|
void ExampleRhiWidget::initialize(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
if (m_rhi != rhi()) {
|
||||||
|
m_pipeline.reset();
|
||||||
|
m_rhi = rhi();
|
||||||
|
}
|
||||||
|
//![init-1]
|
||||||
|
//![init-pipeline]
|
||||||
|
if (!m_pipeline) {
|
||||||
|
m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
|
||||||
|
m_vbuf->create();
|
||||||
|
|
||||||
|
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
|
||||||
|
m_ubuf->create();
|
||||||
|
|
||||||
|
m_srb.reset(m_rhi->newShaderResourceBindings());
|
||||||
|
m_srb->setBindings({
|
||||||
|
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()),
|
||||||
|
});
|
||||||
|
m_srb->create();
|
||||||
|
|
||||||
|
m_pipeline.reset(m_rhi->newGraphicsPipeline());
|
||||||
|
m_pipeline->setShaderStages({
|
||||||
|
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/shader_assets/color.vert.qsb")) },
|
||||||
|
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/shader_assets/color.frag.qsb")) }
|
||||||
|
});
|
||||||
|
QRhiVertexInputLayout inputLayout;
|
||||||
|
inputLayout.setBindings({
|
||||||
|
{ 5 * sizeof(float) }
|
||||||
|
});
|
||||||
|
inputLayout.setAttributes({
|
||||||
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
|
||||||
|
{ 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
|
||||||
|
});
|
||||||
|
m_pipeline->setVertexInputLayout(inputLayout);
|
||||||
|
m_pipeline->setShaderResourceBindings(m_srb.get());
|
||||||
|
m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
|
||||||
|
m_pipeline->create();
|
||||||
|
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
|
||||||
|
cb->resourceUpdate(resourceUpdates);
|
||||||
|
}
|
||||||
|
//![init-pipeline]
|
||||||
|
|
||||||
|
//![init-matrix]
|
||||||
|
const QSize outputSize = renderTarget()->pixelSize();
|
||||||
|
m_viewProjection = m_rhi->clipSpaceCorrMatrix();
|
||||||
|
m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
|
||||||
|
m_viewProjection.translate(0, 0, -4);
|
||||||
|
}
|
||||||
|
//![init-matrix]
|
||||||
|
|
||||||
|
//![render-1]
|
||||||
|
void ExampleRhiWidget::render(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
m_rotation += 1.0f;
|
||||||
|
QMatrix4x4 modelViewProjection = m_viewProjection;
|
||||||
|
modelViewProjection.rotate(m_rotation, 0, 1, 0);
|
||||||
|
resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());
|
||||||
|
//![render-1]
|
||||||
|
//![render-pass]
|
||||||
|
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
|
||||||
|
cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
|
||||||
|
|
||||||
|
cb->setGraphicsPipeline(m_pipeline.get());
|
||||||
|
const QSize outputSize = renderTarget()->pixelSize();
|
||||||
|
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
|
||||||
|
cb->setShaderResources();
|
||||||
|
const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
|
||||||
|
cb->setVertexInput(0, 1, &vbufBinding);
|
||||||
|
cb->draw(3);
|
||||||
|
|
||||||
|
cb->endPass();
|
||||||
|
//![render-pass]
|
||||||
|
|
||||||
|
//![render-2]
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
//![render-2]
|
30
examples/widgets/rhi/simplerhiwidget/examplewidget.h
Normal file
30
examples/widgets/rhi/simplerhiwidget/examplewidget.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#ifndef EXAMPLEWIDGET_H
|
||||||
|
#define EXAMPLEWIDGET_H
|
||||||
|
|
||||||
|
//![0]
|
||||||
|
#include <QRhiWidget>
|
||||||
|
#include <rhi/qrhi.h>
|
||||||
|
|
||||||
|
class ExampleRhiWidget : public QRhiWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExampleRhiWidget(QWidget *parent = nullptr) : QRhiWidget(parent) { }
|
||||||
|
|
||||||
|
void initialize(QRhiCommandBuffer *cb) override;
|
||||||
|
void render(QRhiCommandBuffer *cb) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QRhi *m_rhi = nullptr;
|
||||||
|
std::unique_ptr<QRhiBuffer> m_vbuf;
|
||||||
|
std::unique_ptr<QRhiBuffer> m_ubuf;
|
||||||
|
std::unique_ptr<QRhiShaderResourceBindings> m_srb;
|
||||||
|
std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
|
||||||
|
QMatrix4x4 m_viewProjection;
|
||||||
|
float m_rotation = 0.0f;
|
||||||
|
};
|
||||||
|
//![0]
|
||||||
|
|
||||||
|
#endif
|
26
examples/widgets/rhi/simplerhiwidget/main.cpp
Normal file
26
examples/widgets/rhi/simplerhiwidget/main.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include "examplewidget.h"
|
||||||
|
|
||||||
|
//![0]
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
ExampleRhiWidget *rhiWidget = new ExampleRhiWidget;
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
//![0]
|
Binary file not shown.
Binary file not shown.
10
examples/widgets/rhi/simplerhiwidget/shaders/color.frag
Normal file
10
examples/widgets/rhi/simplerhiwidget/shaders/color.frag
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 v_color;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
fragColor = vec4(v_color, 1.0);
|
||||||
|
}
|
16
examples/widgets/rhi/simplerhiwidget/shaders/color.vert
Normal file
16
examples/widgets/rhi/simplerhiwidget/shaders/color.vert
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 position;
|
||||||
|
layout(location = 1) in vec3 color;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 v_color;
|
||||||
|
|
||||||
|
layout(std140, binding = 0) uniform buf {
|
||||||
|
mat4 mvp;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
v_color = color;
|
||||||
|
gl_Position = mvp * position;
|
||||||
|
}
|
12
examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pro
Normal file
12
examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
# needs gui-private to be able to include <rhi/qrhi.h>
|
||||||
|
QT += gui-private widgets
|
||||||
|
|
||||||
|
HEADERS += examplewidget.h
|
||||||
|
SOURCES += examplewidget.cpp main.cpp
|
||||||
|
|
||||||
|
RESOURCES += simplerhiwidget.qrc
|
||||||
|
|
||||||
|
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/rhi/simplerhiwidget
|
||||||
|
INSTALLS += target
|
6
examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc
Normal file
6
examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>shader_assets/color.vert.qsb</file>
|
||||||
|
<file>shader_assets/color.frag.qsb</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
@ -15,6 +15,7 @@ SUBDIRS = \
|
|||||||
layouts \
|
layouts \
|
||||||
mainwindows \
|
mainwindows \
|
||||||
painting \
|
painting \
|
||||||
|
rhi \
|
||||||
richtext \
|
richtext \
|
||||||
tools \
|
tools \
|
||||||
touch \
|
touch \
|
||||||
|
@ -90,6 +90,18 @@
|
|||||||
portable, cross-platform application that performs accelerated 3D rendering
|
portable, cross-platform application that performs accelerated 3D rendering
|
||||||
onto a QWindow using QRhi.
|
onto a QWindow using QRhi.
|
||||||
|
|
||||||
|
Working directly with QWindow is the most advanced and often the most
|
||||||
|
flexible way of rendering with the QRhi API. It is the most low-level
|
||||||
|
approach, however, and limited in the sense that Qt's UI technologies,
|
||||||
|
widgets and Qt Quick, are not utilized at all. In many cases applications
|
||||||
|
will rather want to integrate QRhi-based rendering into a widget or Qt
|
||||||
|
Quick-based user interface. QWidget-based applications may choose to embed
|
||||||
|
the window as a native child into the widget hierarchy via
|
||||||
|
QWidget::createWindowContainer(), but in many cases \l QRhiWidget will
|
||||||
|
offer a more convenient enabler to integrate QRhi-based rendering into a
|
||||||
|
widget UI. Qt Quick provides its own set of enablers for extending the
|
||||||
|
2D/3D scene with QRhi-based custom rendering.
|
||||||
|
|
||||||
\note The RHI family of APIs are currently offered with a limited
|
\note The RHI family of APIs are currently offered with a limited
|
||||||
compatibility guarantee, as opposed to regular Qt public APIs. See \l QRhi
|
compatibility guarantee, as opposed to regular Qt public APIs. See \l QRhi
|
||||||
for details.
|
for details.
|
||||||
|
@ -551,10 +551,13 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < textureWidgetCount; ++i) {
|
for (int i = 0; i < textureWidgetCount; ++i) {
|
||||||
|
const bool invertSourceForTextureWidget = textures->flags(i).testFlag(QPlatformTextureList::MirrorVertically)
|
||||||
|
? !invertSource : invertSource;
|
||||||
QMatrix4x4 target;
|
QMatrix4x4 target;
|
||||||
QMatrix3x3 source;
|
QMatrix3x3 source;
|
||||||
if (!prepareDrawForRenderToTextureWidget(textures, i, window, deviceWindowRect,
|
if (!prepareDrawForRenderToTextureWidget(textures, i, window, deviceWindowRect,
|
||||||
offset, invertTargetY, invertSource, &target, &source))
|
offset, invertTargetY, invertSourceForTextureWidget,
|
||||||
|
&target, &source))
|
||||||
{
|
{
|
||||||
m_textureQuadData[i].reset();
|
m_textureQuadData[i].reset();
|
||||||
continue;
|
continue;
|
||||||
|
@ -94,7 +94,8 @@ public:
|
|||||||
enum Flag {
|
enum Flag {
|
||||||
StacksOnTop = 0x01,
|
StacksOnTop = 0x01,
|
||||||
TextureIsSrgb = 0x02,
|
TextureIsSrgb = 0x02,
|
||||||
NeedsPremultipliedAlphaBlending = 0x04
|
NeedsPremultipliedAlphaBlending = 0x04,
|
||||||
|
MirrorVertically = 0x08
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Flags, Flag)
|
Q_DECLARE_FLAGS(Flags, Flag)
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ qt_internal_add_module(Widgets
|
|||||||
kernel/qlayout.cpp kernel/qlayout.h kernel/qlayout_p.h
|
kernel/qlayout.cpp kernel/qlayout.h kernel/qlayout_p.h
|
||||||
kernel/qlayoutengine.cpp kernel/qlayoutengine_p.h
|
kernel/qlayoutengine.cpp kernel/qlayoutengine_p.h
|
||||||
kernel/qlayoutitem.cpp kernel/qlayoutitem.h
|
kernel/qlayoutitem.cpp kernel/qlayoutitem.h
|
||||||
|
kernel/qrhiwidget.cpp kernel/qrhiwidget.h kernel/qrhiwidget_p.h
|
||||||
kernel/qsizepolicy.cpp kernel/qsizepolicy.h
|
kernel/qsizepolicy.cpp kernel/qsizepolicy.h
|
||||||
kernel/qstackedlayout.cpp kernel/qstackedlayout.h
|
kernel/qstackedlayout.cpp kernel/qstackedlayout.h
|
||||||
kernel/qstandardgestures.cpp kernel/qstandardgestures_p.h
|
kernel/qstandardgestures.cpp kernel/qstandardgestures_p.h
|
||||||
|
BIN
src/widgets/doc/images/cuberhiwidget-example.jpg
Normal file
BIN
src/widgets/doc/images/cuberhiwidget-example.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
BIN
src/widgets/doc/images/qrhiwidget-intro.jpg
Normal file
BIN
src/widgets/doc/images/qrhiwidget-intro.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
BIN
src/widgets/doc/images/simplerhiwidget-example.jpg
Normal file
BIN
src/widgets/doc/images/simplerhiwidget-example.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
109
src/widgets/doc/snippets/qrhiwidget/rhiwidgetintro.cpp
Normal file
109
src/widgets/doc/snippets/qrhiwidget/rhiwidgetintro.cpp
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#include <QRhiWidget>
|
||||||
|
#include <QFile>
|
||||||
|
#include <rhi/qrhi.h>
|
||||||
|
|
||||||
|
//![0]
|
||||||
|
class ExampleRhiWidget : public QRhiWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExampleRhiWidget(QWidget *parent = nullptr) : QRhiWidget(parent) { }
|
||||||
|
void initialize(QRhiCommandBuffer *cb) override;
|
||||||
|
void render(QRhiCommandBuffer *cb) override;
|
||||||
|
private:
|
||||||
|
QRhi *m_rhi = nullptr;
|
||||||
|
std::unique_ptr<QRhiBuffer> m_vbuf;
|
||||||
|
std::unique_ptr<QRhiBuffer> m_ubuf;
|
||||||
|
std::unique_ptr<QRhiShaderResourceBindings> m_srb;
|
||||||
|
std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
|
||||||
|
QMatrix4x4 m_viewProjection;
|
||||||
|
float m_rotation = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
float vertexData[] = {
|
||||||
|
0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
|
||||||
|
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
|
||||||
|
0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
QShader getShader(const QString &name)
|
||||||
|
{
|
||||||
|
QFile f(name);
|
||||||
|
return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExampleRhiWidget::initialize(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
if (m_rhi != rhi()) {
|
||||||
|
m_pipeline.reset();
|
||||||
|
m_rhi = rhi();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_pipeline) {
|
||||||
|
m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
|
||||||
|
m_vbuf->create();
|
||||||
|
|
||||||
|
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
|
||||||
|
m_ubuf->create();
|
||||||
|
|
||||||
|
m_srb.reset(m_rhi->newShaderResourceBindings());
|
||||||
|
m_srb->setBindings({
|
||||||
|
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()),
|
||||||
|
});
|
||||||
|
m_srb->create();
|
||||||
|
|
||||||
|
m_pipeline.reset(m_rhi->newGraphicsPipeline());
|
||||||
|
m_pipeline->setShaderStages({
|
||||||
|
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/shader_assets/color.vert.qsb")) },
|
||||||
|
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/shader_assets/color.frag.qsb")) }
|
||||||
|
});
|
||||||
|
QRhiVertexInputLayout inputLayout;
|
||||||
|
inputLayout.setBindings({
|
||||||
|
{ 5 * sizeof(float) }
|
||||||
|
});
|
||||||
|
inputLayout.setAttributes({
|
||||||
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
|
||||||
|
{ 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
|
||||||
|
});
|
||||||
|
m_pipeline->setVertexInputLayout(inputLayout);
|
||||||
|
m_pipeline->setShaderResourceBindings(m_srb.get());
|
||||||
|
m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
|
||||||
|
m_pipeline->create();
|
||||||
|
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
|
||||||
|
cb->resourceUpdate(resourceUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSize outputSize = colorTexture()->pixelSize();
|
||||||
|
m_viewProjection = m_rhi->clipSpaceCorrMatrix();
|
||||||
|
m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
|
||||||
|
m_viewProjection.translate(0, 0, -4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExampleRhiWidget::render(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
m_rotation += 1.0f;
|
||||||
|
QMatrix4x4 modelViewProjection = m_viewProjection;
|
||||||
|
modelViewProjection.rotate(m_rotation, 0, 1, 0);
|
||||||
|
resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());
|
||||||
|
|
||||||
|
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
|
||||||
|
cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
|
||||||
|
|
||||||
|
cb->setGraphicsPipeline(m_pipeline.get());
|
||||||
|
const QSize outputSize = colorTexture()->pixelSize();
|
||||||
|
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
|
||||||
|
cb->setShaderResources();
|
||||||
|
const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
|
||||||
|
cb->setVertexInput(0, 1, &vbufBinding);
|
||||||
|
cb->draw(3);
|
||||||
|
|
||||||
|
cb->endPass();
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
//![0]
|
10
src/widgets/doc/snippets/qrhiwidget/rhiwidgetintro.frag
Normal file
10
src/widgets/doc/snippets/qrhiwidget/rhiwidgetintro.frag
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//![0]
|
||||||
|
#version 440
|
||||||
|
layout(location = 0) in vec3 v_color;
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
fragColor = vec4(v_color, 1.0);
|
||||||
|
}
|
||||||
|
//![0]
|
15
src/widgets/doc/snippets/qrhiwidget/rhiwidgetintro.vert
Normal file
15
src/widgets/doc/snippets/qrhiwidget/rhiwidgetintro.vert
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//![0]
|
||||||
|
#version 440
|
||||||
|
layout(location = 0) in vec4 position;
|
||||||
|
layout(location = 1) in vec3 color;
|
||||||
|
layout(location = 0) out vec3 v_color;
|
||||||
|
layout(std140, binding = 0) uniform buf {
|
||||||
|
mat4 mvp;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
v_color = color;
|
||||||
|
gl_Position = mvp * position;
|
||||||
|
}
|
||||||
|
//![0]
|
1283
src/widgets/kernel/qrhiwidget.cpp
Normal file
1283
src/widgets/kernel/qrhiwidget.cpp
Normal file
File diff suppressed because it is too large
Load Diff
102
src/widgets/kernel/qrhiwidget.h
Normal file
102
src/widgets/kernel/qrhiwidget.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#ifndef QRHIWIDGET_H
|
||||||
|
#define QRHIWIDGET_H
|
||||||
|
|
||||||
|
#include <QtWidgets/qwidget.h>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class QRhiWidgetPrivate;
|
||||||
|
class QRhi;
|
||||||
|
class QRhiTexture;
|
||||||
|
class QRhiRenderBuffer;
|
||||||
|
class QRhiTextureRenderTarget;
|
||||||
|
class QRhiCommandBuffer;
|
||||||
|
|
||||||
|
class Q_WIDGETS_EXPORT QRhiWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DECLARE_PRIVATE(QRhiWidget)
|
||||||
|
Q_PROPERTY(int sampleCount READ sampleCount WRITE setSampleCount NOTIFY sampleCountChanged)
|
||||||
|
Q_PROPERTY(TextureFormat textureFormat READ textureFormat WRITE setTextureFormat NOTIFY textureFormatChanged)
|
||||||
|
Q_PROPERTY(bool autoRenderTarget READ isAutoRenderTargetEnabled WRITE setAutoRenderTarget NOTIFY autoRenderTargetChanged)
|
||||||
|
Q_PROPERTY(QSize explicitSize READ explicitSize WRITE setExplicitSize NOTIFY explicitSizeChanged)
|
||||||
|
Q_PROPERTY(bool mirrorVertically READ isMirrorVerticallyEnabled WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {});
|
||||||
|
~QRhiWidget();
|
||||||
|
|
||||||
|
enum class Api {
|
||||||
|
OpenGL,
|
||||||
|
Metal,
|
||||||
|
Vulkan,
|
||||||
|
D3D11,
|
||||||
|
D3D12,
|
||||||
|
Null
|
||||||
|
};
|
||||||
|
Q_ENUM(Api)
|
||||||
|
|
||||||
|
enum class TextureFormat {
|
||||||
|
RGBA8,
|
||||||
|
RGBA16F,
|
||||||
|
RGBA32F,
|
||||||
|
RGB10A2
|
||||||
|
};
|
||||||
|
Q_ENUM(TextureFormat)
|
||||||
|
|
||||||
|
Api api() const;
|
||||||
|
void setApi(Api api);
|
||||||
|
|
||||||
|
bool isDebugLayerEnabled() const;
|
||||||
|
void setDebugLayer(bool enable);
|
||||||
|
|
||||||
|
int sampleCount() const;
|
||||||
|
void setSampleCount(int samples);
|
||||||
|
|
||||||
|
TextureFormat textureFormat() const;
|
||||||
|
void setTextureFormat(TextureFormat format);
|
||||||
|
|
||||||
|
QSize explicitSize() const;
|
||||||
|
void setExplicitSize(const QSize &pixelSize);
|
||||||
|
void setExplicitSize(int w, int h) { setExplicitSize(QSize(w, h)); }
|
||||||
|
|
||||||
|
bool isAutoRenderTargetEnabled() const;
|
||||||
|
void setAutoRenderTarget(bool enabled);
|
||||||
|
|
||||||
|
bool isMirrorVerticallyEnabled() const;
|
||||||
|
void setMirrorVertically(bool enabled);
|
||||||
|
|
||||||
|
QImage grab();
|
||||||
|
|
||||||
|
virtual void initialize(QRhiCommandBuffer *cb);
|
||||||
|
virtual void render(QRhiCommandBuffer *cb);
|
||||||
|
virtual void releaseResources();
|
||||||
|
|
||||||
|
QRhi *rhi() const;
|
||||||
|
QRhiTexture *colorTexture() const;
|
||||||
|
QRhiRenderBuffer *msaaColorBuffer() const;
|
||||||
|
QRhiTexture *resolveTexture() const;
|
||||||
|
QRhiRenderBuffer *depthStencilBuffer() const;
|
||||||
|
QRhiTextureRenderTarget *renderTarget() const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void frameSubmitted();
|
||||||
|
void renderFailed();
|
||||||
|
void sampleCountChanged(int samples);
|
||||||
|
void textureFormatChanged(TextureFormat format);
|
||||||
|
void autoRenderTargetChanged(bool enabled);
|
||||||
|
void explicitSizeChanged(const QSize &pixelSize);
|
||||||
|
void mirrorVerticallyChanged(bool enabled);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
bool event(QEvent *e) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif
|
63
src/widgets/kernel/qrhiwidget_p.h
Normal file
63
src/widgets/kernel/qrhiwidget_p.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
#ifndef QRHIWIDGET_P_H
|
||||||
|
#define QRHIWIDGET_P_H
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G
|
||||||
|
// -------------
|
||||||
|
//
|
||||||
|
// This file is not part of the Qt API. It exists purely as an
|
||||||
|
// implementation detail. This header file may change from version to
|
||||||
|
// version without notice, or even be removed.
|
||||||
|
//
|
||||||
|
// We mean it.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "qrhiwidget.h"
|
||||||
|
#include <rhi/qrhi.h>
|
||||||
|
#include <private/qwidget_p.h>
|
||||||
|
#include <private/qbackingstorerhisupport_p.h>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class QRhiWidgetPrivate : public QWidgetPrivate
|
||||||
|
{
|
||||||
|
Q_DECLARE_PUBLIC(QRhiWidget)
|
||||||
|
public:
|
||||||
|
TextureData texture() const override;
|
||||||
|
QPlatformTextureList::Flags textureListFlags() override;
|
||||||
|
QPlatformBackingStoreRhiConfig rhiConfig() const override;
|
||||||
|
void endCompose() override;
|
||||||
|
|
||||||
|
void ensureRhi();
|
||||||
|
void ensureTexture(bool *changed);
|
||||||
|
bool invokeInitialize(QRhiCommandBuffer *cb);
|
||||||
|
void resetColorBufferObjects();
|
||||||
|
void resetRenderTargetObjects();
|
||||||
|
void releaseResources();
|
||||||
|
|
||||||
|
QRhi *rhi = nullptr;
|
||||||
|
bool noSize = false;
|
||||||
|
QPlatformBackingStoreRhiConfig config;
|
||||||
|
QRhiWidget::TextureFormat widgetTextureFormat = QRhiWidget::TextureFormat::RGBA8;
|
||||||
|
QRhiTexture::Format rhiTextureFormat = QRhiTexture::RGBA8;
|
||||||
|
int samples = 1;
|
||||||
|
QSize explicitSize;
|
||||||
|
bool autoRenderTarget = true;
|
||||||
|
bool mirrorVertically = false;
|
||||||
|
QBackingStoreRhiSupport offscreenRenderer;
|
||||||
|
bool textureInvalid = false;
|
||||||
|
QRhiTexture *colorTexture = nullptr;
|
||||||
|
QRhiRenderBuffer *msaaColorBuffer = nullptr;
|
||||||
|
QRhiTexture *resolveTexture = nullptr;
|
||||||
|
QRhiRenderBuffer *depthStencilBuffer = nullptr;
|
||||||
|
QRhiTextureRenderTarget *renderTarget = nullptr;
|
||||||
|
QRhiRenderPassDescriptor *renderPassDescriptor = nullptr;
|
||||||
|
mutable QVector<QRhiResource *> pendingDeletes;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif
|
@ -10858,9 +10858,10 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
|
|||||||
// do it on newtlw instead, the performance implications of that are
|
// do it on newtlw instead, the performance implications of that are
|
||||||
// problematic when it comes to large widget trees.
|
// problematic when it comes to large widget trees.
|
||||||
if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) {
|
if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) {
|
||||||
|
const bool wasUsingRhiFlush = newtlw->d_func()->usesRhiFlush;
|
||||||
newtlw->d_func()->usesRhiFlush = true;
|
newtlw->d_func()->usesRhiFlush = true;
|
||||||
if (QWindow *w = newtlw->windowHandle()) {
|
if (QWindow *w = newtlw->windowHandle()) {
|
||||||
if (w->surfaceType() != surfaceType) {
|
if (w->surfaceType() != surfaceType || !wasUsingRhiFlush) {
|
||||||
newtlw->destroy();
|
newtlw->destroy();
|
||||||
newtlw->create();
|
newtlw->create();
|
||||||
}
|
}
|
||||||
|
@ -58,3 +58,4 @@ endif()
|
|||||||
if(QT_FEATURE_opengl)
|
if(QT_FEATURE_opengl)
|
||||||
add_subdirectory(qopenglwidget)
|
add_subdirectory(qopenglwidget)
|
||||||
endif()
|
endif()
|
||||||
|
add_subdirectory(qrhiwidget)
|
||||||
|
25
tests/auto/widgets/widgets/qrhiwidget/CMakeLists.txt
Normal file
25
tests/auto/widgets/widgets/qrhiwidget/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(tst_qrhiwidget LANGUAGES CXX)
|
||||||
|
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB_RECURSE qrhiwidget_resource_files
|
||||||
|
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
|
data/*
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_internal_add_test(tst_qrhiwidget
|
||||||
|
SOURCES
|
||||||
|
tst_qrhiwidget.cpp
|
||||||
|
LIBRARIES
|
||||||
|
Qt::CorePrivate
|
||||||
|
Qt::Gui
|
||||||
|
Qt::GuiPrivate
|
||||||
|
Qt::Widgets
|
||||||
|
TESTDATA ${qrhiwidget_resource_files}
|
||||||
|
BUILTIN_TESTDATA
|
||||||
|
)
|
8
tests/auto/widgets/widgets/qrhiwidget/data/simple.frag
Normal file
8
tests/auto/widgets/widgets/qrhiwidget/data/simple.frag
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
BIN
tests/auto/widgets/widgets/qrhiwidget/data/simple.frag.qsb
Normal file
BIN
tests/auto/widgets/widgets/qrhiwidget/data/simple.frag.qsb
Normal file
Binary file not shown.
8
tests/auto/widgets/widgets/qrhiwidget/data/simple.vert
Normal file
8
tests/auto/widgets/widgets/qrhiwidget/data/simple.vert
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 position;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = position;
|
||||||
|
}
|
BIN
tests/auto/widgets/widgets/qrhiwidget/data/simple.vert.qsb
Normal file
BIN
tests/auto/widgets/widgets/qrhiwidget/data/simple.vert.qsb
Normal file
Binary file not shown.
792
tests/auto/widgets/widgets/qrhiwidget/tst_qrhiwidget.cpp
Normal file
792
tests/auto/widgets/widgets/qrhiwidget/tst_qrhiwidget.cpp
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include <QtWidgets/QRhiWidget>
|
||||||
|
#include <QtGui/QPainter>
|
||||||
|
#include <QTest>
|
||||||
|
#include <QSignalSpy>
|
||||||
|
#include <private/qguiapplication_p.h>
|
||||||
|
#include <qpa/qplatformintegration.h>
|
||||||
|
#include <rhi/qrhi.h>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#if QT_CONFIG(vulkan)
|
||||||
|
#include <private/qvulkandefaultinstance_p.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class tst_QRhiWidget : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase();
|
||||||
|
void create_data();
|
||||||
|
void create();
|
||||||
|
void noCreate();
|
||||||
|
void simple_data();
|
||||||
|
void simple();
|
||||||
|
void msaa_data();
|
||||||
|
void msaa();
|
||||||
|
void explicitSize_data();
|
||||||
|
void explicitSize();
|
||||||
|
void autoRt_data();
|
||||||
|
void autoRt();
|
||||||
|
void reparent_data();
|
||||||
|
void reparent();
|
||||||
|
void grab_data();
|
||||||
|
void grab();
|
||||||
|
void mirror_data();
|
||||||
|
void mirror();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void testData();
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_QRhiWidget::initTestCase()
|
||||||
|
{
|
||||||
|
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering))
|
||||||
|
QSKIP("RhiBasedRendering capability is reported as unsupported on this platform.");
|
||||||
|
|
||||||
|
qputenv("QT_RHI_LEAK_CHECK", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::testData()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QRhiWidget::Api>("api");
|
||||||
|
|
||||||
|
#ifndef Q_OS_WEBOS
|
||||||
|
QTest::newRow("Null") << QRhiWidget::Api::Null;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if QT_CONFIG(opengl)
|
||||||
|
if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
|
||||||
|
QTest::newRow("OpenGL") << QRhiWidget::Api::OpenGL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if QT_CONFIG(vulkan)
|
||||||
|
// Have to probe to be sure Vulkan is actually working (the test cases
|
||||||
|
// themselves will assume QRhi init succeeds).
|
||||||
|
if (QVulkanDefaultInstance::instance()) {
|
||||||
|
QRhiVulkanInitParams vulkanInitParams;
|
||||||
|
vulkanInitParams.inst = QVulkanDefaultInstance::instance();
|
||||||
|
if (QRhi::probe(QRhi::Vulkan, &vulkanInitParams))
|
||||||
|
QTest::newRow("Vulkan") << QRhiWidget::Api::Vulkan;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
|
||||||
|
QRhiMetalInitParams metalInitParams;
|
||||||
|
if (QRhi::probe(QRhi::Metal, &metalInitParams))
|
||||||
|
QTest::newRow("Metal") << QRhiWidget::Api::Metal;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QTest::newRow("D3D11") << QRhiWidget::Api::D3D11;
|
||||||
|
// D3D12 needs to be probed too due to being disabled if the SDK headers
|
||||||
|
// are too old (clang, mingw).
|
||||||
|
QRhiD3D12InitParams d3d12InitParams;
|
||||||
|
if (QRhi::probe(QRhi::D3D12, &d3d12InitParams))
|
||||||
|
QTest::newRow("D3D12") << QRhiWidget::Api::D3D12;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::create_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::create()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
{
|
||||||
|
QRhiWidget w;
|
||||||
|
w.setApi(api);
|
||||||
|
w.resize(320, 240);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QWidget topLevel;
|
||||||
|
topLevel.resize(320, 240);
|
||||||
|
QRhiWidget *w = new QRhiWidget(&topLevel);
|
||||||
|
w->setApi(api);
|
||||||
|
w->resize(100, 100);
|
||||||
|
topLevel.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::noCreate()
|
||||||
|
{
|
||||||
|
// Now try something that is guaranteed to fail.
|
||||||
|
// E.g. try using Metal on Windows.
|
||||||
|
// The error signal should be emitted. The frame signal should not.
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
qDebug("Warnings will be printed below, this is as expected");
|
||||||
|
QRhiWidget rhiWidget;
|
||||||
|
rhiWidget.setApi(QRhiWidget::Api::Metal);
|
||||||
|
QSignalSpy frameSpy(&rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(&rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
rhiWidget.resize(320, 240);
|
||||||
|
rhiWidget.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&rhiWidget));
|
||||||
|
QTRY_VERIFY(errorSpy.count() > 0);
|
||||||
|
QCOMPARE(frameSpy.count(), 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static QShader getShader(const QString &name)
|
||||||
|
{
|
||||||
|
QFile f(name);
|
||||||
|
return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch)
|
||||||
|
{
|
||||||
|
QRhiCommandBuffer *cb = nullptr;
|
||||||
|
QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
|
||||||
|
if (result != QRhi::FrameOpSuccess) {
|
||||||
|
qWarning("beginOffscreenFrame returned %d", result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!cb) {
|
||||||
|
qWarning("No command buffer from beginOffscreenFrame");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cb->resourceUpdate(batch);
|
||||||
|
rhi->endOffscreenFrame();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool imageRGBAEquals(const QImage &a, const QImage &b, int maxFuzz = 1)
|
||||||
|
{
|
||||||
|
if (a.size() != b.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QImage image0 = a.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
|
||||||
|
const QImage image1 = b.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
|
||||||
|
|
||||||
|
const int width = image0.width();
|
||||||
|
const int height = image0.height();
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
const quint32 *p0 = reinterpret_cast<const quint32 *>(image0.constScanLine(y));
|
||||||
|
const quint32 *p1 = reinterpret_cast<const quint32 *>(image1.constScanLine(y));
|
||||||
|
int x = width - 1;
|
||||||
|
while (x-- >= 0) {
|
||||||
|
const QRgb c0(*p0++);
|
||||||
|
const QRgb c1(*p1++);
|
||||||
|
const int red = qAbs(qRed(c0) - qRed(c1));
|
||||||
|
const int green = qAbs(qGreen(c0) - qGreen(c1));
|
||||||
|
const int blue = qAbs(qBlue(c0) - qBlue(c1));
|
||||||
|
const int alpha = qAbs(qAlpha(c0) - qAlpha(c1));
|
||||||
|
if (red > maxFuzz || green > maxFuzz || blue > maxFuzz || alpha > maxFuzz)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleRhiWidget : public QRhiWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SimpleRhiWidget(int sampleCount = 1, QWidget *parent = nullptr)
|
||||||
|
: QRhiWidget(parent),
|
||||||
|
m_sampleCount(sampleCount)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
~SimpleRhiWidget()
|
||||||
|
{
|
||||||
|
delete m_rt;
|
||||||
|
delete m_rp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize(QRhiCommandBuffer *cb) override;
|
||||||
|
void render(QRhiCommandBuffer *cb) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
|
||||||
|
int m_sampleCount;
|
||||||
|
QRhi *m_rhi = nullptr;
|
||||||
|
std::unique_ptr<QRhiBuffer> m_vbuf;
|
||||||
|
std::unique_ptr<QRhiBuffer> m_ubuf;
|
||||||
|
std::unique_ptr<QRhiShaderResourceBindings> m_srb;
|
||||||
|
std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
|
||||||
|
QRhiTextureRenderTarget *m_rt = nullptr; // used when autoRenderTarget is off
|
||||||
|
QRhiRenderPassDescriptor *m_rp = nullptr; // used when autoRenderTarget is off
|
||||||
|
};
|
||||||
|
|
||||||
|
void SimpleRhiWidget::initialize(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
if (m_rhi != rhi()) {
|
||||||
|
m_pipeline.reset();
|
||||||
|
m_rhi = rhi();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_pipeline) {
|
||||||
|
if (!isAutoRenderTargetEnabled()) {
|
||||||
|
delete m_rt;
|
||||||
|
delete m_rp;
|
||||||
|
QRhiTextureRenderTargetDescription rtDesc;
|
||||||
|
if (colorTexture()) {
|
||||||
|
rtDesc.setColorAttachments({ colorTexture() });
|
||||||
|
} else if (msaaColorBuffer()) {
|
||||||
|
QRhiColorAttachment att;
|
||||||
|
att.setRenderBuffer(msaaColorBuffer());
|
||||||
|
rtDesc.setColorAttachments({ att });
|
||||||
|
}
|
||||||
|
m_rt = m_rhi->newTextureRenderTarget(rtDesc);
|
||||||
|
m_rp = m_rt->newCompatibleRenderPassDescriptor();
|
||||||
|
m_rt->setRenderPassDescriptor(m_rp);
|
||||||
|
m_rt->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
static float vertexData[] = {
|
||||||
|
0, 1,
|
||||||
|
-1, -1,
|
||||||
|
1, -1
|
||||||
|
};
|
||||||
|
|
||||||
|
m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
|
||||||
|
m_vbuf->create();
|
||||||
|
|
||||||
|
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
|
||||||
|
m_ubuf->create();
|
||||||
|
|
||||||
|
m_srb.reset(m_rhi->newShaderResourceBindings());
|
||||||
|
m_srb->create();
|
||||||
|
|
||||||
|
m_pipeline.reset(m_rhi->newGraphicsPipeline());
|
||||||
|
m_pipeline->setShaderStages({
|
||||||
|
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/data/simple.vert.qsb")) },
|
||||||
|
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/data/simple.frag.qsb")) }
|
||||||
|
});
|
||||||
|
QRhiVertexInputLayout inputLayout;
|
||||||
|
inputLayout.setBindings({
|
||||||
|
{ 2 * sizeof(float) }
|
||||||
|
});
|
||||||
|
inputLayout.setAttributes({
|
||||||
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 }
|
||||||
|
});
|
||||||
|
m_pipeline->setSampleCount(m_sampleCount);
|
||||||
|
m_pipeline->setVertexInputLayout(inputLayout);
|
||||||
|
m_pipeline->setShaderResourceBindings(m_srb.get());
|
||||||
|
m_pipeline->setRenderPassDescriptor(renderTarget() ? renderTarget()->renderPassDescriptor() : m_rp);
|
||||||
|
m_pipeline->create();
|
||||||
|
|
||||||
|
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||||
|
resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
|
||||||
|
cb->resourceUpdate(resourceUpdates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleRhiWidget::render(QRhiCommandBuffer *cb)
|
||||||
|
{
|
||||||
|
const QSize outputSize = colorTexture() ? colorTexture()->pixelSize() : msaaColorBuffer()->pixelSize();
|
||||||
|
if (renderTarget()) {
|
||||||
|
QCOMPARE(outputSize, renderTarget()->pixelSize());
|
||||||
|
if (rhi()->backend() != QRhi::Null && rhi()->supportedSampleCounts().contains(m_sampleCount))
|
||||||
|
QCOMPARE(m_sampleCount, renderTarget()->sampleCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
|
||||||
|
cb->beginPass(renderTarget() ? renderTarget() : m_rt, clearColor, { 1.0f, 0 });
|
||||||
|
cb->setGraphicsPipeline(m_pipeline.get());
|
||||||
|
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
|
||||||
|
cb->setShaderResources();
|
||||||
|
const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
|
||||||
|
cb->setVertexInput(0, 1, &vbufBinding);
|
||||||
|
cb->draw(3);
|
||||||
|
cb->endPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleRhiWidget::releaseResources()
|
||||||
|
{
|
||||||
|
m_pipeline.reset();
|
||||||
|
m_srb.reset();
|
||||||
|
m_ubuf.reset();
|
||||||
|
m_vbuf.reset();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::simple_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::simple()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
SimpleRhiWidget *rhiWidget = new SimpleRhiWidget;
|
||||||
|
rhiWidget->setApi(api);
|
||||||
|
QSignalSpy frameSpy(rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
QCOMPARE(rhiWidget->sampleCount(), 1);
|
||||||
|
QCOMPARE(rhiWidget->textureFormat(), QRhiWidget::TextureFormat::RGBA8);
|
||||||
|
QVERIFY(rhiWidget->isAutoRenderTargetEnabled());
|
||||||
|
|
||||||
|
// Pull out the QRhiTexture (we know colorTexture() and rhi() and friends
|
||||||
|
// are all there even outside initialize() and render(), even though this
|
||||||
|
// is not quite documented), and read it back.
|
||||||
|
QRhiTexture *backingTexture = rhiWidget->colorTexture();
|
||||||
|
QVERIFY(backingTexture);
|
||||||
|
QCOMPARE(backingTexture->format(), QRhiTexture::RGBA8);
|
||||||
|
QVERIFY(rhiWidget->depthStencilBuffer());
|
||||||
|
QVERIFY(rhiWidget->renderTarget());
|
||||||
|
QVERIFY(!rhiWidget->resolveTexture());
|
||||||
|
QRhi *rhi = rhiWidget->rhi();
|
||||||
|
QVERIFY(rhi);
|
||||||
|
|
||||||
|
switch (api) {
|
||||||
|
case QRhiWidget::Api::OpenGL:
|
||||||
|
QCOMPARE(rhi->backend(), QRhi::OpenGLES2);
|
||||||
|
break;
|
||||||
|
case QRhiWidget::Api::Metal:
|
||||||
|
QCOMPARE(rhi->backend(), QRhi::Metal);
|
||||||
|
break;
|
||||||
|
case QRhiWidget::Api::Vulkan:
|
||||||
|
QCOMPARE(rhi->backend(), QRhi::Vulkan);
|
||||||
|
break;
|
||||||
|
case QRhiWidget::Api::D3D11:
|
||||||
|
QCOMPARE(rhi->backend(), QRhi::D3D11);
|
||||||
|
break;
|
||||||
|
case QRhiWidget::Api::D3D12:
|
||||||
|
QCOMPARE(rhi->backend(), QRhi::D3D12);
|
||||||
|
break;
|
||||||
|
case QRhiWidget::Api::Null:
|
||||||
|
QCOMPARE(rhi->backend(), QRhi::Null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int maxFuzz = 1;
|
||||||
|
QImage resultOne;
|
||||||
|
if (rhi->backend() != QRhi::Null) {
|
||||||
|
QRhiReadbackResult readResult;
|
||||||
|
bool readCompleted = false;
|
||||||
|
readResult.completed = [&readCompleted] { readCompleted = true; };
|
||||||
|
QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
|
||||||
|
rub->readBackTexture(backingTexture, &readResult);
|
||||||
|
QVERIFY(submitResourceUpdates(rhi, rub));
|
||||||
|
QVERIFY(readCompleted);
|
||||||
|
|
||||||
|
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
|
||||||
|
readResult.pixelSize.width(), readResult.pixelSize.height(),
|
||||||
|
QImage::Format_RGBA8888);
|
||||||
|
if (rhi->isYUpInFramebuffer())
|
||||||
|
resultOne = wrapperImage.mirrored();
|
||||||
|
else
|
||||||
|
resultOne = wrapperImage.copy();
|
||||||
|
|
||||||
|
// result is now a red triangle upon greenish background, where the
|
||||||
|
// triangle's edges are (0, 1), (-1, -1), and (1, -1).
|
||||||
|
// It's upside down with Vulkan (Y is not corrected, clipSpaceCorrMatrix() is not used),
|
||||||
|
// but that won't matter for the test.
|
||||||
|
|
||||||
|
// Check that the center is a red pixel.
|
||||||
|
QRgb c = resultOne.pixel(resultOne.width() / 2, resultOne.height() / 2);
|
||||||
|
QVERIFY(qRed(c) >= 255 - maxFuzz);
|
||||||
|
QVERIFY(qGreen(c) <= maxFuzz);
|
||||||
|
QVERIFY(qBlue(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now through grab().
|
||||||
|
QImage resultTwo;
|
||||||
|
if (rhi->backend() != QRhi::Null) {
|
||||||
|
resultTwo = rhiWidget->grab();
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
QVERIFY(!resultTwo.isNull());
|
||||||
|
QRgb c = resultTwo.pixel(resultTwo.width() / 2, resultTwo.height() / 2);
|
||||||
|
QVERIFY(qRed(c) >= 255 - maxFuzz);
|
||||||
|
QVERIFY(qGreen(c) <= maxFuzz);
|
||||||
|
QVERIFY(qBlue(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we got the same result from our manual readback and when the
|
||||||
|
// texture was rendered to again and grab() was called.
|
||||||
|
QVERIFY(imageRGBAEquals(resultOne, resultTwo, maxFuzz));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::msaa_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::msaa()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
const int SAMPLE_COUNT = 4;
|
||||||
|
SimpleRhiWidget *rhiWidget = new SimpleRhiWidget(SAMPLE_COUNT);
|
||||||
|
rhiWidget->setApi(api);
|
||||||
|
rhiWidget->setSampleCount(SAMPLE_COUNT);
|
||||||
|
QSignalSpy frameSpy(rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
QCOMPARE(rhiWidget->sampleCount(), 4);
|
||||||
|
QCOMPARE(rhiWidget->textureFormat(), QRhiWidget::TextureFormat::RGBA8);
|
||||||
|
QVERIFY(!rhiWidget->colorTexture());
|
||||||
|
QVERIFY(rhiWidget->msaaColorBuffer());
|
||||||
|
QVERIFY(rhiWidget->depthStencilBuffer());
|
||||||
|
QVERIFY(rhiWidget->renderTarget());
|
||||||
|
QVERIFY(rhiWidget->resolveTexture());
|
||||||
|
QCOMPARE(rhiWidget->resolveTexture()->format(), QRhiTexture::RGBA8);
|
||||||
|
QRhi *rhi = rhiWidget->rhi();
|
||||||
|
QVERIFY(rhi);
|
||||||
|
|
||||||
|
if (rhi->backend() != QRhi::Null) {
|
||||||
|
QRhiReadbackResult readResult;
|
||||||
|
QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
|
||||||
|
rub->readBackTexture(rhiWidget->resolveTexture(), &readResult);
|
||||||
|
QVERIFY(submitResourceUpdates(rhi, rub));
|
||||||
|
|
||||||
|
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
|
||||||
|
readResult.pixelSize.width(), readResult.pixelSize.height(),
|
||||||
|
QImage::Format_RGBA8888);
|
||||||
|
QImage result;
|
||||||
|
if (rhi->isYUpInFramebuffer())
|
||||||
|
result = wrapperImage.mirrored();
|
||||||
|
else
|
||||||
|
result = wrapperImage.copy();
|
||||||
|
|
||||||
|
// Check that the center is a red pixel.
|
||||||
|
const int maxFuzz = 1;
|
||||||
|
QRgb c = result.pixel(result.width() / 2, result.height() / 2);
|
||||||
|
QVERIFY(qRed(c) >= 255 - maxFuzz);
|
||||||
|
QVERIFY(qGreen(c) <= maxFuzz);
|
||||||
|
QVERIFY(qBlue(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if switching back and forth works.
|
||||||
|
frameSpy.clear();
|
||||||
|
rhiWidget->m_pipeline.reset();
|
||||||
|
rhiWidget->m_sampleCount = 1;
|
||||||
|
rhiWidget->setSampleCount(1);
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
QVERIFY(rhiWidget->colorTexture());
|
||||||
|
QVERIFY(!rhiWidget->msaaColorBuffer());
|
||||||
|
|
||||||
|
frameSpy.clear();
|
||||||
|
rhiWidget->m_pipeline.reset();
|
||||||
|
rhiWidget->m_sampleCount = SAMPLE_COUNT;
|
||||||
|
rhiWidget->setSampleCount(SAMPLE_COUNT);
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
QVERIFY(!rhiWidget->colorTexture());
|
||||||
|
QVERIFY(rhiWidget->msaaColorBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::explicitSize_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::explicitSize()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
SimpleRhiWidget *rhiWidget = new SimpleRhiWidget;
|
||||||
|
rhiWidget->setApi(api);
|
||||||
|
QSignalSpy frameSpy(rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
|
||||||
|
rhiWidget->setExplicitSize(QSize(320, 200));
|
||||||
|
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
QVERIFY(rhiWidget->rhi());
|
||||||
|
QVERIFY(rhiWidget->colorTexture());
|
||||||
|
QCOMPARE(rhiWidget->colorTexture()->pixelSize(), QSize(320, 200));
|
||||||
|
QVERIFY(rhiWidget->depthStencilBuffer());
|
||||||
|
QCOMPARE(rhiWidget->depthStencilBuffer()->pixelSize(), QSize(320, 200));
|
||||||
|
QVERIFY(rhiWidget->renderTarget());
|
||||||
|
QVERIFY(!rhiWidget->resolveTexture());
|
||||||
|
|
||||||
|
frameSpy.clear();
|
||||||
|
rhiWidget->setExplicitSize(640, 480); // should also trigger update()
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
|
||||||
|
QVERIFY(rhiWidget->colorTexture());
|
||||||
|
QCOMPARE(rhiWidget->colorTexture()->pixelSize(), QSize(640, 480));
|
||||||
|
QVERIFY(rhiWidget->depthStencilBuffer());
|
||||||
|
QCOMPARE(rhiWidget->depthStencilBuffer()->pixelSize(), QSize(640, 480));
|
||||||
|
|
||||||
|
frameSpy.clear();
|
||||||
|
rhiWidget->setExplicitSize(QSize());
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
|
||||||
|
QVERIFY(rhiWidget->colorTexture());
|
||||||
|
QVERIFY(rhiWidget->colorTexture()->pixelSize() != QSize(640, 480));
|
||||||
|
QVERIFY(rhiWidget->depthStencilBuffer());
|
||||||
|
QVERIFY(rhiWidget->depthStencilBuffer()->pixelSize() != QSize(640, 480));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::autoRt_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::autoRt()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
SimpleRhiWidget *rhiWidget = new SimpleRhiWidget;
|
||||||
|
rhiWidget->setApi(api);
|
||||||
|
QVERIFY(rhiWidget->isAutoRenderTargetEnabled());
|
||||||
|
rhiWidget->setAutoRenderTarget(false);
|
||||||
|
QVERIFY(!rhiWidget->isAutoRenderTargetEnabled());
|
||||||
|
QSignalSpy frameSpy(rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
QVERIFY(rhiWidget->rhi());
|
||||||
|
QVERIFY(rhiWidget->colorTexture());
|
||||||
|
QVERIFY(!rhiWidget->depthStencilBuffer());
|
||||||
|
QVERIFY(!rhiWidget->renderTarget());
|
||||||
|
QVERIFY(!rhiWidget->resolveTexture());
|
||||||
|
|
||||||
|
QVERIFY(rhiWidget->m_rt);
|
||||||
|
QVERIFY(rhiWidget->m_rp);
|
||||||
|
QCOMPARE(rhiWidget->m_rt->description().cbeginColorAttachments()->texture(), rhiWidget->colorTexture());
|
||||||
|
|
||||||
|
frameSpy.clear();
|
||||||
|
// do something that triggers creating a new backing texture
|
||||||
|
rhiWidget->setExplicitSize(QSize(320, 200));
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
|
||||||
|
QVERIFY(rhiWidget->colorTexture());
|
||||||
|
QCOMPARE(rhiWidget->m_rt->description().cbeginColorAttachments()->texture(), rhiWidget->colorTexture());
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::reparent_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::reparent()
|
||||||
|
{
|
||||||
|
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows))
|
||||||
|
QSKIP("MultipleWindows capability is reported as unsupported, skipping reparenting test.");
|
||||||
|
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
QWidget *windowOne = new QWidget;
|
||||||
|
windowOne->resize(1280, 720);
|
||||||
|
|
||||||
|
SimpleRhiWidget *rhiWidget = new SimpleRhiWidget(1, windowOne);
|
||||||
|
rhiWidget->setApi(api);
|
||||||
|
rhiWidget->resize(800, 600);
|
||||||
|
QSignalSpy frameSpy(rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
windowOne->show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(windowOne));
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
frameSpy.clear();
|
||||||
|
QWidget windowTwo;
|
||||||
|
windowTwo.resize(1280, 720);
|
||||||
|
|
||||||
|
rhiWidget->setParent(&windowTwo);
|
||||||
|
|
||||||
|
// There's nothing saying the old top-level parent is going to be around,
|
||||||
|
// which is interesting wrt to its QRhi and resources created with that;
|
||||||
|
// exercise this.
|
||||||
|
delete windowOne;
|
||||||
|
|
||||||
|
windowTwo.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&windowTwo));
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
// now reparent after show() has already been called
|
||||||
|
frameSpy.clear();
|
||||||
|
QWidget windowThree;
|
||||||
|
windowThree.resize(1280, 720);
|
||||||
|
windowThree.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&windowThree));
|
||||||
|
|
||||||
|
rhiWidget->setParent(&windowThree);
|
||||||
|
// this case needs a show() on rhiWidget
|
||||||
|
rhiWidget->show();
|
||||||
|
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::grab_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::grab()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
const int maxFuzz = 1;
|
||||||
|
|
||||||
|
SimpleRhiWidget w;
|
||||||
|
w.setApi(api);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
QSignalSpy errorSpy(&w, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
QImage image = w.grab(); // creates its own QRhi just to render offscreen
|
||||||
|
QVERIFY(!image.isNull());
|
||||||
|
QVERIFY(w.rhi());
|
||||||
|
QVERIFY(w.colorTexture());
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
if (api != QRhiWidget::Api::Null) {
|
||||||
|
QRgb c = image.pixel(image.width() / 2, image.height() / 2);
|
||||||
|
QVERIFY(qRed(c) >= 255 - maxFuzz);
|
||||||
|
QVERIFY(qGreen(c) <= maxFuzz);
|
||||||
|
QVERIFY(qBlue(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the window visible, this under the hood drops the QRhiWidget's
|
||||||
|
// own QRhi and attaches to the backingstore's.
|
||||||
|
QSignalSpy frameSpy(&w, &QRhiWidget::frameSubmitted);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
if (api != QRhiWidget::Api::Null) {
|
||||||
|
QRhiReadbackResult readResult;
|
||||||
|
QRhiResourceUpdateBatch *rub = w.rhi()->nextResourceUpdateBatch();
|
||||||
|
rub->readBackTexture(w.colorTexture(), &readResult);
|
||||||
|
QVERIFY(submitResourceUpdates(w.rhi(), rub));
|
||||||
|
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
|
||||||
|
readResult.pixelSize.width(), readResult.pixelSize.height(),
|
||||||
|
QImage::Format_RGBA8888);
|
||||||
|
if (w.rhi()->isYUpInFramebuffer())
|
||||||
|
image = wrapperImage.mirrored();
|
||||||
|
else
|
||||||
|
image = wrapperImage.copy();
|
||||||
|
QRgb c = image.pixel(image.width() / 2, image.height() / 2);
|
||||||
|
QVERIFY(qRed(c) >= 255 - maxFuzz);
|
||||||
|
QVERIFY(qGreen(c) <= maxFuzz);
|
||||||
|
QVERIFY(qBlue(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::mirror_data()
|
||||||
|
{
|
||||||
|
testData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QRhiWidget::mirror()
|
||||||
|
{
|
||||||
|
QFETCH(QRhiWidget::Api, api);
|
||||||
|
|
||||||
|
SimpleRhiWidget *rhiWidget = new SimpleRhiWidget;
|
||||||
|
rhiWidget->setApi(api);
|
||||||
|
QVERIFY(!rhiWidget->isMirrorVerticallyEnabled());
|
||||||
|
|
||||||
|
QSignalSpy frameSpy(rhiWidget, &QRhiWidget::frameSubmitted);
|
||||||
|
QSignalSpy errorSpy(rhiWidget, &QRhiWidget::renderFailed);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(rhiWidget);
|
||||||
|
QWidget w;
|
||||||
|
w.setLayout(layout);
|
||||||
|
w.resize(1280, 720);
|
||||||
|
w.show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(&w));
|
||||||
|
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
frameSpy.clear();
|
||||||
|
rhiWidget->setMirrorVertically(true);
|
||||||
|
QVERIFY(rhiWidget->isMirrorVerticallyEnabled());
|
||||||
|
QTRY_VERIFY(frameSpy.count() > 0);
|
||||||
|
QCOMPARE(errorSpy.count(), 0);
|
||||||
|
|
||||||
|
if (api != QRhiWidget::Api::Null) {
|
||||||
|
QRhi *rhi = rhiWidget->rhi();
|
||||||
|
QRhiReadbackResult readResult;
|
||||||
|
QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
|
||||||
|
rub->readBackTexture(rhiWidget->colorTexture(), &readResult);
|
||||||
|
QVERIFY(submitResourceUpdates(rhi, rub));
|
||||||
|
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
|
||||||
|
readResult.pixelSize.width(), readResult.pixelSize.height(),
|
||||||
|
QImage::Format_RGBA8888);
|
||||||
|
QImage image;
|
||||||
|
if (rhi->isYUpInFramebuffer())
|
||||||
|
image = wrapperImage.mirrored();
|
||||||
|
else
|
||||||
|
image = wrapperImage.copy();
|
||||||
|
|
||||||
|
const int maxFuzz = 1;
|
||||||
|
QRgb c = image.pixel(50, 5);
|
||||||
|
if (api != QRhiWidget::Api::Vulkan) {
|
||||||
|
// this should be the background (greenish), not the red triangle
|
||||||
|
QVERIFY(qGreen(c) > qRed(c));
|
||||||
|
} else {
|
||||||
|
// remember that Vulkan is upside down due to not correcting for Y down in NDC
|
||||||
|
// hence this is red
|
||||||
|
QVERIFY(qRed(c) >= 255 - maxFuzz);
|
||||||
|
QVERIFY(qGreen(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
QVERIFY(qBlue(c) <= maxFuzz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(tst_QRhiWidget)
|
||||||
|
|
||||||
|
#include "tst_qrhiwidget.moc"
|
@ -34,5 +34,5 @@ add_subdirectory(displacement)
|
|||||||
add_subdirectory(imguirenderer)
|
add_subdirectory(imguirenderer)
|
||||||
add_subdirectory(multiview)
|
add_subdirectory(multiview)
|
||||||
if(QT_FEATURE_widgets)
|
if(QT_FEATURE_widgets)
|
||||||
add_subdirectory(rhiwidget)
|
add_subdirectory(rhiwidgetproto)
|
||||||
endif()
|
endif()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Copyright (C) 2022 The Qt Company Ltd.
|
# Copyright (C) 2022 The Qt Company Ltd.
|
||||||
# SPDX-License-Identifier: BSD-3-Clause
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
qt_internal_add_manual_test(rhiwidget
|
qt_internal_add_manual_test(rhiwidgetproto
|
||||||
GUI
|
GUI
|
||||||
SOURCES
|
SOURCES
|
||||||
examplewidget.cpp examplewidget.h
|
examplewidget.cpp examplewidget.h
|
||||||
@ -20,14 +20,14 @@ set_source_files_properties("../shared/texture.vert.qsb"
|
|||||||
set_source_files_properties("../shared/texture.frag.qsb"
|
set_source_files_properties("../shared/texture.frag.qsb"
|
||||||
PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
|
PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
|
||||||
)
|
)
|
||||||
set(rhiwidget_resource_files
|
set(rhiwidgetproto_resource_files
|
||||||
"../shared/texture.vert.qsb"
|
"../shared/texture.vert.qsb"
|
||||||
"../shared/texture.frag.qsb"
|
"../shared/texture.frag.qsb"
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_internal_add_resource(rhiwidget "rhiwidget"
|
qt_internal_add_resource(rhiwidgetproto "rhiwidgetproto"
|
||||||
PREFIX
|
PREFIX
|
||||||
"/"
|
"/"
|
||||||
FILES
|
FILES
|
||||||
${rhiwidget_resource_files}
|
${rhiwidgetproto_resource_files}
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user