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 \
|
||||
mainwindows \
|
||||
painting \
|
||||
rhi \
|
||||
richtext \
|
||||
tools \
|
||||
touch \
|
||||
|
@ -90,6 +90,18 @@
|
||||
portable, cross-platform application that performs accelerated 3D rendering
|
||||
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
|
||||
compatibility guarantee, as opposed to regular Qt public APIs. See \l QRhi
|
||||
for details.
|
||||
|
@ -551,10 +551,13 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
|
||||
}
|
||||
|
||||
for (int i = 0; i < textureWidgetCount; ++i) {
|
||||
const bool invertSourceForTextureWidget = textures->flags(i).testFlag(QPlatformTextureList::MirrorVertically)
|
||||
? !invertSource : invertSource;
|
||||
QMatrix4x4 target;
|
||||
QMatrix3x3 source;
|
||||
if (!prepareDrawForRenderToTextureWidget(textures, i, window, deviceWindowRect,
|
||||
offset, invertTargetY, invertSource, &target, &source))
|
||||
offset, invertTargetY, invertSourceForTextureWidget,
|
||||
&target, &source))
|
||||
{
|
||||
m_textureQuadData[i].reset();
|
||||
continue;
|
||||
|
@ -94,7 +94,8 @@ public:
|
||||
enum Flag {
|
||||
StacksOnTop = 0x01,
|
||||
TextureIsSrgb = 0x02,
|
||||
NeedsPremultipliedAlphaBlending = 0x04
|
||||
NeedsPremultipliedAlphaBlending = 0x04,
|
||||
MirrorVertically = 0x08
|
||||
};
|
||||
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/qlayoutengine.cpp kernel/qlayoutengine_p.h
|
||||
kernel/qlayoutitem.cpp kernel/qlayoutitem.h
|
||||
kernel/qrhiwidget.cpp kernel/qrhiwidget.h kernel/qrhiwidget_p.h
|
||||
kernel/qsizepolicy.cpp kernel/qsizepolicy.h
|
||||
kernel/qstackedlayout.cpp kernel/qstackedlayout.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
|
||||
// problematic when it comes to large widget trees.
|
||||
if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) {
|
||||
const bool wasUsingRhiFlush = newtlw->d_func()->usesRhiFlush;
|
||||
newtlw->d_func()->usesRhiFlush = true;
|
||||
if (QWindow *w = newtlw->windowHandle()) {
|
||||
if (w->surfaceType() != surfaceType) {
|
||||
if (w->surfaceType() != surfaceType || !wasUsingRhiFlush) {
|
||||
newtlw->destroy();
|
||||
newtlw->create();
|
||||
}
|
||||
|
@ -58,3 +58,4 @@ endif()
|
||||
if(QT_FEATURE_opengl)
|
||||
add_subdirectory(qopenglwidget)
|
||||
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(multiview)
|
||||
if(QT_FEATURE_widgets)
|
||||
add_subdirectory(rhiwidget)
|
||||
add_subdirectory(rhiwidgetproto)
|
||||
endif()
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_manual_test(rhiwidget
|
||||
qt_internal_add_manual_test(rhiwidgetproto
|
||||
GUI
|
||||
SOURCES
|
||||
examplewidget.cpp examplewidget.h
|
||||
@ -20,14 +20,14 @@ set_source_files_properties("../shared/texture.vert.qsb"
|
||||
set_source_files_properties("../shared/texture.frag.qsb"
|
||||
PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
|
||||
)
|
||||
set(rhiwidget_resource_files
|
||||
set(rhiwidgetproto_resource_files
|
||||
"../shared/texture.vert.qsb"
|
||||
"../shared/texture.frag.qsb"
|
||||
)
|
||||
|
||||
qt_internal_add_resource(rhiwidget "rhiwidget"
|
||||
qt_internal_add_resource(rhiwidgetproto "rhiwidgetproto"
|
||||
PREFIX
|
||||
"/"
|
||||
FILES
|
||||
${rhiwidget_resource_files}
|
||||
${rhiwidgetproto_resource_files}
|
||||
)
|
Loading…
Reference in New Issue
Block a user