diff options
Diffstat (limited to 'examples/vulkan/hellovulkancubes')
23 files changed, 2388 insertions, 0 deletions
diff --git a/examples/vulkan/hellovulkancubes/camera.cpp b/examples/vulkan/hellovulkancubes/camera.cpp new file mode 100644 index 0000000000..64dee03154 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/camera.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camera.h" + +Camera::Camera(const QVector3D &pos) + : m_forward(0.0f, 0.0f, -1.0f), + m_right(1.0f, 0.0f, 0.0f), + m_up(0.0f, 1.0f, 0.0f), + m_pos(pos), + m_yaw(0.0f), + m_pitch(0.0f) +{ +} + +static inline void clamp360(float *v) +{ + if (*v > 360.0f) + *v -= 360.0f; + if (*v < -360.0f) + *v += 360.0f; +} + +void Camera::yaw(float degrees) +{ + m_yaw += degrees; + clamp360(&m_yaw); + m_yawMatrix.setToIdentity(); + m_yawMatrix.rotate(m_yaw, 0, 1, 0); + + QMatrix4x4 rotMat = m_pitchMatrix * m_yawMatrix; + m_forward = (QVector4D(0.0f, 0.0f, -1.0f, 0.0f) * rotMat).toVector3D(); + m_right = (QVector4D(1.0f, 0.0f, 0.0f, 0.0f) * rotMat).toVector3D(); +} + +void Camera::pitch(float degrees) +{ + m_pitch += degrees; + clamp360(&m_pitch); + m_pitchMatrix.setToIdentity(); + m_pitchMatrix.rotate(m_pitch, 1, 0, 0); + + QMatrix4x4 rotMat = m_pitchMatrix * m_yawMatrix; + m_forward = (QVector4D(0.0f, 0.0f, -1.0f, 0.0f) * rotMat).toVector3D(); + m_up = (QVector4D(0.0f, 1.0f, 0.0f, 0.0f) * rotMat).toVector3D(); +} + +void Camera::walk(float amount) +{ + m_pos[0] += amount * m_forward.x(); + m_pos[2] += amount * m_forward.z(); +} + +void Camera::strafe(float amount) +{ + m_pos[0] += amount * m_right.x(); + m_pos[2] += amount * m_right.z(); +} + +QMatrix4x4 Camera::viewMatrix() const +{ + QMatrix4x4 m = m_pitchMatrix * m_yawMatrix; + m.translate(-m_pos); + return m; +} diff --git a/examples/vulkan/hellovulkancubes/camera.h b/examples/vulkan/hellovulkancubes/camera.h new file mode 100644 index 0000000000..c5c579f066 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/camera.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERA_H +#define CAMERA_H + +#include <QVector3D> +#include <QMatrix4x4> + +class Camera +{ +public: + Camera(const QVector3D &pos); + + void yaw(float degrees); + void pitch(float degrees); + void walk(float amount); + void strafe(float amount); + + QMatrix4x4 viewMatrix() const; + +private: + QVector3D m_forward; + QVector3D m_right; + QVector3D m_up; + QVector3D m_pos; + float m_yaw; + float m_pitch; + QMatrix4x4 m_yawMatrix; + QMatrix4x4 m_pitchMatrix; +}; + +#endif diff --git a/examples/vulkan/hellovulkancubes/color.frag b/examples/vulkan/hellovulkancubes/color.frag new file mode 100644 index 0000000000..3b04955963 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color.frag @@ -0,0 +1,12 @@ +#version 440 + +layout(push_constant) uniform PC { + layout(offset = 64) vec3 color; +} pc; + +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = vec4(pc.color, 1.0); +} diff --git a/examples/vulkan/hellovulkancubes/color.vert b/examples/vulkan/hellovulkancubes/color.vert new file mode 100644 index 0000000000..19bf815819 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color.vert @@ -0,0 +1,14 @@ +#version 440 + +layout(location = 0) in vec4 position; + +out gl_PerVertex { vec4 gl_Position; }; + +layout(push_constant) uniform PC { + mat4 mvp; +} pc; + +void main() +{ + gl_Position = pc.mvp * position; +} diff --git a/examples/vulkan/hellovulkancubes/color_frag.spv b/examples/vulkan/hellovulkancubes/color_frag.spv Binary files differnew file mode 100644 index 0000000000..bd72984ffe --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color_frag.spv diff --git a/examples/vulkan/hellovulkancubes/color_phong.frag b/examples/vulkan/hellovulkancubes/color_phong.frag new file mode 100644 index 0000000000..8b0c715f3b --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color_phong.frag @@ -0,0 +1,39 @@ +#version 440 + +layout(location = 0) in vec3 vECVertNormal; +layout(location = 1) in vec3 vECVertPos; +layout(location = 2) flat in vec3 vDiffuseAdjust; + +layout(std140, binding = 1) uniform buf { + vec3 ECCameraPosition; + vec3 ka; + vec3 kd; + vec3 ks; + // Have one light only for now. + vec3 ECLightPosition; + vec3 attenuation; + vec3 color; + float intensity; + float specularExp; +} ubuf; + +layout(location = 0) out vec4 fragColor; + +void main() +{ + vec3 unnormL = ubuf.ECLightPosition - vECVertPos; + float dist = length(unnormL); + float att = 1.0 / (ubuf.attenuation.x + ubuf.attenuation.y * dist + ubuf.attenuation.z * dist * dist); + + vec3 N = normalize(vECVertNormal); + vec3 L = normalize(unnormL); + float NL = max(0.0, dot(N, L)); + vec3 dColor = att * ubuf.intensity * ubuf.color * NL; + + vec3 R = reflect(-L, N); + vec3 V = normalize(ubuf.ECCameraPosition - vECVertPos); + float RV = max(0.0, dot(R, V)); + vec3 sColor = att * ubuf.intensity * ubuf.color * pow(RV, ubuf.specularExp); + + fragColor = vec4(ubuf.ka + (ubuf.kd + vDiffuseAdjust) * dColor + ubuf.ks * sColor, 1.0); +} diff --git a/examples/vulkan/hellovulkancubes/color_phong.vert b/examples/vulkan/hellovulkancubes/color_phong.vert new file mode 100644 index 0000000000..a1d1552685 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color_phong.vert @@ -0,0 +1,32 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec3 normal; + +// Instanced attributes to variate the translation of the model and the diffuse +// color of the material. +layout(location = 2) in vec3 instTranslate; +layout(location = 3) in vec3 instDiffuseAdjust; + +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out vec3 vECVertNormal; +layout(location = 1) out vec3 vECVertPos; +layout(location = 2) flat out vec3 vDiffuseAdjust; + +layout(std140, binding = 0) uniform buf { + mat4 vp; + mat4 model; + mat3 modelNormal; +} ubuf; + +void main() +{ + vECVertNormal = normalize(ubuf.modelNormal * normal); + mat4 t = mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + instTranslate.x, instTranslate.y, instTranslate.z, 1); + vECVertPos = vec3(t * ubuf.model * position); + vDiffuseAdjust = instDiffuseAdjust; + gl_Position = ubuf.vp * t * ubuf.model * position; +} diff --git a/examples/vulkan/hellovulkancubes/color_phong_frag.spv b/examples/vulkan/hellovulkancubes/color_phong_frag.spv Binary files differnew file mode 100644 index 0000000000..a1a413533b --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color_phong_frag.spv diff --git a/examples/vulkan/hellovulkancubes/color_phong_vert.spv b/examples/vulkan/hellovulkancubes/color_phong_vert.spv Binary files differnew file mode 100644 index 0000000000..3ede21e007 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color_phong_vert.spv diff --git a/examples/vulkan/hellovulkancubes/color_vert.spv b/examples/vulkan/hellovulkancubes/color_vert.spv Binary files differnew file mode 100644 index 0000000000..3f708b7d07 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/color_vert.spv diff --git a/examples/vulkan/hellovulkancubes/hellovulkancubes.pro b/examples/vulkan/hellovulkancubes/hellovulkancubes.pro new file mode 100644 index 0000000000..f9a9c3cff1 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/hellovulkancubes.pro @@ -0,0 +1,24 @@ +QT += widgets concurrent + +HEADERS += \ + mainwindow.h \ + vulkanwindow.h \ + renderer.h \ + mesh.h \ + shader.h \ + camera.h + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + vulkanwindow.cpp \ + renderer.cpp \ + mesh.cpp \ + shader.cpp \ + camera.cpp + +RESOURCES += hellovulkancubes.qrc + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/vulkan/hellovulkancubes +INSTALLS += target diff --git a/examples/vulkan/hellovulkancubes/hellovulkancubes.qrc b/examples/vulkan/hellovulkancubes/hellovulkancubes.qrc new file mode 100644 index 0000000000..7b085e1875 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/hellovulkancubes.qrc @@ -0,0 +1,10 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file alias="block.buf">../shared/block.buf</file> + <file alias="qt_logo.buf">../shared/qt_logo.buf</file> + <file>color_phong_vert.spv</file> + <file>color_phong_frag.spv</file> + <file>color_vert.spv</file> + <file>color_frag.spv</file> +</qresource> +</RCC> diff --git a/examples/vulkan/hellovulkancubes/main.cpp b/examples/vulkan/hellovulkancubes/main.cpp new file mode 100644 index 0000000000..4ec4d48645 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/main.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QApplication> +#include <QLoggingCategory> +#include "mainwindow.h" +#include "vulkanwindow.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + const bool dbg = qEnvironmentVariableIntValue("QT_VK_DEBUG"); + + QVulkanInstance inst; + + if (dbg) { + QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true")); + +#ifndef Q_OS_ANDROID + inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); +#else + inst.setLayers(QByteArrayList() + << "VK_LAYER_GOOGLE_threading" + << "VK_LAYER_LUNARG_parameter_validation" + << "VK_LAYER_LUNARG_object_tracker" + << "VK_LAYER_LUNARG_core_validation" + << "VK_LAYER_LUNARG_image" + << "VK_LAYER_LUNARG_swapchain" + << "VK_LAYER_GOOGLE_unique_objects"); +#endif + } + + if (!inst.create()) + qFatal("Failed to create Vulkan instance: %d", inst.errorCode()); + + VulkanWindow *vulkanWindow = new VulkanWindow(dbg); + vulkanWindow->setVulkanInstance(&inst); + + MainWindow mainWindow(vulkanWindow); + mainWindow.resize(1024, 768); + mainWindow.show(); + + return app.exec(); +} diff --git a/examples/vulkan/hellovulkancubes/mainwindow.cpp b/examples/vulkan/hellovulkancubes/mainwindow.cpp new file mode 100644 index 0000000000..2be7d237af --- /dev/null +++ b/examples/vulkan/hellovulkancubes/mainwindow.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "vulkanwindow.h" +#include <QApplication> +#include <QLabel> +#include <QPushButton> +#include <QLCDNumber> +#include <QCheckBox> +#include <QGridLayout> + +MainWindow::MainWindow(VulkanWindow *vulkanWindow) +{ + QWidget *wrapper = QWidget::createWindowContainer(vulkanWindow); + wrapper->setFocusPolicy(Qt::StrongFocus); + wrapper->setFocus(); + + infoLabel = new QLabel; + infoLabel->setFrameStyle(QFrame::Box | QFrame::Raised); + infoLabel->setAlignment(Qt::AlignCenter); + infoLabel->setText(tr("This example demonstrates instanced drawing\nof a mesh loaded from a file.\n" + "Uses a Phong material with a single light.\n" + "Also demonstrates dynamic uniform buffers\nand a bit of threading with QtConcurrent.\n" + "Uses 4x MSAA when available.\n" + "Comes with an FPS camera.\n" + "Hit [Shift+]WASD to walk and strafe.\nPress and move mouse to look around.\n" + "Click Add New to increase the number of instances.")); + + meshSwitch = new QCheckBox(tr("&Use Qt logo")); + meshSwitch->setFocusPolicy(Qt::NoFocus); // do not interfere with vulkanWindow's keyboard input + + counterLcd = new QLCDNumber(5); + counterLcd->setSegmentStyle(QLCDNumber::Filled); + counterLcd->display(m_count); + + newButton = new QPushButton(tr("&Add new")); + newButton->setFocusPolicy(Qt::NoFocus); + quitButton = new QPushButton(tr("&Quit")); + quitButton->setFocusPolicy(Qt::NoFocus); + pauseButton = new QPushButton(tr("&Pause")); + pauseButton->setFocusPolicy(Qt::NoFocus); + + connect(quitButton, &QPushButton::clicked, qApp, &QCoreApplication::quit); + connect(newButton, &QPushButton::clicked, vulkanWindow, [=] { + vulkanWindow->addNew(); + m_count = vulkanWindow->instanceCount(); + counterLcd->display(m_count); + }); + connect(pauseButton, &QPushButton::clicked, vulkanWindow, &VulkanWindow::togglePaused); + connect(meshSwitch, &QCheckBox::clicked, vulkanWindow, &VulkanWindow::meshSwitched); + + QGridLayout *layout = new QGridLayout; + layout->addWidget(infoLabel, 0, 2); + layout->addWidget(meshSwitch, 1, 2); + layout->addWidget(createLabel(tr("INSTANCES")), 2, 2); + layout->addWidget(counterLcd, 3, 2); + layout->addWidget(newButton, 4, 2); + layout->addWidget(pauseButton, 5, 2); + layout->addWidget(quitButton, 6, 2); + layout->addWidget(wrapper, 0, 0, 7, 2); + setLayout(layout); +} + +QLabel *MainWindow::createLabel(const QString &text) +{ + QLabel *lbl = new QLabel(text); + lbl->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + return lbl; +} diff --git a/examples/vulkan/hellovulkancubes/mainwindow.h b/examples/vulkan/hellovulkancubes/mainwindow.h new file mode 100644 index 0000000000..4109709959 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/mainwindow.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QWidget> + +QT_BEGIN_NAMESPACE +class QLCDNumber; +class QLabel; +class QPushButton; +class QCheckBox; +QT_END_NAMESPACE + +class VulkanWindow; + +class MainWindow : public QWidget +{ +public: + MainWindow(VulkanWindow *vulkanWindow); + +private: + QLabel *createLabel(const QString &text); + + QLabel *infoLabel; + QCheckBox *meshSwitch; + QLCDNumber *counterLcd; + QPushButton *newButton; + QPushButton *quitButton; + QPushButton *pauseButton; + + int m_count = 128; +}; + +#endif diff --git a/examples/vulkan/hellovulkancubes/mesh.cpp b/examples/vulkan/hellovulkancubes/mesh.cpp new file mode 100644 index 0000000000..fcc45bfd57 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/mesh.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mesh.h" +#include <QtConcurrentRun> +#include <QFile> + +void Mesh::load(const QString &fn) +{ + reset(); + m_maybeRunning = true; + m_future = QtConcurrent::run([fn]() { + MeshData md; + QFile f(fn); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("Failed to open %s", qPrintable(fn)); + return md; + } + QByteArray buf = f.readAll(); + const char *p = buf.constData(); + quint32 format; + memcpy(&format, p, 4); + if (format != 1) { + qWarning("Invalid format in %s", qPrintable(fn)); + return md; + } + int ofs = 4; + memcpy(&md.vertexCount, p + ofs, 4); + ofs += 4; + memcpy(md.aabb, p + ofs, 6 * 4); + ofs += 6 * 4; + const int byteCount = md.vertexCount * 8 * 4; + md.geom.resize(byteCount); + memcpy(md.geom.data(), p + ofs, byteCount); + return md; + }); +} + +MeshData *Mesh::data() +{ + if (m_maybeRunning && !m_data.isValid()) + m_data = m_future.result(); + + return &m_data; +} + +void Mesh::reset() +{ + *data() = MeshData(); + m_maybeRunning = false; +} diff --git a/examples/vulkan/hellovulkancubes/mesh.h b/examples/vulkan/hellovulkancubes/mesh.h new file mode 100644 index 0000000000..cb6ee9c830 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/mesh.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MESH_H +#define MESH_H + +#include <QString> +#include <QFuture> + +struct MeshData +{ + bool isValid() const { return vertexCount > 0; } + int vertexCount = 0; + float aabb[6]; + QByteArray geom; // x, y, z, u, v, nx, ny, nz +}; + +class Mesh +{ +public: + void load(const QString &fn); + MeshData *data(); + bool isValid() { return data()->isValid(); } + void reset(); + +private: + bool m_maybeRunning = false; + QFuture<MeshData> m_future; + MeshData m_data; +}; + +#endif diff --git a/examples/vulkan/hellovulkancubes/renderer.cpp b/examples/vulkan/hellovulkancubes/renderer.cpp new file mode 100644 index 0000000000..96ae6020a7 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/renderer.cpp @@ -0,0 +1,1048 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "renderer.h" +#include <QVulkanFunctions> +#include <QtConcurrentRun> +#include <QTime> + +static float quadVert[] = { + -1, -1, 0, + -1, 1, 0, + 1, -1, 0, + 1, 1, 0 +}; + +#define DBG Q_UNLIKELY(m_window->isDebugEnabled()) + +const int MAX_INSTANCES = 16384; +const VkDeviceSize PER_INSTANCE_DATA_SIZE = 6 * sizeof(float); // instTranslate, instDiffuseAdjust + +static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign) +{ + return (v + byteAlign - 1) & ~(byteAlign - 1); +} + +Renderer::Renderer(VulkanWindow *w, int initialCount) + : m_window(w), + // Have the light positioned just behind the default camera position, looking forward. + m_lightPos(0.0f, 0.0f, 25.0f), + m_cam(QVector3D(0.0f, 0.0f, 20.0f)), // starting camera position + m_instCount(initialCount) +{ + qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime())); + + m_floorModel.translate(0, -5, 0); + m_floorModel.rotate(-90, 1, 0, 0); + m_floorModel.scale(20, 100, 1); + + m_blockMesh.load(QStringLiteral(":/block.buf")); + m_logoMesh.load(QStringLiteral(":/qt_logo.buf")); + + QObject::connect(&m_frameWatcher, &QFutureWatcherBase::finished, [this] { + if (m_framePending) { + m_framePending = false; + m_window->frameReady(); + m_window->requestUpdate(); + } + }); +} + +void Renderer::preInitResources() +{ + QSet<int> sampleCounts = m_window->supportedSampleCounts(); + if (DBG) + qDebug() << "Supported sample counts:" << sampleCounts; + if (sampleCounts.contains(4)) { + if (DBG) + qDebug("Requesting 4x MSAA"); + m_window->setSampleCount(4); + } +} + +void Renderer::initResources() +{ + if (DBG) + qDebug("Renderer init"); + + m_animating = true; + m_framePending = false; + + QVulkanInstance *inst = m_window->vulkanInstance(); + VkDevice dev = m_window->device(); + const VkPhysicalDeviceLimits *pdevLimits = &m_window->physicalDeviceProperties()->limits; + const VkDeviceSize uniAlign = pdevLimits->minUniformBufferOffsetAlignment; + + m_devFuncs = inst->deviceFunctions(dev); + + // Note the std140 packing rules. A vec3 still has an alignment of 16, + // while a mat3 is like 3 * vec3. + m_itemMaterial.vertUniSize = aligned(2 * 64 + 48, uniAlign); // see color_phong.vert + m_itemMaterial.fragUniSize = aligned(6 * 16 + 12 + 2 * 4, uniAlign); // see color_phong.frag + + if (!m_itemMaterial.vs.isValid()) + m_itemMaterial.vs.load(inst, dev, QStringLiteral(":/color_phong_vert.spv")); + if (!m_itemMaterial.fs.isValid()) + m_itemMaterial.fs.load(inst, dev, QStringLiteral(":/color_phong_frag.spv")); + + if (!m_floorMaterial.vs.isValid()) + m_floorMaterial.vs.load(inst, dev, QStringLiteral(":/color_vert.spv")); + if (!m_floorMaterial.fs.isValid()) + m_floorMaterial.fs.load(inst, dev, QStringLiteral(":/color_frag.spv")); + + m_pipelinesFuture = QtConcurrent::run(this, &Renderer::createPipelines); +} + +void Renderer::createPipelines() +{ + VkDevice dev = m_window->device(); + + VkPipelineCacheCreateInfo pipelineCacheInfo; + memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo)); + pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + VkResult err = m_devFuncs->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &m_pipelineCache); + if (err != VK_SUCCESS) + qFatal("Failed to create pipeline cache: %d", err); + + createItemPipeline(); + createFloorPipeline(); +} + +void Renderer::createItemPipeline() +{ + VkDevice dev = m_window->device(); + + // Vertex layout. + VkVertexInputBindingDescription vertexBindingDesc[] = { + { + 0, // binding + 8 * sizeof(float), + VK_VERTEX_INPUT_RATE_VERTEX + }, + { + 1, + 6 * sizeof(float), + VK_VERTEX_INPUT_RATE_INSTANCE + } + }; + VkVertexInputAttributeDescription vertexAttrDesc[] = { + { // position + 0, // location + 0, // binding + VK_FORMAT_R32G32B32_SFLOAT, + 0 // offset + }, + { // normal + 1, + 0, + VK_FORMAT_R32G32B32_SFLOAT, + 5 * sizeof(float) + }, + { // instTranslate + 2, + 1, + VK_FORMAT_R32G32B32_SFLOAT, + 0 + }, + { // instDiffuseAdjust + 3, + 1, + VK_FORMAT_R32G32B32_SFLOAT, + 3 * sizeof(float) + } + }; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.pNext = nullptr; + vertexInputInfo.flags = 0; + vertexInputInfo.vertexBindingDescriptionCount = sizeof(vertexBindingDesc) / sizeof(vertexBindingDesc[0]); + vertexInputInfo.pVertexBindingDescriptions = vertexBindingDesc; + vertexInputInfo.vertexAttributeDescriptionCount = sizeof(vertexAttrDesc) / sizeof(vertexAttrDesc[0]); + vertexInputInfo.pVertexAttributeDescriptions = vertexAttrDesc; + + // Descriptor set layout. + VkDescriptorPoolSize descPoolSizes[] = { + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 2 } + }; + VkDescriptorPoolCreateInfo descPoolInfo; + memset(&descPoolInfo, 0, sizeof(descPoolInfo)); + descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descPoolInfo.maxSets = 1; // a single set is enough due to the dynamic uniform buffer + descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]); + descPoolInfo.pPoolSizes = descPoolSizes; + VkResult err = m_devFuncs->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, &m_itemMaterial.descPool); + if (err != VK_SUCCESS) + qFatal("Failed to create descriptor pool: %d", err); + + VkDescriptorSetLayoutBinding layoutBindings[] = + { + { + 0, // binding + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, + 1, // descriptorCount + VK_SHADER_STAGE_VERTEX_BIT, + nullptr + }, + { + 1, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, + 1, + VK_SHADER_STAGE_FRAGMENT_BIT, + nullptr + } + }; + VkDescriptorSetLayoutCreateInfo descLayoutInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + nullptr, + 0, + sizeof(layoutBindings) / sizeof(layoutBindings[0]), + layoutBindings + }; + err = m_devFuncs->vkCreateDescriptorSetLayout(dev, &descLayoutInfo, nullptr, &m_itemMaterial.descSetLayout); + if (err != VK_SUCCESS) + qFatal("Failed to create descriptor set layout: %d", err); + + VkDescriptorSetAllocateInfo descSetAllocInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + nullptr, + m_itemMaterial.descPool, + 1, + &m_itemMaterial.descSetLayout + }; + err = m_devFuncs->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_itemMaterial.descSet); + if (err != VK_SUCCESS) + qFatal("Failed to allocate descriptor set: %d", err); + + // Graphics pipeline. + VkPipelineLayoutCreateInfo pipelineLayoutInfo; + memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &m_itemMaterial.descSetLayout; + + err = m_devFuncs->vkCreatePipelineLayout(dev, &pipelineLayoutInfo, nullptr, &m_itemMaterial.pipelineLayout); + if (err != VK_SUCCESS) + qFatal("Failed to create pipeline layout: %d", err); + + VkGraphicsPipelineCreateInfo pipelineInfo; + memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + + VkPipelineShaderStageCreateInfo shaderStages[2] = { + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_VERTEX_BIT, + m_itemMaterial.vs.data()->shaderModule, + "main", + nullptr + }, + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_FRAGMENT_BIT, + m_itemMaterial.fs.data()->shaderModule, + "main", + nullptr + } + }; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + + pipelineInfo.pVertexInputState = &vertexInputInfo; + + VkPipelineInputAssemblyStateCreateInfo ia; + memset(&ia, 0, sizeof(ia)); + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineInfo.pInputAssemblyState = &ia; + + VkPipelineViewportStateCreateInfo vp; + memset(&vp, 0, sizeof(vp)); + vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; + vp.scissorCount = 1; + pipelineInfo.pViewportState = &vp; + + VkPipelineRasterizationStateCreateInfo rs; + memset(&rs, 0, sizeof(rs)); + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rs.lineWidth = 1.0f; + pipelineInfo.pRasterizationState = &rs; + + VkPipelineMultisampleStateCreateInfo ms; + memset(&ms, 0, sizeof(ms)); + ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.rasterizationSamples = m_window->sampleCountFlagBits(); + pipelineInfo.pMultisampleState = &ms; + + VkPipelineDepthStencilStateCreateInfo ds; + memset(&ds, 0, sizeof(ds)); + ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + ds.depthTestEnable = VK_TRUE; + ds.depthWriteEnable = VK_TRUE; + ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + pipelineInfo.pDepthStencilState = &ds; + + VkPipelineColorBlendStateCreateInfo cb; + memset(&cb, 0, sizeof(cb)); + cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + VkPipelineColorBlendAttachmentState att; + memset(&att, 0, sizeof(att)); + att.colorWriteMask = 0xF; + cb.attachmentCount = 1; + cb.pAttachments = &att; + pipelineInfo.pColorBlendState = &cb; + + VkDynamicState dynEnable[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dyn; + memset(&dyn, 0, sizeof(dyn)); + dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dyn.dynamicStateCount = sizeof(dynEnable) / sizeof(VkDynamicState); + dyn.pDynamicStates = dynEnable; + pipelineInfo.pDynamicState = &dyn; + + pipelineInfo.layout = m_itemMaterial.pipelineLayout; + pipelineInfo.renderPass = m_window->defaultRenderPass(); + + err = m_devFuncs->vkCreateGraphicsPipelines(dev, m_pipelineCache, 1, &pipelineInfo, nullptr, &m_itemMaterial.pipeline); + if (err != VK_SUCCESS) + qFatal("Failed to create graphics pipeline: %d", err); +} + +void Renderer::createFloorPipeline() +{ + VkDevice dev = m_window->device(); + + // Vertex layout. + VkVertexInputBindingDescription vertexBindingDesc = { + 0, // binding + 3 * sizeof(float), + VK_VERTEX_INPUT_RATE_VERTEX + }; + VkVertexInputAttributeDescription vertexAttrDesc[] = { + { // position + 0, // location + 0, // binding + VK_FORMAT_R32G32B32_SFLOAT, + 0 // offset + }, + }; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.pNext = nullptr; + vertexInputInfo.flags = 0; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &vertexBindingDesc; + vertexInputInfo.vertexAttributeDescriptionCount = sizeof(vertexAttrDesc) / sizeof(vertexAttrDesc[0]); + vertexInputInfo.pVertexAttributeDescriptions = vertexAttrDesc; + + // Do not bother with uniform buffers and descriptors, all the data fits + // into the spec mandated minimum of 128 bytes for push constants. + VkPushConstantRange pcr[] = { + // mvp + { + VK_SHADER_STAGE_VERTEX_BIT, + 0, + 64 + }, + // color + { + VK_SHADER_STAGE_FRAGMENT_BIT, + 64, + 12 + } + }; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo; + memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.pushConstantRangeCount = sizeof(pcr) / sizeof(pcr[0]); + pipelineLayoutInfo.pPushConstantRanges = pcr; + + VkResult err = m_devFuncs->vkCreatePipelineLayout(dev, &pipelineLayoutInfo, nullptr, &m_floorMaterial.pipelineLayout); + if (err != VK_SUCCESS) + qFatal("Failed to create pipeline layout: %d", err); + + VkGraphicsPipelineCreateInfo pipelineInfo; + memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + + VkPipelineShaderStageCreateInfo shaderStages[2] = { + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_VERTEX_BIT, + m_floorMaterial.vs.data()->shaderModule, + "main", + nullptr + }, + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_FRAGMENT_BIT, + m_floorMaterial.fs.data()->shaderModule, + "main", + nullptr + } + }; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + + pipelineInfo.pVertexInputState = &vertexInputInfo; + + VkPipelineInputAssemblyStateCreateInfo ia; + memset(&ia, 0, sizeof(ia)); + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + pipelineInfo.pInputAssemblyState = &ia; + + VkPipelineViewportStateCreateInfo vp; + memset(&vp, 0, sizeof(vp)); + vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; + vp.scissorCount = 1; + pipelineInfo.pViewportState = &vp; + + VkPipelineRasterizationStateCreateInfo rs; + memset(&rs, 0, sizeof(rs)); + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_CLOCKWISE; + rs.lineWidth = 1.0f; + pipelineInfo.pRasterizationState = &rs; + + VkPipelineMultisampleStateCreateInfo ms; + memset(&ms, 0, sizeof(ms)); + ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.rasterizationSamples = m_window->sampleCountFlagBits(); + pipelineInfo.pMultisampleState = &ms; + + VkPipelineDepthStencilStateCreateInfo ds; + memset(&ds, 0, sizeof(ds)); + ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + ds.depthTestEnable = VK_TRUE; + ds.depthWriteEnable = VK_TRUE; + ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + pipelineInfo.pDepthStencilState = &ds; + + VkPipelineColorBlendStateCreateInfo cb; + memset(&cb, 0, sizeof(cb)); + cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + VkPipelineColorBlendAttachmentState att; + memset(&att, 0, sizeof(att)); + att.colorWriteMask = 0xF; + cb.attachmentCount = 1; + cb.pAttachments = &att; + pipelineInfo.pColorBlendState = &cb; + + VkDynamicState dynEnable[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dyn; + memset(&dyn, 0, sizeof(dyn)); + dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dyn.dynamicStateCount = sizeof(dynEnable) / sizeof(VkDynamicState); + dyn.pDynamicStates = dynEnable; + pipelineInfo.pDynamicState = &dyn; + + pipelineInfo.layout = m_floorMaterial.pipelineLayout; + pipelineInfo.renderPass = m_window->defaultRenderPass(); + + err = m_devFuncs->vkCreateGraphicsPipelines(dev, m_pipelineCache, 1, &pipelineInfo, nullptr, &m_floorMaterial.pipeline); + if (err != VK_SUCCESS) + qFatal("Failed to create graphics pipeline: %d", err); +} + +void Renderer::initSwapChainResources() +{ + m_proj = *m_window->clipCorrectionMatrix(); + const QSize sz = m_window->swapChainImageSize(); + m_proj.perspective(45.0f, sz.width() / (float) sz.height(), 0.01f, 1000.0f); + markViewProjDirty(); +} + +void Renderer::releaseSwapChainResources() +{ + // It is important to finish the pending frame right here since this is the + // last opportunity to act with all resources intact. + m_frameWatcher.waitForFinished(); + // Cannot count on the finished() signal being emitted before returning + // from here. + if (m_framePending) { + m_framePending = false; + m_window->frameReady(); + } +} + +void Renderer::releaseResources() +{ + if (DBG) + qDebug("Renderer release"); + + m_pipelinesFuture.waitForFinished(); + + VkDevice dev = m_window->device(); + + if (m_itemMaterial.descSetLayout) { + m_devFuncs->vkDestroyDescriptorSetLayout(dev, m_itemMaterial.descSetLayout, nullptr); + m_itemMaterial.descSetLayout = VK_NULL_HANDLE; + } + + if (m_itemMaterial.descPool) { + m_devFuncs->vkDestroyDescriptorPool(dev, m_itemMaterial.descPool, nullptr); + m_itemMaterial.descPool = VK_NULL_HANDLE; + } + + if (m_itemMaterial.pipeline) { + m_devFuncs->vkDestroyPipeline(dev, m_itemMaterial.pipeline, nullptr); + m_itemMaterial.pipeline = VK_NULL_HANDLE; + } + + if (m_itemMaterial.pipelineLayout) { + m_devFuncs->vkDestroyPipelineLayout(dev, m_itemMaterial.pipelineLayout, nullptr); + m_itemMaterial.pipelineLayout = VK_NULL_HANDLE; + } + + if (m_floorMaterial.pipeline) { + m_devFuncs->vkDestroyPipeline(dev, m_floorMaterial.pipeline, nullptr); + m_floorMaterial.pipeline = VK_NULL_HANDLE; + } + + if (m_floorMaterial.pipelineLayout) { + m_devFuncs->vkDestroyPipelineLayout(dev, m_floorMaterial.pipelineLayout, nullptr); + m_floorMaterial.pipelineLayout = VK_NULL_HANDLE; + } + + if (m_pipelineCache) { + m_devFuncs->vkDestroyPipelineCache(dev, m_pipelineCache, nullptr); + m_pipelineCache = VK_NULL_HANDLE; + } + + if (m_blockVertexBuf) { + m_devFuncs->vkDestroyBuffer(dev, m_blockVertexBuf, nullptr); + m_blockVertexBuf = VK_NULL_HANDLE; + } + + if (m_logoVertexBuf) { + m_devFuncs->vkDestroyBuffer(dev, m_logoVertexBuf, nullptr); + m_logoVertexBuf = VK_NULL_HANDLE; + } + + if (m_floorVertexBuf) { + m_devFuncs->vkDestroyBuffer(dev, m_floorVertexBuf, nullptr); + m_floorVertexBuf = VK_NULL_HANDLE; + } + + if (m_uniBuf) { + m_devFuncs->vkDestroyBuffer(dev, m_uniBuf, nullptr); + m_uniBuf = VK_NULL_HANDLE; + } + + if (m_bufMem) { + m_devFuncs->vkFreeMemory(dev, m_bufMem, nullptr); + m_bufMem = VK_NULL_HANDLE; + } + + if (m_instBuf) { + m_devFuncs->vkDestroyBuffer(dev, m_instBuf, nullptr); + m_instBuf = VK_NULL_HANDLE; + } + + if (m_instBufMem) { + m_devFuncs->vkFreeMemory(dev, m_instBufMem, nullptr); + m_instBufMem = VK_NULL_HANDLE; + } + + if (m_itemMaterial.vs.isValid()) { + m_devFuncs->vkDestroyShaderModule(dev, m_itemMaterial.vs.data()->shaderModule, nullptr); + m_itemMaterial.vs.reset(); + } + if (m_itemMaterial.fs.isValid()) { + m_devFuncs->vkDestroyShaderModule(dev, m_itemMaterial.fs.data()->shaderModule, nullptr); + m_itemMaterial.fs.reset(); + } + + if (m_floorMaterial.vs.isValid()) { + m_devFuncs->vkDestroyShaderModule(dev, m_floorMaterial.vs.data()->shaderModule, nullptr); + m_floorMaterial.vs.reset(); + } + if (m_floorMaterial.fs.isValid()) { + m_devFuncs->vkDestroyShaderModule(dev, m_floorMaterial.fs.data()->shaderModule, nullptr); + m_floorMaterial.fs.reset(); + } +} + +void Renderer::ensureBuffers() +{ + if (m_blockVertexBuf) + return; + + VkDevice dev = m_window->device(); + const int concurrentFrameCount = m_window->concurrentFrameCount(); + + // Vertex buffer for the block. + VkBufferCreateInfo bufInfo; + memset(&bufInfo, 0, sizeof(bufInfo)); + bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + const int blockMeshByteCount = m_blockMesh.data()->vertexCount * 8 * sizeof(float); + bufInfo.size = blockMeshByteCount; + bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + VkResult err = m_devFuncs->vkCreateBuffer(dev, &bufInfo, nullptr, &m_blockVertexBuf); + if (err != VK_SUCCESS) + qFatal("Failed to create vertex buffer: %d", err); + + VkMemoryRequirements blockVertMemReq; + m_devFuncs->vkGetBufferMemoryRequirements(dev, m_blockVertexBuf, &blockVertMemReq); + + // Vertex buffer for the logo. + const int logoMeshByteCount = m_logoMesh.data()->vertexCount * 8 * sizeof(float); + bufInfo.size = logoMeshByteCount; + bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + err = m_devFuncs->vkCreateBuffer(dev, &bufInfo, nullptr, &m_logoVertexBuf); + if (err != VK_SUCCESS) + qFatal("Failed to create vertex buffer: %d", err); + + VkMemoryRequirements logoVertMemReq; + m_devFuncs->vkGetBufferMemoryRequirements(dev, m_logoVertexBuf, &logoVertMemReq); + + // Vertex buffer for the floor. + bufInfo.size = sizeof(quadVert); + err = m_devFuncs->vkCreateBuffer(dev, &bufInfo, nullptr, &m_floorVertexBuf); + if (err != VK_SUCCESS) + qFatal("Failed to create vertex buffer: %d", err); + + VkMemoryRequirements floorVertMemReq; + m_devFuncs->vkGetBufferMemoryRequirements(dev, m_floorVertexBuf, &floorVertMemReq); + + // Uniform buffer. Instead of using multiple descriptor sets, we take a + // different approach: have a single dynamic uniform buffer and specify the + // active-frame-specific offset at the time of binding the descriptor set. + bufInfo.size = (m_itemMaterial.vertUniSize + m_itemMaterial.fragUniSize) * concurrentFrameCount; + bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + err = m_devFuncs->vkCreateBuffer(dev, &bufInfo, nullptr, &m_uniBuf); + if (err != VK_SUCCESS) + qFatal("Failed to create uniform buffer: %d", err); + + VkMemoryRequirements uniMemReq; + m_devFuncs->vkGetBufferMemoryRequirements(dev, m_uniBuf, &uniMemReq); + + // Allocate memory for everything at once. + VkDeviceSize logoVertStartOffset = aligned(0 + blockVertMemReq.size, logoVertMemReq.alignment); + VkDeviceSize floorVertStartOffset = aligned(logoVertStartOffset + logoVertMemReq.size, floorVertMemReq.alignment); + m_itemMaterial.uniMemStartOffset = aligned(floorVertStartOffset + floorVertMemReq.size, uniMemReq.alignment); + VkMemoryAllocateInfo memAllocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + nullptr, + m_itemMaterial.uniMemStartOffset + uniMemReq.size, + m_window->hostVisibleMemoryIndex() + }; + err = m_devFuncs->vkAllocateMemory(dev, &memAllocInfo, nullptr, &m_bufMem); + if (err != VK_SUCCESS) + qFatal("Failed to allocate memory: %d", err); + + err = m_devFuncs->vkBindBufferMemory(dev, m_blockVertexBuf, m_bufMem, 0); + if (err != VK_SUCCESS) + qFatal("Failed to bind vertex buffer memory: %d", err); + err = m_devFuncs->vkBindBufferMemory(dev, m_logoVertexBuf, m_bufMem, logoVertStartOffset); + if (err != VK_SUCCESS) + qFatal("Failed to bind vertex buffer memory: %d", err); + err = m_devFuncs->vkBindBufferMemory(dev, m_floorVertexBuf, m_bufMem, floorVertStartOffset); + if (err != VK_SUCCESS) + qFatal("Failed to bind vertex buffer memory: %d", err); + err = m_devFuncs->vkBindBufferMemory(dev, m_uniBuf, m_bufMem, m_itemMaterial.uniMemStartOffset); + if (err != VK_SUCCESS) + qFatal("Failed to bind uniform buffer memory: %d", err); + + // Copy vertex data. + quint8 *p; + err = m_devFuncs->vkMapMemory(dev, m_bufMem, 0, m_itemMaterial.uniMemStartOffset, 0, reinterpret_cast<void **>(&p)); + if (err != VK_SUCCESS) + qFatal("Failed to map memory: %d", err); + memcpy(p, m_blockMesh.data()->geom.constData(), blockMeshByteCount); + memcpy(p + logoVertStartOffset, m_logoMesh.data()->geom.constData(), logoMeshByteCount); + memcpy(p + floorVertStartOffset, quadVert, sizeof(quadVert)); + m_devFuncs->vkUnmapMemory(dev, m_bufMem); + + // Write descriptors for the uniform buffers in the vertex and fragment shaders. + VkDescriptorBufferInfo vertUni = { m_uniBuf, 0, m_itemMaterial.vertUniSize }; + VkDescriptorBufferInfo fragUni = { m_uniBuf, m_itemMaterial.vertUniSize, m_itemMaterial.fragUniSize }; + + VkWriteDescriptorSet descWrite[2]; + memset(descWrite, 0, sizeof(descWrite)); + descWrite[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descWrite[0].dstSet = m_itemMaterial.descSet; + descWrite[0].dstBinding = 0; + descWrite[0].descriptorCount = 1; + descWrite[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + descWrite[0].pBufferInfo = &vertUni; + + descWrite[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descWrite[1].dstSet = m_itemMaterial.descSet; + descWrite[1].dstBinding = 1; + descWrite[1].descriptorCount = 1; + descWrite[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + descWrite[1].pBufferInfo = &fragUni; + + m_devFuncs->vkUpdateDescriptorSets(dev, 2, descWrite, 0, nullptr); +} + +void Renderer::ensureInstanceBuffer() +{ + if (m_instCount == m_preparedInstCount && m_instBuf) + return; + + Q_ASSERT(m_instCount <= MAX_INSTANCES); + + VkDevice dev = m_window->device(); + + // allocate only once, for the maximum instance count + if (!m_instBuf) { + VkBufferCreateInfo bufInfo; + memset(&bufInfo, 0, sizeof(bufInfo)); + bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufInfo.size = MAX_INSTANCES * PER_INSTANCE_DATA_SIZE; + bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + // Keep a copy of the data since we may lose all graphics resources on + // unexpose, and reinitializing to new random positions afterwards + // would not be nice. + m_instData.resize(bufInfo.size); + + VkResult err = m_devFuncs->vkCreateBuffer(dev, &bufInfo, nullptr, &m_instBuf); + if (err != VK_SUCCESS) + qFatal("Failed to create instance buffer: %d", err); + + VkMemoryRequirements memReq; + m_devFuncs->vkGetBufferMemoryRequirements(dev, m_instBuf, &memReq); + if (DBG) + qDebug("Allocating %u bytes for instance data", uint32_t(memReq.size)); + + VkMemoryAllocateInfo memAllocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + nullptr, + memReq.size, + m_window->hostVisibleMemoryIndex() + }; + err = m_devFuncs->vkAllocateMemory(dev, &memAllocInfo, nullptr, &m_instBufMem); + if (err != VK_SUCCESS) + qFatal("Failed to allocate memory: %d", err); + + err = m_devFuncs->vkBindBufferMemory(dev, m_instBuf, m_instBufMem, 0); + if (err != VK_SUCCESS) + qFatal("Failed to bind instance buffer memory: %d", err); + } + + if (m_instCount != m_preparedInstCount) { + if (DBG) + qDebug("Preparing instances %d..%d", m_preparedInstCount, m_instCount - 1); + char *p = m_instData.data(); + p += m_preparedInstCount * PER_INSTANCE_DATA_SIZE; + auto gen = [](float a, float b) { return float((qrand() % int(b - a + 1)) + a); }; + for (int i = m_preparedInstCount; i < m_instCount; ++i) { + // Apply a random translation to each instance of the mesh. + float t[] = { gen(-5, 5), gen(-4, 6), gen(-30, 5) }; + memcpy(p, t, 12); + // Apply a random adjustment to the diffuse color for each instance. (default is 0.7) + float d[] = { gen(-6, 3) / 10.0f, gen(-6, 3) / 10.0f, gen(-6, 3) / 10.0f }; + memcpy(p + 12, d, 12); + p += PER_INSTANCE_DATA_SIZE; + } + m_preparedInstCount = m_instCount; + } + + quint8 *p; + VkResult err = m_devFuncs->vkMapMemory(dev, m_instBufMem, 0, m_instCount * PER_INSTANCE_DATA_SIZE, 0, + reinterpret_cast<void **>(&p)); + if (err != VK_SUCCESS) + qFatal("Failed to map memory: %d", err); + memcpy(p, m_instData.constData(), m_instData.size()); + m_devFuncs->vkUnmapMemory(dev, m_instBufMem); +} + +void Renderer::getMatrices(QMatrix4x4 *vp, QMatrix4x4 *model, QMatrix3x3 *modelNormal, QVector3D *eyePos) +{ + model->setToIdentity(); + if (m_useLogo) + model->rotate(90, 1, 0, 0); + model->rotate(m_rotation, 1, 1, 0); + + *modelNormal = model->normalMatrix(); + + QMatrix4x4 view = m_cam.viewMatrix(); + *vp = m_proj * view; + + *eyePos = view.inverted().column(3).toVector3D(); +} + +void Renderer::writeFragUni(quint8 *p, const QVector3D &eyePos) +{ + float ECCameraPosition[] = { eyePos.x(), eyePos.y(), eyePos.z() }; + memcpy(p, ECCameraPosition, 12); + p += 16; + + // Material + float ka[] = { 0.05f, 0.05f, 0.05f }; + memcpy(p, ka, 12); + p += 16; + + float kd[] = { 0.7f, 0.7f, 0.7f }; + memcpy(p, kd, 12); + p += 16; + + float ks[] = { 0.66f, 0.66f, 0.66f }; + memcpy(p, ks, 12); + p += 16; + + // Light parameters + float ECLightPosition[] = { m_lightPos.x(), m_lightPos.y(), m_lightPos.z() }; + memcpy(p, ECLightPosition, 12); + p += 16; + + float att[] = { 1, 0, 0 }; + memcpy(p, att, 12); + p += 16; + + float color[] = { 1.0f, 1.0f, 1.0f }; + memcpy(p, color, 12); + p += 12; // next we have two floats which have an alignment of 4, hence 12 only + + float intensity = 0.8f; + memcpy(p, &intensity, 4); + p += 4; + + float specularExp = 150.0f; + memcpy(p, &specularExp, 4); + p += 4; +} + +void Renderer::startNextFrame() +{ + // For demonstration purposes offload the command buffer generation onto a + // worker thread and continue with the frame submission only when it has + // finished. + Q_ASSERT(!m_framePending); + m_framePending = true; + QFuture<void> future = QtConcurrent::run(this, &Renderer::buildFrame); + m_frameWatcher.setFuture(future); +} + +void Renderer::buildFrame() +{ + QMutexLocker locker(&m_guiMutex); + + ensureBuffers(); + ensureInstanceBuffer(); + m_pipelinesFuture.waitForFinished(); + + VkCommandBuffer cb = m_window->currentCommandBuffer(); + const QSize sz = m_window->swapChainImageSize(); + + VkClearColorValue clearColor = { 0.67f, 0.84f, 0.9f, 1.0f }; + VkClearDepthStencilValue clearDS = { 1, 0 }; + VkClearValue clearValues[3]; + memset(clearValues, 0, sizeof(clearValues)); + clearValues[0].color = clearValues[2].color = clearColor; + clearValues[1].depthStencil = clearDS; + + VkRenderPassBeginInfo rpBeginInfo; + memset(&rpBeginInfo, 0, sizeof(rpBeginInfo)); + rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + rpBeginInfo.renderPass = m_window->defaultRenderPass(); + rpBeginInfo.framebuffer = m_window->currentFramebuffer(); + rpBeginInfo.renderArea.extent.width = sz.width(); + rpBeginInfo.renderArea.extent.height = sz.height(); + rpBeginInfo.clearValueCount = m_window->sampleCountFlagBits() > VK_SAMPLE_COUNT_1_BIT ? 3 : 2; + rpBeginInfo.pClearValues = clearValues; + VkCommandBuffer cmdBuf = m_window->currentCommandBuffer(); + m_devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport = { + 0, 0, + float(sz.width()), float(sz.height()), + 0, 1 + }; + m_devFuncs->vkCmdSetViewport(cb, 0, 1, &viewport); + + VkRect2D scissor = { + { 0, 0 }, + { uint32_t(sz.width()), uint32_t(sz.height()) } + }; + m_devFuncs->vkCmdSetScissor(cb, 0, 1, &scissor); + + buildDrawCallsForFloor(); + buildDrawCallsForItems(); + + m_devFuncs->vkCmdEndRenderPass(cmdBuf); +} + +void Renderer::buildDrawCallsForItems() +{ + VkDevice dev = m_window->device(); + VkCommandBuffer cb = m_window->currentCommandBuffer(); + + m_devFuncs->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_itemMaterial.pipeline); + + VkDeviceSize vbOffset = 0; + m_devFuncs->vkCmdBindVertexBuffers(cb, 0, 1, m_useLogo ? &m_logoVertexBuf : &m_blockVertexBuf, &vbOffset); + m_devFuncs->vkCmdBindVertexBuffers(cb, 1, 1, &m_instBuf, &vbOffset); + + // Now provide offsets so that the two dynamic buffers point to the + // beginning of the vertex and fragment uniform data for the current frame. + uint32_t frameUniOffset = m_window->currentFrame() * (m_itemMaterial.vertUniSize + m_itemMaterial.fragUniSize); + uint32_t frameUniOffsets[] = { frameUniOffset, frameUniOffset }; + m_devFuncs->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_itemMaterial.pipelineLayout, 0, 1, + &m_itemMaterial.descSet, 2, frameUniOffsets); + + if (m_animating) + m_rotation += 0.5; + + if (m_animating || m_vpDirty) { + if (m_vpDirty) + --m_vpDirty; + QMatrix4x4 vp, model; + QMatrix3x3 modelNormal; + QVector3D eyePos; + getMatrices(&vp, &model, &modelNormal, &eyePos); + + // Map the uniform data for the current frame, ignore the geometry data at + // the beginning and the uniforms for other frames. + quint8 *p; + VkResult err = m_devFuncs->vkMapMemory(dev, m_bufMem, + m_itemMaterial.uniMemStartOffset + frameUniOffset, + m_itemMaterial.vertUniSize + m_itemMaterial.fragUniSize, + 0, reinterpret_cast<void **>(&p)); + if (err != VK_SUCCESS) + qFatal("Failed to map memory: %d", err); + + // Vertex shader uniforms + memcpy(p, vp.constData(), 64); + memcpy(p + 64, model.constData(), 64); + const float *mnp = modelNormal.constData(); + memcpy(p + 128, mnp, 12); + memcpy(p + 128 + 16, mnp + 3, 12); + memcpy(p + 128 + 32, mnp + 6, 12); + + // Fragment shader uniforms + p += m_itemMaterial.vertUniSize; + writeFragUni(p, eyePos); + + m_devFuncs->vkUnmapMemory(dev, m_bufMem); + } + + m_devFuncs->vkCmdDraw(cb, (m_useLogo ? m_logoMesh.data() : m_blockMesh.data())->vertexCount, m_instCount, 0, 0); +} + +void Renderer::buildDrawCallsForFloor() +{ + VkCommandBuffer cb = m_window->currentCommandBuffer(); + + m_devFuncs->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_floorMaterial.pipeline); + + VkDeviceSize vbOffset = 0; + m_devFuncs->vkCmdBindVertexBuffers(cb, 0, 1, &m_floorVertexBuf, &vbOffset); + + QMatrix4x4 mvp = m_proj * m_cam.viewMatrix() * m_floorModel; + m_devFuncs->vkCmdPushConstants(cb, m_floorMaterial.pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, 64, mvp.constData()); + float color[] = { 0.67f, 1.0f, 0.2f }; + m_devFuncs->vkCmdPushConstants(cb, m_floorMaterial.pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 64, 12, color); + + m_devFuncs->vkCmdDraw(cb, 4, 1, 0, 0); +} + +void Renderer::addNew() +{ + QMutexLocker locker(&m_guiMutex); + m_instCount = qMin(m_instCount + 16, MAX_INSTANCES); +} + +void Renderer::yaw(float degrees) +{ + QMutexLocker locker(&m_guiMutex); + m_cam.yaw(degrees); + markViewProjDirty(); +} + +void Renderer::pitch(float degrees) +{ + QMutexLocker locker(&m_guiMutex); + m_cam.pitch(degrees); + markViewProjDirty(); +} + +void Renderer::walk(float amount) +{ + QMutexLocker locker(&m_guiMutex); + m_cam.walk(amount); + markViewProjDirty(); +} + +void Renderer::strafe(float amount) +{ + QMutexLocker locker(&m_guiMutex); + m_cam.strafe(amount); + markViewProjDirty(); +} + +void Renderer::setUseLogo(bool b) +{ + QMutexLocker locker(&m_guiMutex); + m_useLogo = b; + if (!m_animating) + m_window->requestUpdate(); +} diff --git a/examples/vulkan/hellovulkancubes/renderer.h b/examples/vulkan/hellovulkancubes/renderer.h new file mode 100644 index 0000000000..60bb48377e --- /dev/null +++ b/examples/vulkan/hellovulkancubes/renderer.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef RENDERER_H +#define RENDERER_H + +#include "vulkanwindow.h" +#include "mesh.h" +#include "shader.h" +#include "camera.h" +#include <QFutureWatcher> +#include <QMutex> + +class Renderer : public QVulkanWindowRenderer +{ +public: + Renderer(VulkanWindow *w, int initialCount); + + void preInitResources() override; + void initResources() override; + void initSwapChainResources() override; + void releaseSwapChainResources() override; + void releaseResources() override; + + void startNextFrame() override; + + bool animating() const { return m_animating; } + void setAnimating(bool a) { m_animating = a; } + + int instanceCount() const { return m_instCount; } + void addNew(); + + void yaw(float degrees); + void pitch(float degrees); + void walk(float amount); + void strafe(float amount); + + void setUseLogo(bool b); + +private: + void createPipelines(); + void createItemPipeline(); + void createFloorPipeline(); + void ensureBuffers(); + void ensureInstanceBuffer(); + void getMatrices(QMatrix4x4 *mvp, QMatrix4x4 *model, QMatrix3x3 *modelNormal, QVector3D *eyePos); + void writeFragUni(quint8 *p, const QVector3D &eyePos); + void buildFrame(); + void buildDrawCallsForItems(); + void buildDrawCallsForFloor(); + + void markViewProjDirty() { m_vpDirty = m_window->concurrentFrameCount(); } + + VulkanWindow *m_window; + QVulkanDeviceFunctions *m_devFuncs; + + bool m_useLogo = false; + Mesh m_blockMesh; + Mesh m_logoMesh; + VkBuffer m_blockVertexBuf = VK_NULL_HANDLE; + VkBuffer m_logoVertexBuf = VK_NULL_HANDLE; + struct { + VkDeviceSize vertUniSize; + VkDeviceSize fragUniSize; + VkDeviceSize uniMemStartOffset; + Shader vs; + Shader fs; + VkDescriptorPool descPool = VK_NULL_HANDLE; + VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE; + VkDescriptorSet descSet; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + } m_itemMaterial; + + VkBuffer m_floorVertexBuf = VK_NULL_HANDLE; + struct { + Shader vs; + Shader fs; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + } m_floorMaterial; + + VkDeviceMemory m_bufMem = VK_NULL_HANDLE; + VkBuffer m_uniBuf = VK_NULL_HANDLE; + + VkPipelineCache m_pipelineCache = VK_NULL_HANDLE; + QFuture<void> m_pipelinesFuture; + + QVector3D m_lightPos; + Camera m_cam; + + QMatrix4x4 m_proj; + int m_vpDirty = 0; + QMatrix4x4 m_floorModel; + + bool m_animating; + float m_rotation = 0.0f; + + int m_instCount; + int m_preparedInstCount = 0; + QByteArray m_instData; + VkBuffer m_instBuf = VK_NULL_HANDLE; + VkDeviceMemory m_instBufMem = VK_NULL_HANDLE; + + QFutureWatcher<void> m_frameWatcher; + bool m_framePending; + + QMutex m_guiMutex; +}; + +#endif diff --git a/examples/vulkan/hellovulkancubes/shader.cpp b/examples/vulkan/hellovulkancubes/shader.cpp new file mode 100644 index 0000000000..e1c01c6842 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/shader.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "shader.h" +#include <QtConcurrentRun> +#include <QFile> +#include <QVulkanDeviceFunctions> + +void Shader::load(QVulkanInstance *inst, VkDevice dev, const QString &fn) +{ + reset(); + m_maybeRunning = true; + m_future = QtConcurrent::run([inst, dev, fn]() { + ShaderData sd; + QFile f(fn); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("Failed to open %s", qPrintable(fn)); + return sd; + } + QByteArray blob = f.readAll(); + VkShaderModuleCreateInfo shaderInfo; + memset(&shaderInfo, 0, sizeof(shaderInfo)); + shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + shaderInfo.codeSize = blob.size(); + shaderInfo.pCode = reinterpret_cast<const uint32_t *>(blob.constData()); + VkResult err = inst->deviceFunctions(dev)->vkCreateShaderModule(dev, &shaderInfo, nullptr, &sd.shaderModule); + if (err != VK_SUCCESS) { + qWarning("Failed to create shader module: %d", err); + return sd; + } + return sd; + }); +} + +ShaderData *Shader::data() +{ + if (m_maybeRunning && !m_data.isValid()) + m_data = m_future.result(); + + return &m_data; +} + +void Shader::reset() +{ + *data() = ShaderData(); + m_maybeRunning = false; +} diff --git a/examples/vulkan/hellovulkancubes/shader.h b/examples/vulkan/hellovulkancubes/shader.h new file mode 100644 index 0000000000..265868d2b0 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/shader.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SHADER_H +#define SHADER_H + +#include <QVulkanInstance> +#include <QFuture> + +struct ShaderData +{ + bool isValid() const { return shaderModule != VK_NULL_HANDLE; } + VkShaderModule shaderModule = VK_NULL_HANDLE; +}; + +class Shader +{ +public: + void load(QVulkanInstance *inst, VkDevice dev, const QString &fn); + ShaderData *data(); + bool isValid() { return data()->isValid(); } + void reset(); + +private: + bool m_maybeRunning = false; + QFuture<ShaderData> m_future; + ShaderData m_data; +}; + +#endif diff --git a/examples/vulkan/hellovulkancubes/vulkanwindow.cpp b/examples/vulkan/hellovulkancubes/vulkanwindow.cpp new file mode 100644 index 0000000000..9a4eaf1901 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/vulkanwindow.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "vulkanwindow.h" +#include "renderer.h" +#include <QMouseEvent> +#include <QKeyEvent> + +VulkanWindow::VulkanWindow(bool dbg) + : m_debug(dbg) +{ +} + +QVulkanWindowRenderer *VulkanWindow::createRenderer() +{ + m_renderer = new Renderer(this, 128); + return m_renderer; +} + +void VulkanWindow::addNew() +{ + m_renderer->addNew(); +} + +void VulkanWindow::togglePaused() +{ + m_renderer->setAnimating(!m_renderer->animating()); +} + +void VulkanWindow::meshSwitched(bool enable) +{ + m_renderer->setUseLogo(enable); +} + +void VulkanWindow::mousePressEvent(QMouseEvent *e) +{ + m_pressed = true; + m_lastPos = e->pos(); +} + +void VulkanWindow::mouseReleaseEvent(QMouseEvent *) +{ + m_pressed = false; +} + +void VulkanWindow::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_pressed) + return; + + int dx = e->pos().x() - m_lastPos.x(); + int dy = e->pos().y() - m_lastPos.y(); + + if (dy) + m_renderer->pitch(dy / 10.0f); + + if (dx) + m_renderer->yaw(dx / 10.0f); + + m_lastPos = e->pos(); +} + +void VulkanWindow::keyPressEvent(QKeyEvent *e) +{ + const float amount = e->modifiers().testFlag(Qt::ShiftModifier) ? 1.0f : 0.1f; + switch (e->key()) { + case Qt::Key_W: + m_renderer->walk(amount); + break; + case Qt::Key_S: + m_renderer->walk(-amount); + break; + case Qt::Key_A: + m_renderer->strafe(-amount); + break; + case Qt::Key_D: + m_renderer->strafe(amount); + break; + default: + break; + } +} + +int VulkanWindow::instanceCount() const +{ + return m_renderer->instanceCount(); +} diff --git a/examples/vulkan/hellovulkancubes/vulkanwindow.h b/examples/vulkan/hellovulkancubes/vulkanwindow.h new file mode 100644 index 0000000000..d085c0bde7 --- /dev/null +++ b/examples/vulkan/hellovulkancubes/vulkanwindow.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VULKANWINDOW_H +#define VULKANWINDOW_H + +#include <QVulkanWindow> + +class Renderer; + +class VulkanWindow : public QVulkanWindow +{ +public: + VulkanWindow(bool dbg); + + QVulkanWindowRenderer *createRenderer() override; + + bool isDebugEnabled() const { return m_debug; } + int instanceCount() const; + +public slots: + void addNew(); + void togglePaused(); + void meshSwitched(bool enable); + +private: + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override; + void keyPressEvent(QKeyEvent *) override; + + bool m_debug; + Renderer *m_renderer; + bool m_pressed = false; + QPoint m_lastPos; +}; + +#endif |