diff options
Diffstat (limited to 'examples/widgets/rhi')
23 files changed, 904 insertions, 0 deletions
diff --git a/examples/widgets/rhi/CMakeLists.txt b/examples/widgets/rhi/CMakeLists.txt new file mode 100644 index 0000000000..6734a13c9e --- /dev/null +++ b/examples/widgets/rhi/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +if(NOT TARGET Qt6::Widgets) + return() +endif() +qt_internal_add_example(simplerhiwidget) +qt_internal_add_example(cuberhiwidget) diff --git a/examples/widgets/rhi/cuberhiwidget/CMakeLists.txt b/examples/widgets/rhi/cuberhiwidget/CMakeLists.txt new file mode 100644 index 0000000000..effa9a5a73 --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(cuberhiwidget LANGUAGES CXX) + +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 + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET cuberhiwidget + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/examples/widgets/rhi/cuberhiwidget/cube.h b/examples/widgets/rhi/cuberhiwidget/cube.h new file mode 100644 index 0000000000..9d55eede92 --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/cube.h @@ -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 diff --git a/examples/widgets/rhi/cuberhiwidget/cuberhiwidget.pro b/examples/widgets/rhi/cuberhiwidget/cuberhiwidget.pro new file mode 100644 index 0000000000..94abd29e08 --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/cuberhiwidget.pro @@ -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 diff --git a/examples/widgets/rhi/cuberhiwidget/cuberhiwidget.qrc b/examples/widgets/rhi/cuberhiwidget/cuberhiwidget.qrc new file mode 100644 index 0000000000..33ca81dbde --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/cuberhiwidget.qrc @@ -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> diff --git a/examples/widgets/rhi/cuberhiwidget/examplewidget.cpp b/examples/widgets/rhi/cuberhiwidget/examplewidget.cpp new file mode 100644 index 0000000000..fe39d904dd --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/examplewidget.cpp @@ -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 +} diff --git a/examples/widgets/rhi/cuberhiwidget/examplewidget.h b/examples/widgets/rhi/cuberhiwidget/examplewidget.h new file mode 100644 index 0000000000..9cc554b3fb --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/examplewidget.h @@ -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 diff --git a/examples/widgets/rhi/cuberhiwidget/main.cpp b/examples/widgets/rhi/cuberhiwidget/main.cpp new file mode 100644 index 0000000000..95a29feee5 --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/main.cpp @@ -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(QObject::tr("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(QObject::tr("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(QObject::tr("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(QObject::tr("Using QRhi on ") + apiName); + }); + + QPushButton *btnMakeWindow = new QPushButton(QObject::tr("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(QObject::tr("Make child widget")); + } else { + rhiWidget->setAttribute(Qt::WA_DeleteOnClose, false); + layout->addWidget(rhiWidget); + btnMakeWindow->setText(QObject::tr("Make top-level window")); + } + }); + btnLayout->addWidget(btnMakeWindow); + + QPushButton *btn = new QPushButton(QObject::tr("Grab to image")); + QObject::connect(btn, &QPushButton::clicked, btn, [rhiWidget] { + QImage image = rhiWidget->grabFramebuffer(); + 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(QObject::tr("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(QObject::tr("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(QObject::tr("Flip")); + QObject::connect(cbFlip, &QCheckBox::stateChanged, cbOvberlay, [cbFlip, rhiWidget] { + rhiWidget->setMirrorVertically(cbFlip->isChecked()); + }); + btnLayout->addWidget(cbFlip); + + QCheckBox *cbFixedSize = new QCheckBox(QObject::tr("Use fixed color buffer size")); + btnLayout->addWidget(cbFixedSize); + QSlider *fixedSizeSlider = new QSlider(Qt::Horizontal); + fixedSizeSlider->setMinimum(16); + fixedSizeSlider->setMaximum(512); + btnLayout->addWidget(fixedSizeSlider); + + QObject::connect(cbFixedSize, &QCheckBox::stateChanged, cbFixedSize, [cbFixedSize, fixedSizeSlider, rhiWidget] { + if (cbFixedSize->isChecked()) + rhiWidget->setFixedColorBufferSize(QSize(fixedSizeSlider->value(), fixedSizeSlider->value())); + else + rhiWidget->setFixedColorBufferSize(QSize()); + }); + QObject::connect(fixedSizeSlider, &QSlider::valueChanged, fixedSizeSlider, [fixedSizeSlider, cbFixedSize, rhiWidget] { + if (cbFixedSize->isChecked()) + rhiWidget->setFixedColorBufferSize(QSize(fixedSizeSlider->value(), fixedSizeSlider->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(); +} diff --git a/examples/widgets/rhi/cuberhiwidget/shader_assets/texture.frag.qsb b/examples/widgets/rhi/cuberhiwidget/shader_assets/texture.frag.qsb Binary files differnew file mode 100644 index 0000000000..dc440d8067 --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/shader_assets/texture.frag.qsb diff --git a/examples/widgets/rhi/cuberhiwidget/shader_assets/texture.vert.qsb b/examples/widgets/rhi/cuberhiwidget/shader_assets/texture.vert.qsb Binary files differnew file mode 100644 index 0000000000..84aed7fee2 --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/shader_assets/texture.vert.qsb diff --git a/examples/widgets/rhi/cuberhiwidget/shaders/texture.frag b/examples/widgets/rhi/cuberhiwidget/shaders/texture.frag new file mode 100644 index 0000000000..9a14dc6eeb --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/shaders/texture.frag @@ -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); +} diff --git a/examples/widgets/rhi/cuberhiwidget/shaders/texture.vert b/examples/widgets/rhi/cuberhiwidget/shaders/texture.vert new file mode 100644 index 0000000000..a58a932abc --- /dev/null +++ b/examples/widgets/rhi/cuberhiwidget/shaders/texture.vert @@ -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; +} diff --git a/examples/widgets/rhi/rhi.pro b/examples/widgets/rhi/rhi.pro new file mode 100644 index 0000000000..9248e5e0e3 --- /dev/null +++ b/examples/widgets/rhi/rhi.pro @@ -0,0 +1,4 @@ +requires(qtHaveModule(widgets)) +TEMPLATE = subdirs +SUBDIRS = simplerhiwidget \ + cuberhiwidget diff --git a/examples/widgets/rhi/simplerhiwidget/CMakeLists.txt b/examples/widgets/rhi/simplerhiwidget/CMakeLists.txt new file mode 100644 index 0000000000..cf471f1f8a --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(simplerhiwidget LANGUAGES CXX) + +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 + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET simplerhiwidget + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/examples/widgets/rhi/simplerhiwidget/examplewidget.cpp b/examples/widgets/rhi/simplerhiwidget/examplewidget.cpp new file mode 100644 index 0000000000..7de33059a3 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/examplewidget.cpp @@ -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] diff --git a/examples/widgets/rhi/simplerhiwidget/examplewidget.h b/examples/widgets/rhi/simplerhiwidget/examplewidget.h new file mode 100644 index 0000000000..efd3b90d91 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/examplewidget.h @@ -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 diff --git a/examples/widgets/rhi/simplerhiwidget/main.cpp b/examples/widgets/rhi/simplerhiwidget/main.cpp new file mode 100644 index 0000000000..b9cf848125 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/main.cpp @@ -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] diff --git a/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb Binary files differnew file mode 100644 index 0000000000..32bd2d5953 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb diff --git a/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb Binary files differnew file mode 100644 index 0000000000..bf97035d7e --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb diff --git a/examples/widgets/rhi/simplerhiwidget/shaders/color.frag b/examples/widgets/rhi/simplerhiwidget/shaders/color.frag new file mode 100644 index 0000000000..375587662f --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/shaders/color.frag @@ -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); +} diff --git a/examples/widgets/rhi/simplerhiwidget/shaders/color.vert b/examples/widgets/rhi/simplerhiwidget/shaders/color.vert new file mode 100644 index 0000000000..e876f290e7 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/shaders/color.vert @@ -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; +} diff --git a/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pro b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pro new file mode 100644 index 0000000000..2477d7f368 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pro @@ -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 diff --git a/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc new file mode 100644 index 0000000000..ddc6dfbe5a --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc @@ -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> |