From 53599592e09edd215bfa1eaa7e6f3a9f3fc50ae6 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 22 Mar 2019 09:55:03 +0100 Subject: Introduce the Qt graphics abstraction as private QtGui helpers Comes with backends for Vulkan, Metal, Direct3D 11.1, and OpenGL (ES). All APIs are private for now. Shader conditioning (i.e. generating a QRhiShader in memory or on disk from some shader source code) is done via the tools and APIs provided by qt-labs/qtshadertools. The OpenGL support follows the cross-platform tradition of requiring ES 2.0 only, while optionally using some (ES) 3.x features. It can operate in core profile contexts as well. Task-number: QTBUG-70287 Change-Id: I246f2e36d562e404012c05db2aa72487108aa7cc Reviewed-by: Lars Knoll --- tests/manual/manual.pro | 3 +- .../compressedtexture_bc1.cpp | 198 +++++ .../compressedtexture_bc1.pro | 8 + .../compressedtexture_bc1.qrc | 7 + .../compressedtexture_bc1_subupload.cpp | 213 ++++++ .../compressedtexture_bc1_subupload.pro | 8 + .../compressedtexture_bc1_subupload.qrc | 8 + tests/manual/rhi/cubemap/buildshaders.bat | 2 + tests/manual/rhi/cubemap/c.png | Bin 0 -> 27923 bytes tests/manual/rhi/cubemap/cubemap.cpp | 179 +++++ tests/manual/rhi/cubemap/cubemap.frag | 10 + tests/manual/rhi/cubemap/cubemap.frag.qsb | Bin 0 -> 1311 bytes tests/manual/rhi/cubemap/cubemap.pro | 8 + tests/manual/rhi/cubemap/cubemap.qrc | 7 + tests/manual/rhi/cubemap/cubemap.vert | 15 + tests/manual/rhi/cubemap/cubemap.vert.qsb | Bin 0 -> 1499 bytes tests/manual/rhi/floattexture/floattexture.cpp | 328 ++++++++ tests/manual/rhi/floattexture/floattexture.pro | 8 + tests/manual/rhi/floattexture/floattexture.qrc | 7 + .../hellominimalcrossgfxtriangle.cpp | 554 ++++++++++++++ .../hellominimalcrossgfxtriangle.pro | 8 + .../hellominimalcrossgfxtriangle.qrc | 6 + tests/manual/rhi/mrt/buildshaders.bat | 2 + tests/manual/rhi/mrt/mrt.cpp | 296 ++++++++ tests/manual/rhi/mrt/mrt.frag | 22 + tests/manual/rhi/mrt/mrt.frag.qsb | Bin 0 -> 1934 bytes tests/manual/rhi/mrt/mrt.pro | 8 + tests/manual/rhi/mrt/mrt.qrc | 8 + tests/manual/rhi/mrt/mrt.vert | 19 + tests/manual/rhi/mrt/mrt.vert.qsb | Bin 0 -> 1633 bytes .../rhi/msaarenderbuffer/msaarenderbuffer.cpp | 259 +++++++ .../rhi/msaarenderbuffer/msaarenderbuffer.pro | 8 + .../rhi/msaarenderbuffer/msaarenderbuffer.qrc | 8 + tests/manual/rhi/msaatexture/msaatexture.cpp | 329 ++++++++ tests/manual/rhi/msaatexture/msaatexture.pro | 8 + tests/manual/rhi/msaatexture/msaatexture.qrc | 9 + tests/manual/rhi/multiwindow/multiwindow.cpp | 644 ++++++++++++++++ tests/manual/rhi/multiwindow/multiwindow.pro | 8 + tests/manual/rhi/multiwindow/multiwindow.qrc | 6 + .../multiwindow_threaded/multiwindow_threaded.cpp | 829 +++++++++++++++++++++ .../multiwindow_threaded/multiwindow_threaded.pro | 12 + .../multiwindow_threaded/multiwindow_threaded.qrc | 7 + tests/manual/rhi/multiwindow_threaded/window.cpp | 142 ++++ tests/manual/rhi/multiwindow_threaded/window.h | 86 +++ tests/manual/rhi/offscreen/offscreen.cpp | 366 +++++++++ tests/manual/rhi/offscreen/offscreen.pro | 9 + tests/manual/rhi/offscreen/offscreen.qrc | 6 + tests/manual/rhi/qrhiprof/qrhiprof.cpp | 671 +++++++++++++++++ tests/manual/rhi/qrhiprof/qrhiprof.pro | 6 + tests/manual/rhi/rhi.pro | 22 + tests/manual/rhi/shadowmap/buildshaders.bat | 4 + tests/manual/rhi/shadowmap/buildshaders.sh | 4 + tests/manual/rhi/shadowmap/main.frag | 30 + tests/manual/rhi/shadowmap/main.frag.qsb | Bin 0 -> 2489 bytes tests/manual/rhi/shadowmap/main.vert | 20 + tests/manual/rhi/shadowmap/main.vert.qsb | Bin 0 -> 1595 bytes tests/manual/rhi/shadowmap/shadowmap.cpp | 304 ++++++++ tests/manual/rhi/shadowmap/shadowmap.frag | 5 + tests/manual/rhi/shadowmap/shadowmap.frag.qsb | Bin 0 -> 408 bytes tests/manual/rhi/shadowmap/shadowmap.pro | 8 + tests/manual/rhi/shadowmap/shadowmap.qrc | 8 + tests/manual/rhi/shadowmap/shadowmap.vert | 14 + tests/manual/rhi/shadowmap/shadowmap.vert.qsb | Bin 0 -> 1215 bytes .../rhi/shared/OpenfootageNET_fieldairport-512.hdr | Bin 0 -> 385303 bytes tests/manual/rhi/shared/buildshaders.bat | 5 + tests/manual/rhi/shared/bwqt224_64.png | Bin 0 -> 6339 bytes tests/manual/rhi/shared/bwqt224_64_nomips.dds | Bin 0 -> 9712 bytes tests/manual/rhi/shared/color.frag | 15 + tests/manual/rhi/shared/color.frag.qsb | Bin 0 -> 1483 bytes tests/manual/rhi/shared/color.vert | 19 + tests/manual/rhi/shared/color.vert.qsb | Bin 0 -> 1648 bytes tests/manual/rhi/shared/cube.h | 119 +++ tests/manual/rhi/shared/dds_bc1.h | 137 ++++ tests/manual/rhi/shared/examplefw.h | 548 ++++++++++++++ tests/manual/rhi/shared/qt256.png | Bin 0 -> 6208 bytes tests/manual/rhi/shared/qt256_bc1_9mips.dds | Bin 0 -> 43832 bytes tests/manual/rhi/shared/texture.frag | 18 + tests/manual/rhi/shared/texture.frag.qsb | Bin 0 -> 1710 bytes tests/manual/rhi/shared/texture.vert | 21 + tests/manual/rhi/shared/texture.vert.qsb | Bin 0 -> 1897 bytes tests/manual/rhi/shared/texture_ms4.frag | 20 + tests/manual/rhi/shared/texture_ms4.frag.qsb | Bin 0 -> 1847 bytes tests/manual/rhi/texuploads/texuploads.cpp | 314 ++++++++ tests/manual/rhi/texuploads/texuploads.pro | 8 + tests/manual/rhi/texuploads/texuploads.qrc | 7 + tests/manual/rhi/triquadcube/quadrenderer.cpp | 148 ++++ tests/manual/rhi/triquadcube/quadrenderer.h | 87 +++ .../rhi/triquadcube/texturedcuberenderer.cpp | 222 ++++++ .../manual/rhi/triquadcube/texturedcuberenderer.h | 86 +++ .../rhi/triquadcube/triangleoncuberenderer.cpp | 292 ++++++++ .../rhi/triquadcube/triangleoncuberenderer.h | 95 +++ tests/manual/rhi/triquadcube/trianglerenderer.cpp | 202 +++++ tests/manual/rhi/triquadcube/trianglerenderer.h | 93 +++ tests/manual/rhi/triquadcube/triquadcube.cpp | 271 +++++++ tests/manual/rhi/triquadcube/triquadcube.pro | 18 + tests/manual/rhi/triquadcube/triquadcube.qrc | 9 + 96 files changed, 8517 insertions(+), 1 deletion(-) create mode 100644 tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.cpp create mode 100644 tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.pro create mode 100644 tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.qrc create mode 100644 tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.cpp create mode 100644 tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.pro create mode 100644 tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.qrc create mode 100644 tests/manual/rhi/cubemap/buildshaders.bat create mode 100644 tests/manual/rhi/cubemap/c.png create mode 100644 tests/manual/rhi/cubemap/cubemap.cpp create mode 100644 tests/manual/rhi/cubemap/cubemap.frag create mode 100644 tests/manual/rhi/cubemap/cubemap.frag.qsb create mode 100644 tests/manual/rhi/cubemap/cubemap.pro create mode 100644 tests/manual/rhi/cubemap/cubemap.qrc create mode 100644 tests/manual/rhi/cubemap/cubemap.vert create mode 100644 tests/manual/rhi/cubemap/cubemap.vert.qsb create mode 100644 tests/manual/rhi/floattexture/floattexture.cpp create mode 100644 tests/manual/rhi/floattexture/floattexture.pro create mode 100644 tests/manual/rhi/floattexture/floattexture.qrc create mode 100644 tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.cpp create mode 100644 tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.pro create mode 100644 tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.qrc create mode 100644 tests/manual/rhi/mrt/buildshaders.bat create mode 100644 tests/manual/rhi/mrt/mrt.cpp create mode 100644 tests/manual/rhi/mrt/mrt.frag create mode 100644 tests/manual/rhi/mrt/mrt.frag.qsb create mode 100644 tests/manual/rhi/mrt/mrt.pro create mode 100644 tests/manual/rhi/mrt/mrt.qrc create mode 100644 tests/manual/rhi/mrt/mrt.vert create mode 100644 tests/manual/rhi/mrt/mrt.vert.qsb create mode 100644 tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.cpp create mode 100644 tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.pro create mode 100644 tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.qrc create mode 100644 tests/manual/rhi/msaatexture/msaatexture.cpp create mode 100644 tests/manual/rhi/msaatexture/msaatexture.pro create mode 100644 tests/manual/rhi/msaatexture/msaatexture.qrc create mode 100644 tests/manual/rhi/multiwindow/multiwindow.cpp create mode 100644 tests/manual/rhi/multiwindow/multiwindow.pro create mode 100644 tests/manual/rhi/multiwindow/multiwindow.qrc create mode 100644 tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp create mode 100644 tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.pro create mode 100644 tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.qrc create mode 100644 tests/manual/rhi/multiwindow_threaded/window.cpp create mode 100644 tests/manual/rhi/multiwindow_threaded/window.h create mode 100644 tests/manual/rhi/offscreen/offscreen.cpp create mode 100644 tests/manual/rhi/offscreen/offscreen.pro create mode 100644 tests/manual/rhi/offscreen/offscreen.qrc create mode 100644 tests/manual/rhi/qrhiprof/qrhiprof.cpp create mode 100644 tests/manual/rhi/qrhiprof/qrhiprof.pro create mode 100644 tests/manual/rhi/rhi.pro create mode 100644 tests/manual/rhi/shadowmap/buildshaders.bat create mode 100755 tests/manual/rhi/shadowmap/buildshaders.sh create mode 100644 tests/manual/rhi/shadowmap/main.frag create mode 100644 tests/manual/rhi/shadowmap/main.frag.qsb create mode 100644 tests/manual/rhi/shadowmap/main.vert create mode 100644 tests/manual/rhi/shadowmap/main.vert.qsb create mode 100644 tests/manual/rhi/shadowmap/shadowmap.cpp create mode 100644 tests/manual/rhi/shadowmap/shadowmap.frag create mode 100644 tests/manual/rhi/shadowmap/shadowmap.frag.qsb create mode 100644 tests/manual/rhi/shadowmap/shadowmap.pro create mode 100644 tests/manual/rhi/shadowmap/shadowmap.qrc create mode 100644 tests/manual/rhi/shadowmap/shadowmap.vert create mode 100644 tests/manual/rhi/shadowmap/shadowmap.vert.qsb create mode 100644 tests/manual/rhi/shared/OpenfootageNET_fieldairport-512.hdr create mode 100644 tests/manual/rhi/shared/buildshaders.bat create mode 100644 tests/manual/rhi/shared/bwqt224_64.png create mode 100644 tests/manual/rhi/shared/bwqt224_64_nomips.dds create mode 100644 tests/manual/rhi/shared/color.frag create mode 100644 tests/manual/rhi/shared/color.frag.qsb create mode 100644 tests/manual/rhi/shared/color.vert create mode 100644 tests/manual/rhi/shared/color.vert.qsb create mode 100644 tests/manual/rhi/shared/cube.h create mode 100644 tests/manual/rhi/shared/dds_bc1.h create mode 100644 tests/manual/rhi/shared/examplefw.h create mode 100644 tests/manual/rhi/shared/qt256.png create mode 100644 tests/manual/rhi/shared/qt256_bc1_9mips.dds create mode 100644 tests/manual/rhi/shared/texture.frag create mode 100644 tests/manual/rhi/shared/texture.frag.qsb create mode 100644 tests/manual/rhi/shared/texture.vert create mode 100644 tests/manual/rhi/shared/texture.vert.qsb create mode 100644 tests/manual/rhi/shared/texture_ms4.frag create mode 100644 tests/manual/rhi/shared/texture_ms4.frag.qsb create mode 100644 tests/manual/rhi/texuploads/texuploads.cpp create mode 100644 tests/manual/rhi/texuploads/texuploads.pro create mode 100644 tests/manual/rhi/texuploads/texuploads.qrc create mode 100644 tests/manual/rhi/triquadcube/quadrenderer.cpp create mode 100644 tests/manual/rhi/triquadcube/quadrenderer.h create mode 100644 tests/manual/rhi/triquadcube/texturedcuberenderer.cpp create mode 100644 tests/manual/rhi/triquadcube/texturedcuberenderer.h create mode 100644 tests/manual/rhi/triquadcube/triangleoncuberenderer.cpp create mode 100644 tests/manual/rhi/triquadcube/triangleoncuberenderer.h create mode 100644 tests/manual/rhi/triquadcube/trianglerenderer.cpp create mode 100644 tests/manual/rhi/triquadcube/trianglerenderer.h create mode 100644 tests/manual/rhi/triquadcube/triquadcube.cpp create mode 100644 tests/manual/rhi/triquadcube/triquadcube.pro create mode 100644 tests/manual/rhi/triquadcube/triquadcube.qrc (limited to 'tests/manual') diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index ab00a5ef60..63c8eb6538 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -53,7 +53,8 @@ shortcuts \ dialogs \ windowtransparency \ unc \ -qtabbar +qtabbar \ +rhi !qtConfig(openssl): SUBDIRS -= qssloptions diff --git a/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.cpp b/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.cpp new file mode 100644 index 0000000000..d50e183096 --- /dev/null +++ b/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" +#include "../shared/cube.h" +#include "../shared/dds_bc1.h" + +struct { + QRhiBuffer *vbuf = nullptr; + bool vbufReady = false; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + + float rotation = 0; + + QByteArrayList compressedData; +} d; + +void Window::customInit() +{ + if (!m_r->isTextureFormatSupported(QRhiTexture::BC1)) + qFatal("This backend does not support BC1"); + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + d.vbuf->build(); + d.vbufReady = false; + + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.ubuf->build(); + + QSize imageSize; + d.compressedData = loadBC1(QLatin1String(":/qt256_bc1_9mips.dds"), &imageSize); + qDebug() << d.compressedData.count() << imageSize << m_r->mipLevelsForSize(imageSize); + + d.tex = m_r->newTexture(QRhiTexture::BC1, imageSize, 1, QRhiTexture::MipMapped); + d.tex->build(); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.sampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + + d.ps->setDepthTest(true); + d.ps->setDepthWrite(true); + d.ps->setDepthOp(QRhiGraphicsPipeline::Less); + + d.ps->setCullMode(QRhiGraphicsPipeline::Back); + d.ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + const QShader vs = getShader(QLatin1String(":/texture.vert.qsb")); + if (!vs.isValid()) + qFatal("Failed to load shader pack (vertex)"); + const QShader fs = getShader(QLatin1String(":/texture.frag.qsb")); + if (!fs.isValid()) + qFatal("Failed to load shader pack (fragment)"); + + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + + d.ps->build(); +} + +void Window::customRelease() +{ + delete d.ps; + d.ps = nullptr; + + delete d.srb; + d.srb = nullptr; + + delete d.ubuf; + d.ubuf = nullptr; + + delete d.vbuf; + d.vbuf = nullptr; + + delete d.sampler; + d.sampler = nullptr; + + delete d.tex; + d.tex = nullptr; +} + +void Window::customRender() +{ + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (!d.vbufReady) { + d.vbufReady = true; + u->uploadStaticBuffer(d.vbuf, cube); + qint32 flip = 0; + u->updateDynamicBuffer(d.ubuf, 64, 4, &flip); + } + if (!d.compressedData.isEmpty()) { + QRhiTextureUploadDescription desc; + for (int i = 0; i < d.compressedData.count(); ++i) { + QRhiTextureSubresourceUploadDescription image(d.compressedData[i].constData(), d.compressedData[i].size()); + desc.append({ 0, i, image }); + } + u->uploadTexture(d.tex, desc); + d.compressedData.clear(); + } + d.rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.scale(0.5f); + mvp.rotate(d.rotation, 0, 1, 0); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { d.vbuf, 0 }, + { d.vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); + + cb->endPass(); +} diff --git a/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.pro b/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.pro new file mode 100644 index 0000000000..d185aa6b9a --- /dev/null +++ b/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + compressedtexture_bc1.cpp + +RESOURCES = compressedtexture_bc1.qrc diff --git a/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.qrc b/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.qrc new file mode 100644 index 0000000000..a83259a7d3 --- /dev/null +++ b/tests/manual/rhi/compressedtexture_bc1/compressedtexture_bc1.qrc @@ -0,0 +1,7 @@ + + + ../shared/qt256_bc1_9mips.dds + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + + diff --git a/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.cpp b/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.cpp new file mode 100644 index 0000000000..7ae5dd0d73 --- /dev/null +++ b/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" +#include "../shared/cube.h" +#include "../shared/dds_bc1.h" + +struct { + QRhiBuffer *vbuf = nullptr; + bool vbufReady = false; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + + float rotation = 0; + + QByteArrayList compressedData; + QByteArrayList compressedData2; +} d; + +void Window::customInit() +{ + if (!m_r->isTextureFormatSupported(QRhiTexture::BC1)) + qFatal("This backend does not support BC1"); + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + d.vbuf->build(); + d.vbufReady = false; + + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.ubuf->build(); + + QSize imageSize; + d.compressedData = loadBC1(QLatin1String(":/qt256_bc1_9mips.dds"), &imageSize); + Q_ASSERT(imageSize == QSize(256, 256)); + + d.tex = m_r->newTexture(QRhiTexture::BC1, imageSize); + d.tex->build(); + + d.compressedData2 = loadBC1(QLatin1String(":/bwqt224_64_nomips.dds"), &imageSize); + Q_ASSERT(imageSize == QSize(224, 64)); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, // no mipmapping here + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.sampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + + d.ps->setDepthTest(true); + d.ps->setDepthWrite(true); + d.ps->setDepthOp(QRhiGraphicsPipeline::Less); + + d.ps->setCullMode(QRhiGraphicsPipeline::Back); + d.ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + const QShader vs = getShader(QLatin1String(":/texture.vert.qsb")); + if (!vs.isValid()) + qFatal("Failed to load shader pack (vertex)"); + const QShader fs = getShader(QLatin1String(":/texture.frag.qsb")); + if (!fs.isValid()) + qFatal("Failed to load shader pack (fragment)"); + + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + + d.ps->build(); +} + +void Window::customRelease() +{ + delete d.ps; + d.ps = nullptr; + + delete d.srb; + d.srb = nullptr; + + delete d.ubuf; + d.ubuf = nullptr; + + delete d.vbuf; + d.vbuf = nullptr; + + delete d.sampler; + d.sampler = nullptr; + + delete d.tex; + d.tex = nullptr; +} + +void Window::customRender() +{ + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (!d.vbufReady) { + d.vbufReady = true; + u->uploadStaticBuffer(d.vbuf, cube); + qint32 flip = 0; + u->updateDynamicBuffer(d.ubuf, 64, 4, &flip); + } + if (!d.compressedData.isEmpty()) { + { + QRhiTextureUploadDescription desc({ 0, 0, { d.compressedData[0].constData(), d.compressedData[0].size() } }); + u->uploadTexture(d.tex, desc); + d.compressedData.clear(); + } + + // now exercise uploading a smaller compressed image into the same texture + { + QRhiTextureSubresourceUploadDescription image(d.compressedData2[0].constData(), d.compressedData2[0].size()); + // positions and sizes must be multiples of 4 here (for BC1) + image.setDestinationTopLeft(QPoint(16, 32)); + // the image is smaller than the subresource size (224x64 vs + // 256x256) so the size must be specified manually + image.setSourceSize(QSize(224, 64)); + QRhiTextureUploadDescription desc({ 0, 0, image }); + u->uploadTexture(d.tex, desc); + d.compressedData2.clear(); + } + } + d.rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.scale(0.5f); + mvp.rotate(d.rotation, 0, 1, 0); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { d.vbuf, 0 }, + { d.vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); + + cb->endPass(); +} diff --git a/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.pro b/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.pro new file mode 100644 index 0000000000..a1184fc754 --- /dev/null +++ b/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + compressedtexture_bc1_subupload.cpp + +RESOURCES = compressedtexture_bc1_subupload.qrc diff --git a/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.qrc b/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.qrc new file mode 100644 index 0000000000..859a0a79f6 --- /dev/null +++ b/tests/manual/rhi/compressedtexture_bc1_subupload/compressedtexture_bc1_subupload.qrc @@ -0,0 +1,8 @@ + + + ../shared/qt256_bc1_9mips.dds + ../shared/bwqt224_64_nomips.dds + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + + diff --git a/tests/manual/rhi/cubemap/buildshaders.bat b/tests/manual/rhi/cubemap/buildshaders.bat new file mode 100644 index 0000000000..ebf673875d --- /dev/null +++ b/tests/manual/rhi/cubemap/buildshaders.bat @@ -0,0 +1,2 @@ +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c cubemap.vert -o cubemap.vert.qsb +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c cubemap.frag -o cubemap.frag.qsb diff --git a/tests/manual/rhi/cubemap/c.png b/tests/manual/rhi/cubemap/c.png new file mode 100644 index 0000000000..7b00455e53 Binary files /dev/null and b/tests/manual/rhi/cubemap/c.png differ diff --git a/tests/manual/rhi/cubemap/cubemap.cpp b/tests/manual/rhi/cubemap/cubemap.cpp new file mode 100644 index 0000000000..0420a38cbd --- /dev/null +++ b/tests/manual/rhi/cubemap/cubemap.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" +#include "../shared/cube.h" + +struct { + QVector releasePool; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; +} d; + +void Window::customInit() +{ + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + d.vbuf->build(); + d.releasePool << d.vbuf; + + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64); + d.ubuf->build(); + d.releasePool << d.ubuf; + + const QSize cubeMapSize(512, 512); + d.tex = m_r->newTexture(QRhiTexture::RGBA8, cubeMapSize, 1, QRhiTexture::CubeMap); + d.releasePool << d.tex; + d.tex->build(); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, cube); + + QImage img = QImage(":/c.png").mirrored().convertToFormat(QImage::Format_RGBA8888); + // just use the same image for all faces for now + QRhiTextureSubresourceUploadDescription subresDesc(img); + QRhiTextureUploadDescription desc({ + { 0, 0, subresDesc }, // +X + { 1, 0, subresDesc }, // -X + { 2, 0, subresDesc }, // +Y + { 3, 0, subresDesc }, // -Y + { 4, 0, subresDesc }, // +Z + { 5, 0, subresDesc } // -Z + }); + d.initialUpdates->uploadTexture(d.tex, desc); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::Repeat, QRhiSampler::Repeat); + d.releasePool << d.sampler; + d.sampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.releasePool << d.srb; + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + + d.ps->setDepthTest(true); + d.ps->setDepthWrite(true); + d.ps->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); + + d.ps->setCullMode(QRhiGraphicsPipeline::Front); // we are inside the cube so cull front, not back + d.ps->setFrontFace(QRhiGraphicsPipeline::CCW); // front is ccw in the cube data + + QShader vs = getShader(QLatin1String(":/cubemap.vert.qsb")); + Q_ASSERT(vs.isValid()); + QShader fs = getShader(QLatin1String(":/cubemap.frag.qsb")); + Q_ASSERT(fs.isValid()); + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 } + }); + + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + + d.ps->build(); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +void Window::customRender() +{ + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + QMatrix4x4 mvp = m_r->clipSpaceCorrMatrix(); + mvp.perspective(90.0f, outputSizeInPixels.width() / (float) outputSizeInPixels.height(), 0.01f, 1000.0f); + // cube vertices go from -1..1 + mvp.scale(10); + static float rx = 0; + mvp.rotate(rx, 1, 0, 0); + rx += 0.5f; + // no translation + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + cb->setGraphicsPipeline(d.ps); + cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(36); + cb->endPass(); +} diff --git a/tests/manual/rhi/cubemap/cubemap.frag b/tests/manual/rhi/cubemap/cubemap.frag new file mode 100644 index 0000000000..13a365ed0c --- /dev/null +++ b/tests/manual/rhi/cubemap/cubemap.frag @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec3 v_coord; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform samplerCube tex; + +void main() +{ + fragColor = vec4(texture(tex, v_coord).rgb, 1.0); +} diff --git a/tests/manual/rhi/cubemap/cubemap.frag.qsb b/tests/manual/rhi/cubemap/cubemap.frag.qsb new file mode 100644 index 0000000000..d7cc025554 Binary files /dev/null and b/tests/manual/rhi/cubemap/cubemap.frag.qsb differ diff --git a/tests/manual/rhi/cubemap/cubemap.pro b/tests/manual/rhi/cubemap/cubemap.pro new file mode 100644 index 0000000000..61910ffe20 --- /dev/null +++ b/tests/manual/rhi/cubemap/cubemap.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + cubemap.cpp + +RESOURCES = cubemap.qrc diff --git a/tests/manual/rhi/cubemap/cubemap.qrc b/tests/manual/rhi/cubemap/cubemap.qrc new file mode 100644 index 0000000000..bacd15446c --- /dev/null +++ b/tests/manual/rhi/cubemap/cubemap.qrc @@ -0,0 +1,7 @@ + + + cubemap.vert.qsb + cubemap.frag.qsb + c.png + + diff --git a/tests/manual/rhi/cubemap/cubemap.vert b/tests/manual/rhi/cubemap/cubemap.vert new file mode 100644 index 0000000000..65216dd6b0 --- /dev/null +++ b/tests/manual/rhi/cubemap/cubemap.vert @@ -0,0 +1,15 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 0) out vec3 v_coord; +layout(std140, binding = 0) uniform buf { + mat4 mvp; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + v_coord = position.xyz; + gl_Position = ubuf.mvp * position; +} diff --git a/tests/manual/rhi/cubemap/cubemap.vert.qsb b/tests/manual/rhi/cubemap/cubemap.vert.qsb new file mode 100644 index 0000000000..fae136337c Binary files /dev/null and b/tests/manual/rhi/cubemap/cubemap.vert.qsb differ diff --git a/tests/manual/rhi/floattexture/floattexture.cpp b/tests/manual/rhi/floattexture/floattexture.cpp new file mode 100644 index 0000000000..b62ef290f7 --- /dev/null +++ b/tests/manual/rhi/floattexture/floattexture.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" +#include + +QByteArray loadHdr(const QString &fn, QSize *size) +{ + QFile f(fn); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("Failed to open %s", qPrintable(fn)); + return QByteArray(); + } + + char sig[256]; + f.read(sig, 11); + if (strncmp(sig, "#?RADIANCE\n", 11)) + return QByteArray(); + + QByteArray buf = f.readAll(); + const char *p = buf.constData(); + const char *pEnd = p + buf.size(); + + // Process lines until the empty one. + QByteArray line; + while (p < pEnd) { + char c = *p++; + if (c == '\n') { + if (line.isEmpty()) + break; + if (line.startsWith(QByteArrayLiteral("FORMAT="))) { + const QByteArray format = line.mid(7).trimmed(); + if (format != QByteArrayLiteral("32-bit_rle_rgbe")) { + qWarning("HDR format '%s' is not supported", format.constData()); + return QByteArray(); + } + } + line.clear(); + } else { + line.append(c); + } + } + if (p == pEnd) { + qWarning("Malformed HDR image data at property strings"); + return QByteArray(); + } + + // Get the resolution string. + while (p < pEnd) { + char c = *p++; + if (c == '\n') + break; + line.append(c); + } + if (p == pEnd) { + qWarning("Malformed HDR image data at resolution string"); + return QByteArray(); + } + + int w = 0, h = 0; + // We only care about the standard orientation. + if (!sscanf(line.constData(), "-Y %d +X %d", &h, &w)) { + qWarning("Unsupported HDR resolution string '%s'", line.constData()); + return QByteArray(); + } + if (w <= 0 || h <= 0) { + qWarning("Invalid HDR resolution"); + return QByteArray(); + } + + // output is RGBA32F + const int blockSize = 4 * sizeof(float); + QByteArray data; + data.resize(w * h * blockSize); + + typedef unsigned char RGBE[4]; + RGBE *scanline = new RGBE[w]; + + for (int y = 0; y < h; ++y) { + if (pEnd - p < 4) { + qWarning("Unexpected end of HDR data"); + delete[] scanline; + return QByteArray(); + } + + scanline[0][0] = *p++; + scanline[0][1] = *p++; + scanline[0][2] = *p++; + scanline[0][3] = *p++; + + if (scanline[0][0] == 2 && scanline[0][1] == 2 && scanline[0][2] < 128) { + // new rle, the first pixel was a dummy + for (int channel = 0; channel < 4; ++channel) { + for (int x = 0; x < w && p < pEnd; ) { + unsigned char c = *p++; + if (c > 128) { // run + if (p < pEnd) { + int repCount = c & 127; + c = *p++; + while (repCount--) + scanline[x++][channel] = c; + } + } else { // not a run + while (c-- && p < pEnd) + scanline[x++][channel] = *p++; + } + } + } + } else { + // old rle + scanline[0][0] = 2; + int bitshift = 0; + int x = 1; + while (x < w && pEnd - p >= 4) { + scanline[x][0] = *p++; + scanline[x][1] = *p++; + scanline[x][2] = *p++; + scanline[x][3] = *p++; + + if (scanline[x][0] == 1 && scanline[x][1] == 1 && scanline[x][2] == 1) { // run + int repCount = scanline[x][3] << bitshift; + while (repCount--) { + memcpy(scanline[x], scanline[x - 1], 4); + ++x; + } + bitshift += 8; + } else { // not a run + ++x; + bitshift = 0; + } + } + } + + // adjust for -Y orientation + float *fp = reinterpret_cast(data.data() + (h - 1 - y) * blockSize * w); + for (int x = 0; x < w; ++x) { + float d = qPow(2.0f, float(scanline[x][3]) - 128.0f); + // r, g, b, a + *fp++ = scanline[x][0] / 256.0f * d; + *fp++ = scanline[x][1] / 256.0f * d; + *fp++ = scanline[x][2] / 256.0f * d; + *fp++ = 1.0f; + } + } + + delete[] scanline; + + if (size) + *size = QSize(w, h); + + return data; +} + +static float vertexData[] = +{ // Y up, CCW + -0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 0.0f +}; + +static quint16 indexData[] = +{ + 0, 1, 2, 0, 2, 3 +}; + +struct { + QVector releasePool; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + QMatrix4x4 winProj; +} d; + +void Window::customInit() +{ + if (!m_r->isTextureFormatSupported(QRhiTexture::RGBA32F)) + qWarning("RGBA32F texture format is not supported"); + + QSize size; + QByteArray floatData = loadHdr(QLatin1String(":/OpenfootageNET_fieldairport-512.hdr"), &size); + Q_ASSERT(!floatData.isEmpty()); + qDebug() << size; + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); + d.vbuf->build(); + d.releasePool << d.vbuf; + + d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indexData)); + d.ibuf->build(); + d.releasePool << d.ibuf; + + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.ubuf->build(); + d.releasePool << d.ubuf; + + d.tex = m_r->newTexture(QRhiTexture::RGBA32F, size); + d.releasePool << d.tex; + d.tex->build(); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.sampler; + d.sampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.releasePool << d.srb; + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 4 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + d.ps->build(); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, vertexData); + d.initialUpdates->uploadStaticBuffer(d.ibuf, indexData); + + qint32 flip = 1; + d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &flip); + + QRhiTextureUploadDescription desc({ 0, 0, { floatData.constData(), floatData.size() } }); + d.initialUpdates->uploadTexture(d.tex, desc); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +void Window::customRender() +{ + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + if (d.winProj != m_proj) { + d.winProj = m_proj; + QMatrix4x4 mvp = m_proj; + mvp.scale(2.5f); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + } + + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16); + cb->drawIndexed(6); + cb->endPass(); +} diff --git a/tests/manual/rhi/floattexture/floattexture.pro b/tests/manual/rhi/floattexture/floattexture.pro new file mode 100644 index 0000000000..e566da2b94 --- /dev/null +++ b/tests/manual/rhi/floattexture/floattexture.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + floattexture.cpp + +RESOURCES = floattexture.qrc diff --git a/tests/manual/rhi/floattexture/floattexture.qrc b/tests/manual/rhi/floattexture/floattexture.qrc new file mode 100644 index 0000000000..fefb56b910 --- /dev/null +++ b/tests/manual/rhi/floattexture/floattexture.qrc @@ -0,0 +1,7 @@ + + + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + ../shared/OpenfootageNET_fieldairport-512.hdr + + diff --git a/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.cpp b/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.cpp new file mode 100644 index 0000000000..a24b980173 --- /dev/null +++ b/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.cpp @@ -0,0 +1,554 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +// This is a compact, minimal, single-file demo of deciding the backend at +// runtime while using the exact same shaders and rendering code without any +// branching whatsoever once the QWindow is up and the RHI is initialized. + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#ifndef QT_NO_OPENGL +#include +#include +#endif + +#if QT_CONFIG(vulkan) +#include +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_DARWIN +#include +#endif + +static float vertexData[] = { + // Y up (note clipSpaceCorrMatrix in m_proj), CCW + 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, +}; + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +enum GraphicsApi +{ + OpenGL, + Vulkan, + D3D11, + Metal, + Null +}; + +static GraphicsApi graphicsApi; + +static QString graphicsApiName() +{ + switch (graphicsApi) { + case OpenGL: + return QLatin1String("OpenGL 2.x"); + case Vulkan: + return QLatin1String("Vulkan"); + case D3D11: + return QLatin1String("Direct3D 11"); + case Metal: + return QLatin1String("Metal"); + case Null: + return QLatin1String("Null"); + default: + break; + } + return QString(); +} + +class Window : public QWindow +{ +public: + Window(); + ~Window(); + + void init(); + void releaseResources(); + void resizeSwapChain(); + void releaseSwapChain(); + void render(); + + void exposeEvent(QExposeEvent *) override; + bool event(QEvent *) override; + +private: + bool m_running = false; + bool m_notExposed = false; + bool m_newlyExposed = false; + + QRhi *m_r = nullptr; + bool m_hasSwapChain = false; + QRhiSwapChain *m_sc = nullptr; + QRhiRenderBuffer *m_ds = nullptr; + QRhiRenderPassDescriptor *m_rp = nullptr; + QRhiBuffer *m_vbuf = nullptr; + bool m_vbufReady = false; + QRhiBuffer *m_ubuf = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + QVector releasePool; + + QMatrix4x4 m_proj; + float m_rotation = 0; + float m_opacity = 1; + int m_opacityDir = -1; + + QElapsedTimer m_timer; + qint64 m_elapsedMs; + int m_elapsedCount; + +#ifndef QT_NO_OPENGL + QOffscreenSurface *m_fallbackSurface = nullptr; +#endif +}; + +Window::Window() +{ + // Tell the platform plugin what we want. + switch (graphicsApi) { + case OpenGL: +#if QT_CONFIG(opengl) + setSurfaceType(OpenGLSurface); + setFormat(QRhiGles2InitParams::adjustedFormat()); +#endif + break; + case Vulkan: + setSurfaceType(VulkanSurface); + break; + case D3D11: + setSurfaceType(OpenGLSurface); // not a typo + break; + case Metal: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + setSurfaceType(MetalSurface); +#endif + break; + default: + break; + } +} + +Window::~Window() +{ + releaseResources(); +} + +void Window::exposeEvent(QExposeEvent *) +{ + // initialize and start rendering when the window becomes usable for graphics purposes + if (isExposed() && !m_running) { + m_running = true; + init(); + resizeSwapChain(); + } + + // stop pushing frames when not exposed (or size is 0) + if ((!isExposed() || (m_hasSwapChain && m_sc->surfacePixelSize().isEmpty())) && m_running) + m_notExposed = true; + + // continue when exposed again and the surface has a valid size. + // note that the surface size can be (0, 0) even though size() reports a valid one... + if (isExposed() && m_running && m_notExposed && !m_sc->surfacePixelSize().isEmpty()) { + m_notExposed = false; + m_newlyExposed = true; + } + + // always render a frame on exposeEvent() (when exposed) in order to update + // immediately on window resize. + if (isExposed() && !m_sc->surfacePixelSize().isEmpty()) + render(); +} + +bool Window::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::UpdateRequest: + render(); + break; + + case QEvent::PlatformSurface: + // this is the proper time to tear down the swapchain (while the native window and surface are still around) + if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) + releaseSwapChain(); + break; + + default: + break; + } + + return QWindow::event(e); +} + +void Window::init() +{ + if (graphicsApi == Null) { + QRhiNullInitParams params; + m_r = QRhi::create(QRhi::Null, ¶ms); + } + +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) { + m_fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); + QRhiGles2InitParams params; + params.fallbackSurface = m_fallbackSurface; + params.window = this; + m_r = QRhi::create(QRhi::OpenGLES2, ¶ms); + } +#endif + +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) { + QRhiVulkanInitParams params; + params.inst = vulkanInstance(); + params.window = this; + m_r = QRhi::create(QRhi::Vulkan, ¶ms); + } +#endif + +#ifdef Q_OS_WIN + if (graphicsApi == D3D11) { + QRhiD3D11InitParams params; + m_r = QRhi::create(QRhi::D3D11, ¶ms); + } +#endif + +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + QRhiMetalInitParams params; + m_r = QRhi::create(QRhi::Metal, ¶ms); + } +#endif + + if (!m_r) + qFatal("Failed to create RHI backend"); + + // now onto the backend-independent init + + m_sc = m_r->newSwapChain(); + // allow depth-stencil, although we do not actually enable depth test/write for the triangle + m_ds = m_r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), // no need to set the size yet + 1, + QRhiRenderBuffer::UsedWithSwapChainOnly); + releasePool << m_ds; + m_sc->setWindow(this); + m_sc->setDepthStencil(m_ds); + m_rp = m_sc->newCompatibleRenderPassDescriptor(); + releasePool << m_rp; + m_sc->setRenderPassDescriptor(m_rp); + + m_vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); + releasePool << m_vbuf; + m_vbuf->build(); + m_vbufReady = false; + + m_ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + releasePool << m_ubuf; + m_ubuf->build(); + + m_srb = m_r->newShaderResourceBindings(); + releasePool << m_srb; + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf) + }); + m_srb->build(); + + m_ps = m_r->newGraphicsPipeline(); + releasePool << m_ps; + + QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; + premulAlphaBlend.enable = true; + m_ps->setTargetBlends({ premulAlphaBlend }); + + const QShader vs = getShader(QLatin1String(":/color.vert.qsb")); + if (!vs.isValid()) + qFatal("Failed to load shader pack (vertex)"); + const QShader fs = getShader(QLatin1String(":/color.frag.qsb")); + if (!fs.isValid()) + qFatal("Failed to load shader pack (fragment)"); + + m_ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + + m_ps->setVertexInputLayout(inputLayout); + m_ps->setShaderResourceBindings(m_srb); + m_ps->setRenderPassDescriptor(m_rp); + + m_ps->build(); +} + +void Window::releaseResources() +{ + qDeleteAll(releasePool); + releasePool.clear(); + + delete m_sc; // native swapchain is likely released already + m_sc = nullptr; + + delete m_r; + +#ifndef QT_NO_OPENGL + delete m_fallbackSurface; +#endif +} + +void Window::resizeSwapChain() +{ + const QSize outputSize = m_sc->surfacePixelSize(); + + m_ds->setPixelSize(outputSize); + m_ds->build(); // == m_ds->release(); m_ds->build(); + + m_hasSwapChain = m_sc->buildOrResize(); + + m_elapsedMs = 0; + m_elapsedCount = 0; + + m_proj = m_r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); +} + +void Window::releaseSwapChain() +{ + if (m_hasSwapChain) { + m_hasSwapChain = false; + m_sc->release(); + } +} + +void Window::render() +{ + if (!m_hasSwapChain || m_notExposed) + return; + + // If the window got resized or got newly exposed, resize the swapchain. + // (the newly-exposed case is not actually required by some + // platforms/backends, but f.ex. Vulkan on Windows seems to need it) + if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + m_newlyExposed = false; + } + + // Start a new frame. This is where we block when too far ahead of + // GPU/present, and that's what throttles the thread to the refresh rate. + // (except for OpenGL where it happens either in endFrame or somewhere else + // depending on the GL implementation) + QRhi::FrameOpResult r = m_r->beginFrame(m_sc); + if (r == QRhi::FrameOpSwapChainOutOfDate) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + r = m_r->beginFrame(m_sc); + } + if (r != QRhi::FrameOpSuccess) { + requestUpdate(); + return; + } + + if (m_elapsedCount) + m_elapsedMs += m_timer.elapsed(); + m_timer.restart(); + m_elapsedCount += 1; + if (m_elapsedMs >= 4000) { + qDebug("%f", m_elapsedCount / 4.0f); + m_elapsedMs = 0; + m_elapsedCount = 0; + } + + // Set up buffer updates. + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (!m_vbufReady) { + m_vbufReady = true; + u->uploadStaticBuffer(m_vbuf, vertexData); + } + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.rotate(m_rotation, 0, 1, 0); + u->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); + m_opacity += m_opacityDir * 0.005f; + if (m_opacity < 0.0f || m_opacity > 1.0f) { + m_opacityDir *= -1; + m_opacity = qBound(0.0f, m_opacity, 1.0f); + } + u->updateDynamicBuffer(m_ubuf, 64, 4, &m_opacity); + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + + // Apply buffer updates, clear, start the renderpass (where applicable). + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + + cb->setGraphicsPipeline(m_ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + + const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + cb->endPass(); + + // Submit. + m_r->endFrame(m_sc); + + requestUpdate(); // render continuously, throttled by the presentation rate (due to beginFrame above) +} + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + // Defaults. +#if defined(Q_OS_WIN) + graphicsApi = D3D11; +#elif defined(Q_OS_DARWIN) + graphicsApi = Metal; +#elif QT_CONFIG(vulkan) + graphicsApi = Vulkan; +#else + graphicsApi = OpenGL; +#endif + + // Allow overriding via the command line. + QCommandLineParser cmdLineParser; + cmdLineParser.addHelpOption(); + QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)")); + cmdLineParser.addOption(glOption); + QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); + cmdLineParser.addOption(vkOption); + QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3dOption); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); + cmdLineParser.addOption(nullOption); + cmdLineParser.process(app); + if (cmdLineParser.isSet(glOption)) + graphicsApi = OpenGL; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = Vulkan; + if (cmdLineParser.isSet(d3dOption)) + graphicsApi = D3D11; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = Metal; + if (cmdLineParser.isSet(nullOption)) + graphicsApi = Null; + + // Vulkan setup. +#if QT_CONFIG(vulkan) + QVulkanInstance inst; + if (graphicsApi == Vulkan) { + if (!inst.create()) { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = OpenGL; + } + } +#endif + + // Create and show the window. + Window w; +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) + w.setVulkanInstance(&inst); +#endif + w.resize(1280, 720); + w.setTitle(graphicsApiName()); + w.show(); + + // Window::event() will not get invoked when the + // PlatformSurfaceAboutToBeDestroyed event is sent during the QWindow + // destruction. That happens only when exiting via app::quit() instead of + // the more common QWindow::close(). Take care of it: if the + // QPlatformWindow is still around (there was no close() yet), get rid of + // the swapchain while it's not too late. + if (w.handle()) + w.releaseSwapChain(); + + return app.exec(); +} diff --git a/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.pro b/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.pro new file mode 100644 index 0000000000..eeef33ff6a --- /dev/null +++ b/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + hellominimalcrossgfxtriangle.cpp + +RESOURCES = hellominimalcrossgfxtriangle.qrc diff --git a/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.qrc b/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.qrc new file mode 100644 index 0000000000..8cec4fa9ec --- /dev/null +++ b/tests/manual/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.qrc @@ -0,0 +1,6 @@ + + + ../shared/color.vert.qsb + ../shared/color.frag.qsb + + diff --git a/tests/manual/rhi/mrt/buildshaders.bat b/tests/manual/rhi/mrt/buildshaders.bat new file mode 100644 index 0000000000..75741448b1 --- /dev/null +++ b/tests/manual/rhi/mrt/buildshaders.bat @@ -0,0 +1,2 @@ +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c mrt.vert -o mrt.vert.qsb +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c mrt.frag -o mrt.frag.qsb diff --git a/tests/manual/rhi/mrt/mrt.cpp b/tests/manual/rhi/mrt/mrt.cpp new file mode 100644 index 0000000000..2bbd879dc4 --- /dev/null +++ b/tests/manual/rhi/mrt/mrt.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" + +// Exercises MRT, by attaching 4 textures to a single QRhiTextureRenderTarget. +// The fragment shader outputs to all four targets. +// The textures are then used to render 4 quads, so their contents can be confirmed. + +const int ATTCOUNT = 4; + +static float quadVertexData[] = +{ // Y up, CCW + -0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 0.0f +}; + +static quint16 quadIndexData[] = +{ + 0, 1, 2, 0, 2, 3 +}; + +static float triangleData[] = +{ // Y up, CCW + 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, +}; + +struct { + QVector releasePool; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiTextureRenderTarget *rt = nullptr; + QRhiRenderPassDescriptor *rtRp = nullptr; + QRhiSampler *sampler = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + QMatrix4x4 winProj; + struct PerColorBuffer { + QRhiTexture *tex = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + }; + PerColorBuffer colData[ATTCOUNT]; + + QRhiBuffer *triUbuf = nullptr; + QRhiShaderResourceBindings *triSrb = nullptr; + QRhiGraphicsPipeline *triPs = nullptr; + float triRot = 0; + QMatrix4x4 triBaseMvp; +} d; + +void Window::customInit() +{ + qDebug("Max color attachments: %d", m_r->resourceLimit(QRhi::MaxColorAttachments)); + if (m_r->resourceLimit(QRhi::MaxColorAttachments) < 4) + qWarning("MRT is not supported"); + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVertexData) + sizeof(triangleData)); + d.vbuf->build(); + d.releasePool << d.vbuf; + + d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(quadIndexData)); + d.ibuf->build(); + d.releasePool << d.ibuf; + + const int oneRoundedUniformBlockSize = m_r->ubufAligned(68); + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, oneRoundedUniformBlockSize * ATTCOUNT); + d.ubuf->build(); + d.releasePool << d.ubuf; + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.sampler; + d.sampler->build(); + + for (int i = 0; i < ATTCOUNT; ++i) { + QRhiTexture *tex = m_r->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget); + d.releasePool << tex; + tex->build(); + + QRhiShaderResourceBindings *srb = m_r->newShaderResourceBindings(); + d.releasePool << srb; + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, + d.ubuf, i * oneRoundedUniformBlockSize, 68), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, tex, d.sampler) + }); + srb->build(); + + d.colData[i].tex = tex; + d.colData[i].srb = srb; + } + + QRhiTextureRenderTargetDescription rtDesc; + QVector att; + for (int i = 0; i < ATTCOUNT; ++i) + att.append(QRhiColorAttachment(d.colData[i].tex)); + rtDesc.setColorAttachments(att); + d.rt = m_r->newTextureRenderTarget(rtDesc); + d.releasePool << d.rt; + d.rtRp = d.rt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.rtRp; + d.rt->setRenderPassDescriptor(d.rtRp); + d.rt->build(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 4 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.colData[0].srb); // all of them are layout-compatible + d.ps->setRenderPassDescriptor(m_rp); + d.ps->build(); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(quadVertexData), quadVertexData); + d.initialUpdates->uploadStaticBuffer(d.vbuf, sizeof(quadVertexData), sizeof(triangleData), triangleData); + d.initialUpdates->uploadStaticBuffer(d.ibuf, quadIndexData); + + qint32 flip = m_r->isYUpInFramebuffer() ? 1 : 0; + for (int i = 0; i < ATTCOUNT; ++i) + d.initialUpdates->updateDynamicBuffer(d.ubuf, i * oneRoundedUniformBlockSize + 64, 4, &flip); + + // triangle + d.triUbuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.releasePool << d.triUbuf; + d.triUbuf->build(); + + d.triSrb = m_r->newShaderResourceBindings(); + d.releasePool << d.triSrb; + d.triSrb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.triUbuf) + }); + d.triSrb->build(); + + d.triPs = m_r->newGraphicsPipeline(); + d.releasePool << d.triPs; + d.triPs->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/mrt.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/mrt.frag.qsb")) } + }); + QVector blends; + for (int i = 0; i < ATTCOUNT; ++i) { + QRhiGraphicsPipeline::TargetBlend blend; + blends.append(blend); + } + d.triPs->setTargetBlends(blends); + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + d.triPs->setVertexInputLayout(inputLayout); + d.triPs->setShaderResourceBindings(d.triSrb); + d.triPs->setRenderPassDescriptor(d.rtRp); + d.triPs->build(); + + d.triBaseMvp = m_r->clipSpaceCorrMatrix(); + d.triBaseMvp.perspective(45.0f, d.rt->pixelSize().width() / float(d.rt->pixelSize().height()), 0.01f, 1000.0f); + d.triBaseMvp.translate(0, 0, -2); + float opacity = 1.0f; + d.initialUpdates->updateDynamicBuffer(d.triUbuf, 64, 4, &opacity); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +void Window::customRender() +{ + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + QMatrix4x4 triMvp = d.triBaseMvp; + triMvp.rotate(d.triRot, 0, 1, 0); + d.triRot += 1; + u->updateDynamicBuffer(d.triUbuf, 0, 64, triMvp.constData()); + + cb->beginPass(d.rt, QColor::fromRgbF(0.5f, 0.2f, 0.0f, 1.0f), { 1.0f, 0 }, u); + cb->setGraphicsPipeline(d.triPs); + cb->setViewport({ 0, 0, float(d.rt->pixelSize().width()), float(d.rt->pixelSize().height()) }); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, sizeof(quadVertexData)); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + cb->endPass(); + + u = m_r->nextResourceUpdateBatch(); + if (d.winProj != m_proj) { + d.winProj = m_proj; + const int oneRoundedUniformBlockSize = m_r->ubufAligned(68); + for (int i = 0; i < ATTCOUNT; ++i) { + QMatrix4x4 mvp = m_proj; + switch (i) { + case 0: + mvp.translate(-2.0f, 0, 0); + break; + case 1: + mvp.translate(-0.8f, 0, 0); + break; + case 2: + mvp.translate(0.4f, 0, 0); + break; + case 3: + mvp.translate(1.6f, 0, 0); + break; + default: + Q_UNREACHABLE(); + break; + } + u->updateDynamicBuffer(d.ubuf, i * oneRoundedUniformBlockSize, 64, mvp.constData()); + } + } + + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + vbufBinding.second = 0; + cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16); + for (int i = 0; i < ATTCOUNT; ++i) { + cb->setShaderResources(d.colData[i].srb); + cb->drawIndexed(6); + } + cb->endPass(); +} diff --git a/tests/manual/rhi/mrt/mrt.frag b/tests/manual/rhi/mrt/mrt.frag new file mode 100644 index 0000000000..9166b552b9 --- /dev/null +++ b/tests/manual/rhi/mrt/mrt.frag @@ -0,0 +1,22 @@ +#version 440 + +layout(location = 0) in vec3 v_color; + +layout(location = 0) out vec4 c0; +layout(location = 1) out vec4 c1; +layout(location = 2) out vec4 c2; +layout(location = 3) out vec4 c3; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +} ubuf; + +void main() +{ + vec4 c = vec4(v_color * ubuf.opacity, ubuf.opacity); + c0 = vec4(c.r, 0, 0, c.a); + c1 = vec4(0, c.g, 0, c.a); + c2 = vec4(0, 0, c.b, c.a); + c3 = c; +} diff --git a/tests/manual/rhi/mrt/mrt.frag.qsb b/tests/manual/rhi/mrt/mrt.frag.qsb new file mode 100644 index 0000000000..389b6affa3 Binary files /dev/null and b/tests/manual/rhi/mrt/mrt.frag.qsb differ diff --git a/tests/manual/rhi/mrt/mrt.pro b/tests/manual/rhi/mrt/mrt.pro new file mode 100644 index 0000000000..7cde425488 --- /dev/null +++ b/tests/manual/rhi/mrt/mrt.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + mrt.cpp + +RESOURCES = mrt.qrc diff --git a/tests/manual/rhi/mrt/mrt.qrc b/tests/manual/rhi/mrt/mrt.qrc new file mode 100644 index 0000000000..275f476013 --- /dev/null +++ b/tests/manual/rhi/mrt/mrt.qrc @@ -0,0 +1,8 @@ + + + mrt.vert.qsb + mrt.frag.qsb + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + + diff --git a/tests/manual/rhi/mrt/mrt.vert b/tests/manual/rhi/mrt/mrt.vert new file mode 100644 index 0000000000..43af543f44 --- /dev/null +++ b/tests/manual/rhi/mrt/mrt.vert @@ -0,0 +1,19 @@ +#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; + float opacity; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + v_color = color; + gl_Position = ubuf.mvp * position; +} diff --git a/tests/manual/rhi/mrt/mrt.vert.qsb b/tests/manual/rhi/mrt/mrt.vert.qsb new file mode 100644 index 0000000000..ff8c0e50bb Binary files /dev/null and b/tests/manual/rhi/mrt/mrt.vert.qsb differ diff --git a/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.cpp b/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.cpp new file mode 100644 index 0000000000..b61fb91c6d --- /dev/null +++ b/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" + +// Uses a multisample renderbuffer (whatever that may be on a given backend) to +// render to and then resolves the samples into a non-multisample texture. + +static float vertexData[] = +{ // Y up, CCW + -0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 0.0f +}; + +static quint16 indexData[] = +{ + 0, 1, 2, 0, 2, 3 +}; + +static float triangleData[] = +{ // Y up, CCW + 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, +}; + +struct { + QVector releasePool; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiRenderBuffer *rb = nullptr; + QRhiTextureRenderTarget *rt = nullptr; + QRhiRenderPassDescriptor *rtRp = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiBuffer *triUbuf = nullptr; + QRhiShaderResourceBindings *triSrb = nullptr; + QRhiGraphicsPipeline *triPs = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + QMatrix4x4 triBaseMvp; + float triRot = 0; + QMatrix4x4 winProj; +} d; + +void Window::customInit() +{ + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData) + sizeof(triangleData)); + d.vbuf->build(); + d.releasePool << d.vbuf; + + d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indexData)); + d.ibuf->build(); + d.releasePool << d.ibuf; + + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.ubuf->build(); + d.releasePool << d.ubuf; + + d.rb = m_r->newRenderBuffer(QRhiRenderBuffer::Color, QSize(512, 512), 4); // 4x MSAA + d.rb->build(); + d.releasePool << d.rb; + + // the non-msaa texture that will be the destination in the resolve + d.tex = m_r->newTexture(QRhiTexture::RGBA8, d.rb->pixelSize(), 1, QRhiTexture::RenderTarget); + d.releasePool << d.tex; + d.tex->build(); + + // rb is multisample, instead of writing out the msaa data into it, + // resolve into d.tex at the end of each render pass + QRhiTextureRenderTargetDescription rtDesc; + QRhiColorAttachment rtAtt(d.rb); + rtAtt.setResolveTexture(d.tex); + rtDesc.setColorAttachments({ rtAtt }); + + d.rt = m_r->newTextureRenderTarget(rtDesc); + d.releasePool << d.rt; + d.rtRp = d.rt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.rtRp; + d.rt->setRenderPassDescriptor(d.rtRp); + d.rt->build(); + + d.triUbuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.releasePool << d.triUbuf; + d.triUbuf->build(); + + d.triSrb = m_r->newShaderResourceBindings(); + d.releasePool << d.triSrb; + d.triSrb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.triUbuf) + }); + d.triSrb->build(); + + d.triPs = m_r->newGraphicsPipeline(); + d.releasePool << d.triPs; + d.triPs->setSampleCount(4); // must match the render target + d.triPs->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/color.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/color.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + d.triPs->setVertexInputLayout(inputLayout); + d.triPs->setShaderResourceBindings(d.triSrb); + d.triPs->setRenderPassDescriptor(d.rtRp); + d.triPs->build(); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.sampler; + d.sampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.releasePool << d.srb; + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }); + inputLayout.setBindings({ + { 4 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + d.ps->build(); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(vertexData), vertexData); + d.initialUpdates->uploadStaticBuffer(d.vbuf, sizeof(vertexData), sizeof(triangleData), triangleData); + d.initialUpdates->uploadStaticBuffer(d.ibuf, indexData); + + d.triBaseMvp = m_r->clipSpaceCorrMatrix(); + d.triBaseMvp.perspective(45.0f, d.rb->pixelSize().width() / float(d.rb->pixelSize().height()), 0.01f, 1000.0f); + d.triBaseMvp.translate(0, 0, -2); + float opacity = 1.0f; + d.initialUpdates->updateDynamicBuffer(d.triUbuf, 64, 4, &opacity); + + qint32 flip = m_r->isYUpInFramebuffer() ? 1 : 0; + d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &flip); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +void Window::customRender() +{ + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + QMatrix4x4 triMvp = d.triBaseMvp; + triMvp.rotate(d.triRot, 0, 1, 0); + d.triRot += 1; + u->updateDynamicBuffer(d.triUbuf, 0, 64, triMvp.constData()); + + if (d.winProj != m_proj) { + d.winProj = m_proj; + QMatrix4x4 mvp = m_proj; + mvp.scale(2.5f); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + } + + // offscreen (triangle, msaa) + cb->beginPass(d.rt, QColor::fromRgbF(0.5f, 0.2f, 0.0f, 1.0f), { 1.0f, 0 }, u); + cb->setGraphicsPipeline(d.triPs); + cb->setViewport({ 0, 0, float(d.rb->pixelSize().width()), float(d.rb->pixelSize().height()) }); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, sizeof(vertexData)); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + cb->endPass(); + + // onscreen (quad) + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }); + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + vbufBinding.second = 0; + cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16); + cb->drawIndexed(6); + cb->endPass(); +} diff --git a/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.pro b/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.pro new file mode 100644 index 0000000000..4b31e39e7d --- /dev/null +++ b/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + msaarenderbuffer.cpp + +RESOURCES = msaarenderbuffer.qrc diff --git a/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.qrc b/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.qrc new file mode 100644 index 0000000000..c7a2380a42 --- /dev/null +++ b/tests/manual/rhi/msaarenderbuffer/msaarenderbuffer.qrc @@ -0,0 +1,8 @@ + + + ../shared/color.vert.qsb + ../shared/color.frag.qsb + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + + diff --git a/tests/manual/rhi/msaatexture/msaatexture.cpp b/tests/manual/rhi/msaatexture/msaatexture.cpp new file mode 100644 index 0000000000..1b652cce92 --- /dev/null +++ b/tests/manual/rhi/msaatexture/msaatexture.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" + +// Renders into a non-multisample and a multisample (4x) texture, and then uses +// those textures to draw two quads. Note that this uses an MSAA sampler in the +// shader, not resolves. Not supported on the GL(ES) backend atm. + +static float vertexData[] = +{ // Y up, CCW + -0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 0.0f +}; + +static quint16 indexData[] = +{ + 0, 1, 2, 0, 2, 3 +}; + +static float triangleData[] = +{ // Y up, CCW + 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, +}; + +const int UBUFSZ = 68; + +struct { + QVector releasePool; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiTexture *msaaTex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srbLeft = nullptr; + QRhiShaderResourceBindings *srbRight = nullptr; + QRhiGraphicsPipeline *psLeft = nullptr; + QRhiGraphicsPipeline *psRight = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + int rightOfs; + QMatrix4x4 winProj; + QMatrix4x4 triBaseMvp; + float triRot = 0; + + QRhiShaderResourceBindings *triSrb = nullptr; + QRhiGraphicsPipeline *msaaTriPs = nullptr; + QRhiGraphicsPipeline *triPs = nullptr; + QRhiBuffer *triUbuf = nullptr; + QRhiTextureRenderTarget *msaaRt = nullptr; + QRhiRenderPassDescriptor *msaaRtRp = nullptr; + QRhiTextureRenderTarget *rt = nullptr; + QRhiRenderPassDescriptor *rtRp = nullptr; +} d; + +//#define NO_MSAA + +void Window::customInit() +{ + if (!m_r->isFeatureSupported(QRhi::MultisampleTexture)) + qFatal("Multisample textures not supported by this backend"); + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData) + sizeof(triangleData)); + d.releasePool << d.vbuf; + d.vbuf->build(); + + d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indexData)); + d.releasePool << d.ibuf; + d.ibuf->build(); + + d.rightOfs = m_r->ubufAligned(UBUFSZ); + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, d.rightOfs + UBUFSZ); + d.releasePool << d.ubuf; + d.ubuf->build(); + + d.tex = m_r->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget); + d.releasePool << d.tex; + d.tex->build(); +#ifndef NO_MSAA + d.msaaTex = m_r->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 4, QRhiTexture::RenderTarget); +#else + d.msaaTex = m_r->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget); +#endif + d.releasePool << d.msaaTex; + d.msaaTex->build(); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(vertexData), vertexData); + d.initialUpdates->uploadStaticBuffer(d.vbuf, sizeof(vertexData), sizeof(triangleData), triangleData); + d.initialUpdates->uploadStaticBuffer(d.ibuf, indexData); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.sampler; + d.sampler->build(); + + d.srbLeft = m_r->newShaderResourceBindings(); + d.releasePool << d.srbLeft; + d.srbLeft->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUFSZ), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srbLeft->build(); + + d.srbRight = m_r->newShaderResourceBindings(); + d.releasePool << d.srbRight; + d.srbRight->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, d.rightOfs, UBUFSZ), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.msaaTex, d.sampler) + }); + d.srbRight->build(); + + d.psLeft = m_r->newGraphicsPipeline(); + d.releasePool << d.psLeft; + d.psLeft->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + d.psLeft->setVertexInputLayout(inputLayout); + d.psLeft->setShaderResourceBindings(d.srbLeft); + d.psLeft->setRenderPassDescriptor(m_rp); + d.psLeft->build(); + + d.psRight = m_r->newGraphicsPipeline(); + d.releasePool << d.psRight; + d.psRight->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, +#ifndef NO_MSAA + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture_ms4.frag.qsb")) } +#else + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } +#endif + }); + d.psRight->setVertexInputLayout(d.psLeft->vertexInputLayout()); + d.psRight->setShaderResourceBindings(d.srbRight); + d.psRight->setRenderPassDescriptor(m_rp); + d.psRight->build(); + + // set up the offscreen triangle that goes into tex and msaaTex + d.triUbuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.releasePool << d.triUbuf; + d.triUbuf->build(); + d.triSrb = m_r->newShaderResourceBindings(); + d.releasePool << d.triSrb; + d.triSrb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.triUbuf) + }); + d.triSrb->build(); + d.rt = m_r->newTextureRenderTarget({ d.tex }); + d.releasePool << d.rt; + d.rtRp = d.rt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.rtRp; + d.rt->setRenderPassDescriptor(d.rtRp); + d.rt->build(); + d.msaaRt = m_r->newTextureRenderTarget({ d.msaaTex }); + d.releasePool << d.msaaRt; + d.msaaRtRp = d.msaaRt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.msaaRtRp; + d.msaaRt->setRenderPassDescriptor(d.msaaRtRp); + d.msaaRt->build(); + d.triPs = m_r->newGraphicsPipeline(); + d.releasePool << d.triPs; + d.triPs->setSampleCount(1); + d.triPs->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/color.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/color.frag.qsb")) } + }); + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + d.triPs->setVertexInputLayout(inputLayout); + d.triPs->setShaderResourceBindings(d.triSrb); + d.triPs->setRenderPassDescriptor(d.rtRp); + d.triPs->build(); + d.msaaTriPs = m_r->newGraphicsPipeline(); + d.releasePool << d.msaaTriPs; +#ifndef NO_MSAA + d.msaaTriPs->setSampleCount(4); +#else + d.msaaTriPs->setSampleCount(1); +#endif + d.msaaTriPs->setShaderStages(d.triPs->shaderStages()); + d.msaaTriPs->setVertexInputLayout(d.triPs->vertexInputLayout()); + d.msaaTriPs->setShaderResourceBindings(d.triSrb); + d.msaaTriPs->setRenderPassDescriptor(d.msaaRtRp); + d.msaaTriPs->build(); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +void Window::customRender() +{ + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + + // onscreen ubuf + qint32 flip = m_r->isYUpInFramebuffer() ? 1 : 0; + u->updateDynamicBuffer(d.ubuf, 64, 4, &flip); + u->updateDynamicBuffer(d.ubuf, d.rightOfs + 64, 4, &flip); + + // offscreen ubuf + d.triBaseMvp = m_r->clipSpaceCorrMatrix(); + d.triBaseMvp.perspective(45.0f, d.msaaTex->pixelSize().width() / float(d.msaaTex->pixelSize().height()), 0.01f, 1000.0f); + d.triBaseMvp.translate(0, 0, -2); + float opacity = 1.0f; + u->updateDynamicBuffer(d.triUbuf, 64, 4, &opacity); + } + + if (d.winProj != m_proj) { + // onscreen buf, window size dependent + d.winProj = m_proj; + QMatrix4x4 mvp = m_proj; + mvp.scale(2); + mvp.translate(-0.8f, 0, 0); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + mvp.translate(1.6f, 0, 0); + u->updateDynamicBuffer(d.ubuf, d.rightOfs, 64, mvp.constData()); + } + + // offscreen buf, apply the rotation on every frame + QMatrix4x4 triMvp = d.triBaseMvp; + triMvp.rotate(d.triRot, 0, 1, 0); + d.triRot += 1; + u->updateDynamicBuffer(d.triUbuf, 0, 64, triMvp.constData()); + + cb->resourceUpdate(u); // could have passed u to beginPass but exercise this one too + + // offscreen + cb->beginPass(d.rt, QColor::fromRgbF(0.5f, 0.2f, 0.0f, 1.0f), { 1.0f, 0 }); + cb->setGraphicsPipeline(d.triPs); + cb->setViewport({ 0, 0, float(d.msaaTex->pixelSize().width()), float(d.msaaTex->pixelSize().height()) }); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, sizeof(vertexData)); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + cb->endPass(); + + // offscreen msaa + cb->beginPass(d.msaaRt, QColor::fromRgbF(0.5f, 0.2f, 0.0f, 1.0f), { 1.0f, 0 }); + cb->setGraphicsPipeline(d.msaaTriPs); + cb->setViewport({ 0, 0, float(d.msaaTex->pixelSize().width()), float(d.msaaTex->pixelSize().height()) }); + cb->setShaderResources(); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + cb->endPass(); + + // onscreen + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }); + cb->setGraphicsPipeline(d.psLeft); // showing the non-msaa version + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + vbufBinding.second = 0; + cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16); + cb->drawIndexed(6); + cb->setGraphicsPipeline(d.psRight); // showing the msaa version, resolved in the shader + cb->setShaderResources(); + cb->drawIndexed(6); + cb->endPass(); +} diff --git a/tests/manual/rhi/msaatexture/msaatexture.pro b/tests/manual/rhi/msaatexture/msaatexture.pro new file mode 100644 index 0000000000..0467e4dc9d --- /dev/null +++ b/tests/manual/rhi/msaatexture/msaatexture.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + msaatexture.cpp + +RESOURCES = msaatexture.qrc diff --git a/tests/manual/rhi/msaatexture/msaatexture.qrc b/tests/manual/rhi/msaatexture/msaatexture.qrc new file mode 100644 index 0000000000..ecd968481c --- /dev/null +++ b/tests/manual/rhi/msaatexture/msaatexture.qrc @@ -0,0 +1,9 @@ + + + ../shared/color.vert.qsb + ../shared/color.frag.qsb + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + ../shared/texture_ms4.frag.qsb + + diff --git a/tests/manual/rhi/multiwindow/multiwindow.cpp b/tests/manual/rhi/multiwindow/multiwindow.cpp new file mode 100644 index 0000000000..a5eed78b1d --- /dev/null +++ b/tests/manual/rhi/multiwindow/multiwindow.cpp @@ -0,0 +1,644 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#ifndef QT_NO_OPENGL +#include +#include +#endif + +#if QT_CONFIG(vulkan) +#include +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_DARWIN +#include +#endif + +enum GraphicsApi +{ + OpenGL, + Vulkan, + D3D11, + Metal +}; + +static GraphicsApi graphicsApi; + +static QString graphicsApiName() +{ + switch (graphicsApi) { + case OpenGL: + return QLatin1String("OpenGL 2.x"); + case Vulkan: + return QLatin1String("Vulkan"); + case D3D11: + return QLatin1String("Direct3D 11"); + case Metal: + return QLatin1String("Metal"); + default: + break; + } + return QString(); +} + +static struct { +#if QT_CONFIG(vulkan) + QVulkanInstance *instance = nullptr; +#endif + QRhi *r = nullptr; +#ifndef QT_NO_OPENGL + QOffscreenSurface *fallbackSurface = nullptr; +#endif +} r; + +void createRhi() +{ +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) { + r.fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); + QRhiGles2InitParams params; + params.fallbackSurface = r.fallbackSurface; + //params.window = this; + r.r = QRhi::create(QRhi::OpenGLES2, ¶ms); + } +#endif + +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) { + QRhiVulkanInitParams params; + params.inst = r.instance; + //params.window = this; + r.r = QRhi::create(QRhi::Vulkan, ¶ms); + } +#endif + +#ifdef Q_OS_WIN + if (graphicsApi == D3D11) { + QRhiD3D11InitParams params; + params.enableDebugLayer = true; + r.r = QRhi::create(QRhi::D3D11, ¶ms); + } +#endif + +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + QRhiMetalInitParams params; + r.r = QRhi::create(QRhi::Metal, ¶ms); + } +#endif + + if (!r.r) + qFatal("Failed to create RHI backend"); +} + +void destroyRhi() +{ + delete r.r; + +#ifndef QT_NO_OPENGL + delete r.fallbackSurface; +#endif +} + +struct { + QVector windows; + + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; +} d; + +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, +}; + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +// can use just one rpd from whichever window comes first since they are +// actually compatible due to all windows using the same config (have +// depth-stencil, sample count 1, same format). this means the same pso can be +// reused too. +void ensureSharedResources(QRhiRenderPassDescriptor *rp) +{ + if (!d.vbuf) { + d.vbuf = r.r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); + d.vbuf->build(); + d.initialUpdates = r.r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, vertexData); + } + + if (!d.ubuf) { + d.ubuf = r.r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.ubuf->build(); + } + + if (!d.srb) { + d.srb = r.r->newShaderResourceBindings(); + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf) + }); + d.srb->build(); + } + + if (!d.ps) { + d.ps = r.r->newGraphicsPipeline(); + + QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; + premulAlphaBlend.enable = true; + d.ps->setTargetBlends({ premulAlphaBlend }); + + const QShader vs = getShader(QLatin1String(":/color.vert.qsb")); + if (!vs.isValid()) + qFatal("Failed to load shader pack (vertex)"); + const QShader fs = getShader(QLatin1String(":/color.frag.qsb")); + if (!fs.isValid()) + qFatal("Failed to load shader pack (fragment)"); + + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(rp); + + d.ps->build(); + } +} + +void destroySharedResources() +{ + delete d.ps; + d.ps = nullptr; + + delete d.srb; + d.srb = nullptr; + + delete d.vbuf; + d.vbuf = nullptr; + + delete d.ubuf; + d.ubuf = nullptr; +} + +class Window : public QWindow +{ +public: + Window(const QString &title, const QColor &bgColor, int axis, bool noVSync); + ~Window(); + +protected: + void init(); + void releaseResources(); + void resizeSwapChain(); + void releaseSwapChain(); + void render(); + + void exposeEvent(QExposeEvent *) override; + bool event(QEvent *) override; + + QColor m_bgColor; + int m_rotationAxis; + bool m_noVSync; + + bool m_running = false; + bool m_notExposed = false; + bool m_newlyExposed = false; + + QMatrix4x4 m_proj; + QVector m_releasePool; + + bool m_hasSwapChain = false; + QRhiSwapChain *m_sc = nullptr; + QRhiRenderBuffer *m_ds = nullptr; + QRhiRenderPassDescriptor *m_rp = nullptr; + + float m_rotation = 0; + float m_opacity = 1; + int m_opacityDir = -1; +}; + +Window::Window(const QString &title, const QColor &bgColor, int axis, bool noVSync) + : m_bgColor(bgColor), + m_rotationAxis(axis), + m_noVSync(noVSync) +{ + switch (graphicsApi) { + case OpenGL: + { +#if QT_CONFIG(opengl) + setSurfaceType(OpenGLSurface); + QSurfaceFormat fmt = QRhiGles2InitParams::adjustedFormat(); // default + depth/stencil + fmt.setSwapInterval(noVSync ? 0 : 1); // + swap interval + setFormat(fmt); +#endif + } + break; + case Vulkan: +#if QT_CONFIG(vulkan) + setSurfaceType(VulkanSurface); + setVulkanInstance(r.instance); +#endif + break; + case D3D11: + setSurfaceType(OpenGLSurface); // not a typo + break; + case Metal: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + setSurfaceType(MetalSurface); +#endif + break; + default: + break; + } + + resize(800, 600); + setTitle(title); +} + +Window::~Window() +{ + releaseResources(); +} + +void Window::exposeEvent(QExposeEvent *) +{ + // initialize and start rendering when the window becomes usable for graphics purposes + if (isExposed() && !m_running) { + m_running = true; + init(); + resizeSwapChain(); + } + + // stop pushing frames when not exposed (or size is 0) + if ((!isExposed() || (m_hasSwapChain && m_sc->surfacePixelSize().isEmpty())) && m_running) + m_notExposed = true; + + // continue when exposed again and the surface has a valid size. + // note that the surface size can be (0, 0) even though size() reports a valid one... + if (isExposed() && m_running && m_notExposed && !m_sc->surfacePixelSize().isEmpty()) { + m_notExposed = false; + m_newlyExposed = true; + } + + // always render a frame on exposeEvent() (when exposed) in order to update + // immediately on window resize. + if (isExposed() && !m_sc->surfacePixelSize().isEmpty()) + render(); +} + +bool Window::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::UpdateRequest: + render(); + break; + + case QEvent::PlatformSurface: + // this is the proper time to tear down the swapchain (while the native window and surface are still around) + if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) + releaseSwapChain(); + break; + + default: + break; + } + + return QWindow::event(e); +} + +void Window::init() +{ + m_sc = r.r->newSwapChain(); + m_ds = r.r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), // no need to set the size yet + 1, + QRhiRenderBuffer::UsedWithSwapChainOnly); + m_releasePool << m_ds; + m_sc->setWindow(this); + m_sc->setDepthStencil(m_ds); + if (m_noVSync) + m_sc->setFlags(QRhiSwapChain::NoVSync); + + m_rp = m_sc->newCompatibleRenderPassDescriptor(); + m_releasePool << m_rp; + m_sc->setRenderPassDescriptor(m_rp); + + ensureSharedResources(m_rp); +} + +void Window::releaseResources() +{ + qDeleteAll(m_releasePool); + m_releasePool.clear(); + + delete m_sc; + m_sc = nullptr; +} + +void Window::resizeSwapChain() +{ + const QSize outputSize = m_sc->surfacePixelSize(); + + m_ds->setPixelSize(outputSize); + m_ds->build(); + + m_hasSwapChain = m_sc->buildOrResize(); + + m_proj = r.r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); + m_proj.translate(0, 0, -4); +} + +void Window::releaseSwapChain() +{ + if (m_hasSwapChain) { + m_hasSwapChain = false; + m_sc->release(); + } +} + +void Window::render() +{ + if (!m_hasSwapChain || m_notExposed) + return; + + // If the window got resized or got newly exposed, resize the swapchain. + // (the newly-exposed case is not actually required by some + // platforms/backends, but f.ex. Vulkan on Windows seems to need it) + if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + m_newlyExposed = false; + } + + QRhi::FrameOpResult result = r.r->beginFrame(m_sc); + if (result == QRhi::FrameOpSwapChainOutOfDate) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + result = r.r->beginFrame(m_sc); + } + if (result != QRhi::FrameOpSuccess) { + requestUpdate(); + return; + } + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + + QRhiResourceUpdateBatch *u = r.r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.rotate(m_rotation, m_rotationAxis == 0 ? 1 : 0, m_rotationAxis == 1 ? 1 : 0, m_rotationAxis == 2 ? 1 : 0); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + m_opacity += m_opacityDir * 0.005f; + if (m_opacity < 0.0f || m_opacity > 1.0f) { + m_opacityDir *= -1; + m_opacity = qBound(0.0f, m_opacity, 1.0f); + } + u->updateDynamicBuffer(d.ubuf, 64, 4, &m_opacity); + + cb->beginPass(m_sc->currentFrameRenderTarget(), + QColor::fromRgbF(float(m_bgColor.redF()), float(m_bgColor.greenF()), float(m_bgColor.blueF()), 1.0f), + { 1.0f, 0 }, + u); + + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + cb->endPass(); + + r.r->endFrame(m_sc); + + requestUpdate(); +} + +void createWindow(bool noVSync) +{ + static QColor colors[] = { Qt::red, Qt::green, Qt::blue, Qt::yellow, Qt::cyan, Qt::gray }; + const int n = d.windows.count(); + d.windows.append(new Window(QString::asprintf("Window #%d%s", n, noVSync ? " (no vsync)" : ""), colors[n % 6], n % 3, noVSync)); + d.windows.last()->show(); +} + +void closeWindow() +{ + delete d.windows.takeLast(); +} + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + +#if defined(Q_OS_WIN) + graphicsApi = D3D11; +#elif defined(Q_OS_DARWIN) + graphicsApi = Metal; +#elif QT_CONFIG(vulkan) + graphicsApi = Vulkan; +#else + graphicsApi = OpenGL; +#endif + + QCommandLineParser cmdLineParser; + cmdLineParser.addHelpOption(); + QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)")); + cmdLineParser.addOption(glOption); + QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); + cmdLineParser.addOption(vkOption); + QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3dOption); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + cmdLineParser.process(app); + if (cmdLineParser.isSet(glOption)) + graphicsApi = OpenGL; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = Vulkan; + if (cmdLineParser.isSet(d3dOption)) + graphicsApi = D3D11; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = Metal; + + qDebug("Selected graphics API is %s", qPrintable(graphicsApiName())); + qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText())); + +#if QT_CONFIG(vulkan) + r.instance = new QVulkanInstance; + if (graphicsApi == Vulkan) { +#ifndef Q_OS_ANDROID + r.instance->setLayers({ "VK_LAYER_LUNARG_standard_validation" }); +#else + r.instance->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 (!r.instance->create()) { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = OpenGL; + } + } +#endif + + createRhi(); + + int winCount = 0; + QWidget w; + w.resize(800, 600); + w.setWindowTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + graphicsApiName()); + QVBoxLayout *layout = new QVBoxLayout(&w); + + QPlainTextEdit *info = new QPlainTextEdit( + QLatin1String("This application tests rendering with the same QRhi instance (and so the same Vulkan/Metal/D3D device or OpenGL context) " + "to multiple windows via multiple QRhiSwapChain objects, from the same one thread. Some resources are shared across all windows.\n" + "\nNote that the behavior may differ depending on the underlying graphics API implementation and the number of windows. " + "One challenge here is the vsync throttling: with the default vsync/fifo presentation mode the behavior may differ between " + "platforms, drivers, and APIs as we present different swapchains' images in a row on the same thread. As a potential solution, " + "setting NoVSync on the second, third, and later window swapchains is offered as an option.\n" + "\n\nUsing API: ") + graphicsApiName()); + info->setReadOnly(true); + layout->addWidget(info); + QLabel *label = new QLabel(QLatin1String("Window count: 0")); + layout->addWidget(label); + QCheckBox *vsCb = new QCheckBox(QLatin1String("Set NoVSync on all swapchains except the first")); + vsCb->setChecked(false); + layout->addWidget(vsCb); + QPushButton *btn = new QPushButton(QLatin1String("New window")); + QObject::connect(btn, &QPushButton::clicked, btn, [label, vsCb, &winCount] { + winCount += 1; + label->setText(QString::asprintf("Window count: %d", winCount)); + const bool noVSync = vsCb->isChecked() && winCount > 1; + createWindow(noVSync); + }); + layout->addWidget(btn); + btn = new QPushButton(QLatin1String("Close window")); + QObject::connect(btn, &QPushButton::clicked, btn, [label, &winCount] { + if (winCount > 0) { + winCount -= 1; + label->setText(QString::asprintf("Window count: %d", winCount)); + closeWindow(); + } + }); + layout->addWidget(btn); + w.show(); + + int result = app.exec(); + + qDeleteAll(d.windows); + + destroySharedResources(); + destroyRhi(); + +#if QT_CONFIG(vulkan) + delete r.instance; +#endif + + return result; +} diff --git a/tests/manual/rhi/multiwindow/multiwindow.pro b/tests/manual/rhi/multiwindow/multiwindow.pro new file mode 100644 index 0000000000..b9c3867bf8 --- /dev/null +++ b/tests/manual/rhi/multiwindow/multiwindow.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private widgets + +SOURCES = \ + multiwindow.cpp + +RESOURCES = multiwindow.qrc diff --git a/tests/manual/rhi/multiwindow/multiwindow.qrc b/tests/manual/rhi/multiwindow/multiwindow.qrc new file mode 100644 index 0000000000..8cec4fa9ec --- /dev/null +++ b/tests/manual/rhi/multiwindow/multiwindow.qrc @@ -0,0 +1,6 @@ + + + ../shared/color.vert.qsb + ../shared/color.frag.qsb + + diff --git a/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp new file mode 100644 index 0000000000..45818e91e9 --- /dev/null +++ b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp @@ -0,0 +1,829 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifndef QT_NO_OPENGL +#include +#include +#endif + +#if QT_CONFIG(vulkan) +#include +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_DARWIN +#include +#endif + +#include "window.h" + +#include "../shared/cube.h" + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +static GraphicsApi graphicsApi; + +static QString graphicsApiName() +{ + switch (graphicsApi) { + case OpenGL: + return QLatin1String("OpenGL 2.x"); + case Vulkan: + return QLatin1String("Vulkan"); + case D3D11: + return QLatin1String("Direct3D 11"); + case Metal: + return QLatin1String("Metal"); + default: + break; + } + return QString(); +} + +#if QT_CONFIG(vulkan) +QVulkanInstance *instance = nullptr; +#endif + +// Window (main thread) emit signals -> Renderer::send* (main thread) -> event queue (add on main, process on render thread) -> Renderer::renderEvent (render thread) + +// event queue is taken from the Qt Quick scenegraph as-is +// all this below is conceptually the same as the QSG threaded render loop +class RenderThreadEventQueue : public QQueue +{ +public: + RenderThreadEventQueue() + : waiting(false) + { + } + + void addEvent(QEvent *e) { + mutex.lock(); + enqueue(e); + if (waiting) + condition.wakeOne(); + mutex.unlock(); + } + + QEvent *takeEvent(bool wait) { + mutex.lock(); + if (isEmpty() && wait) { + waiting = true; + condition.wait(&mutex); + waiting = false; + } + QEvent *e = dequeue(); + mutex.unlock(); + return e; + } + + bool hasMoreEvents() { + mutex.lock(); + bool has = !isEmpty(); + mutex.unlock(); + return has; + } + +private: + QMutex mutex; + QWaitCondition condition; + bool waiting; +}; + +struct Renderer; + +struct Thread : public QThread +{ + Thread(Renderer *renderer_) + : renderer(renderer_) + { + active = true; + start(); + } + void run() override; + + Renderer *renderer; + bool active; + RenderThreadEventQueue eventQueue; + bool sleeping = false; + bool stopEventProcessing = false; + bool pendingRender = false; + bool pendingRenderIsNewExpose = false; + // mutex and cond used to allow the main thread waiting until something completes on the render thread + QMutex mutex; + QWaitCondition cond; +}; + +class RenderThreadEvent : public QEvent +{ +public: + RenderThreadEvent(QEvent::Type type) : QEvent(type) { } +}; + +class InitEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 1); + InitEvent() : RenderThreadEvent(TYPE) + { } +}; + +class RequestRenderEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 2); + RequestRenderEvent(bool newlyExposed_) : RenderThreadEvent(TYPE), newlyExposed(newlyExposed_) + { } + bool newlyExposed; +}; + +class SurfaceCleanupEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 3); + SurfaceCleanupEvent() : RenderThreadEvent(TYPE) + { } +}; + +class CloseEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 4); + CloseEvent() : RenderThreadEvent(TYPE) + { } +}; + +class SyncSurfaceSizeEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 5); + SyncSurfaceSizeEvent() : RenderThreadEvent(TYPE) + { } +}; + +struct Renderer +{ + // ctor and dtor and send* are called main thread, rest on the render thread + + Renderer(QWindow *w, const QColor &bgColor, int rotationAxis); + ~Renderer(); + + void sendInit(); + void sendRender(bool newlyExposed); + void sendSurfaceGoingAway(); + void sendSyncSurfaceSize(); + + QWindow *window; + Thread *thread; + QRhi *r = nullptr; +#ifndef QT_NO_OPENGL + QOffscreenSurface *fallbackSurface = nullptr; +#endif + + void createRhi(); + void destroyRhi(); + void renderEvent(QEvent *e); + void init(); + void releaseSwapChain(); + void releaseResources(); + void render(bool newlyExposed, bool wakeBeforePresent); + + QColor m_bgColor; + int m_rotationAxis; + + QVector m_releasePool; + bool m_hasSwapChain = false; + QRhiSwapChain *m_sc = nullptr; + QRhiRenderBuffer *m_ds = nullptr; + QRhiRenderPassDescriptor *m_rp = nullptr; + + QRhiBuffer *m_vbuf = nullptr; + QRhiBuffer *m_ubuf = nullptr; + QRhiTexture *m_tex = nullptr; + QRhiSampler *m_sampler = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + QRhiResourceUpdateBatch *m_initialUpdates = nullptr; + + QMatrix4x4 m_proj; + float m_rotation = 0; + int m_frameCount = 0; +}; + +void Thread::run() +{ + while (active) { + if (pendingRender) { + pendingRender = false; + renderer->render(pendingRenderIsNewExpose, false); + } + + while (eventQueue.hasMoreEvents()) { + QEvent *e = eventQueue.takeEvent(false); + renderer->renderEvent(e); + delete e; + } + + if (active && !pendingRender) { + sleeping = true; + stopEventProcessing = false; + while (!stopEventProcessing) { + QEvent *e = eventQueue.takeEvent(true); + renderer->renderEvent(e); + delete e; + } + sleeping = false; + } + } +} + +Renderer::Renderer(QWindow *w, const QColor &bgColor, int rotationAxis) + : window(w), + m_bgColor(bgColor), + m_rotationAxis(rotationAxis) +{ // main thread + thread = new Thread(this); + +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) + fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); +#endif +} + +Renderer::~Renderer() +{ // main thread + thread->eventQueue.addEvent(new CloseEvent); + thread->wait(); + delete thread; + +#ifndef QT_NO_OPENGL + delete fallbackSurface; +#endif +} + +void Renderer::createRhi() +{ + if (r) + return; + + qDebug() << "renderer" << this << "creating rhi"; + QRhi::Flags rhiFlags = QRhi::EnableProfiling; + +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) { + QRhiGles2InitParams params; + params.fallbackSurface = fallbackSurface; + params.window = window; + r = QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags); + } +#endif + +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) { + QRhiVulkanInitParams params; + params.inst = instance; + params.window = window; + r = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_WIN + if (graphicsApi == D3D11) { + QRhiD3D11InitParams params; + params.enableDebugLayer = true; + r = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + QRhiMetalInitParams params; + r = QRhi::create(QRhi::Metal, ¶ms, rhiFlags); + } +#endif + + if (!r) + qFatal("Failed to create RHI backend"); +} + +void Renderer::destroyRhi() +{ + qDebug() << "renderer" << this << "destroying rhi"; + delete r; + r = nullptr; +} + +void Renderer::renderEvent(QEvent *e) +{ + Q_ASSERT(QThread::currentThread() == thread); + + if (thread->sleeping) + thread->stopEventProcessing = true; + + switch (int(e->type())) { + case InitEvent::TYPE: + qDebug() << "renderer" << this << "for window" << window << "is initializing"; + createRhi(); + init(); + break; + case RequestRenderEvent::TYPE: + thread->pendingRender = true; + thread->pendingRenderIsNewExpose = static_cast(e)->newlyExposed; + break; + case SurfaceCleanupEvent::TYPE: // when the QWindow is closed, before QPlatformWindow goes away + thread->mutex.lock(); + qDebug() << "renderer" << this << "for window" << window << "is destroying swapchain"; + thread->pendingRender = false; + releaseSwapChain(); + thread->cond.wakeOne(); + thread->mutex.unlock(); + break; + case CloseEvent::TYPE: // when destroying the window+renderer (NB not the same as hitting X on the window, that's just QWindow close) + qDebug() << "renderer" << this << "for window" << window << "is shutting down"; + thread->pendingRender = false; + thread->active = false; + thread->stopEventProcessing = true; + releaseResources(); + destroyRhi(); + break; + case SyncSurfaceSizeEvent::TYPE: + thread->mutex.lock(); + thread->pendingRender = false; + render(false, true); + break; + default: + break; + } +} + +void Renderer::init() +{ + m_sc = r->newSwapChain(); + m_ds = r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), // no need to set the size yet + 1, + QRhiRenderBuffer::UsedWithSwapChainOnly); + m_releasePool << m_ds; + m_sc->setWindow(window); + m_sc->setDepthStencil(m_ds); + m_rp = m_sc->newCompatibleRenderPassDescriptor(); + m_releasePool << m_rp; + m_sc->setRenderPassDescriptor(m_rp); + + m_vbuf = r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + m_releasePool << m_vbuf; + m_vbuf->build(); + + m_ubuf = r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4); + m_releasePool << m_ubuf; + m_ubuf->build(); + + QImage image = QImage(QLatin1String(":/qt256.png")).convertToFormat(QImage::Format_RGBA8888); + m_tex = r->newTexture(QRhiTexture::RGBA8, image.size()); + m_releasePool << m_tex; + m_tex->build(); + + m_sampler = r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + m_releasePool << m_sampler; + m_sampler->build(); + + m_srb = r->newShaderResourceBindings(); + m_releasePool << m_srb; + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, m_tex, m_sampler) + }); + m_srb->build(); + + m_ps = r->newGraphicsPipeline(); + m_releasePool << m_ps; + + m_ps->setDepthTest(true); + m_ps->setDepthWrite(true); + m_ps->setDepthOp(QRhiGraphicsPipeline::Less); + + m_ps->setCullMode(QRhiGraphicsPipeline::Back); + m_ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + m_ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + m_ps->setVertexInputLayout(inputLayout); + m_ps->setShaderResourceBindings(m_srb); + m_ps->setRenderPassDescriptor(m_rp); + + m_ps->build(); + + m_initialUpdates = r->nextResourceUpdateBatch(); + + m_initialUpdates->uploadStaticBuffer(m_vbuf, cube); + + qint32 flip = 0; + m_initialUpdates->updateDynamicBuffer(m_ubuf, 64, 4, &flip); + + m_initialUpdates->uploadTexture(m_tex, image); +} + +void Renderer::releaseSwapChain() +{ + if (m_hasSwapChain) { + m_hasSwapChain = false; + m_sc->release(); + } +} + +void Renderer::releaseResources() +{ + qDeleteAll(m_releasePool); + m_releasePool.clear(); + + delete m_sc; + m_sc = nullptr; +} + +void Renderer::render(bool newlyExposed, bool wakeBeforePresent) +{ + // This function handles both resizing and rendering. Resizes have some + // complications due to the threaded model (check exposeEvent and + // sendSyncSurfaceSize) but don't have to worry about that in here. + + auto buildOrResizeSwapChain = [this] { + qDebug() << "renderer" << this << "build or resize swapchain for window" << window; + const QSize outputSize = m_sc->surfacePixelSize(); + qDebug() << " size is" << outputSize; + m_ds->setPixelSize(outputSize); + m_ds->build(); + m_hasSwapChain = m_sc->buildOrResize(); + m_proj = r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); + }; + + auto wakeUpIfNeeded = [wakeBeforePresent, this] { + // make sure the main/gui thread is not blocked when issuing the Present (or equivalent) + if (wakeBeforePresent) { + thread->cond.wakeOne(); + thread->mutex.unlock(); + } + }; + + const QSize surfaceSize = m_sc->surfacePixelSize(); + if (surfaceSize.isEmpty()) { + wakeUpIfNeeded(); + return; + } + + if (newlyExposed || m_sc->currentPixelSize() != surfaceSize) + buildOrResizeSwapChain(); + + if (!m_hasSwapChain) { + wakeUpIfNeeded(); + return; + } + + QRhi::FrameOpResult result = r->beginFrame(m_sc); + if (result == QRhi::FrameOpSwapChainOutOfDate) { + buildOrResizeSwapChain(); + if (!m_hasSwapChain) { + wakeUpIfNeeded(); + return; + } + result = r->beginFrame(m_sc); + } + if (result != QRhi::FrameOpSuccess) { + wakeUpIfNeeded(); + return; + } + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSize = m_sc->currentPixelSize(); + + QRhiResourceUpdateBatch *u = r->nextResourceUpdateBatch(); + if (m_initialUpdates) { + u->merge(m_initialUpdates); + m_initialUpdates->release(); + m_initialUpdates = nullptr; + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.scale(0.5f); + mvp.rotate(m_rotation, m_rotationAxis == 0 ? 1 : 0, m_rotationAxis == 1 ? 1 : 0, m_rotationAxis == 2 ? 1 : 0); + u->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); + + cb->beginPass(m_sc->currentFrameRenderTarget(), + QColor::fromRgbF(float(m_bgColor.redF()), float(m_bgColor.greenF()), float(m_bgColor.blueF()), 1.0f), + { 1.0f, 0 }, + u); + + cb->setGraphicsPipeline(m_ps); + cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { m_vbuf, 0 }, + { m_vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); + + cb->endPass(); + + wakeUpIfNeeded(); + + r->endFrame(m_sc); + + m_frameCount += 1; + if ((m_frameCount % 300) == 0) { + const QRhiProfiler::CpuTime ff = r->profiler()->frameToFrameTimes(m_sc); + const QRhiProfiler::CpuTime be = r->profiler()->frameBuildTimes(m_sc); + const QRhiProfiler::GpuTime gp = r->profiler()->gpuFrameTimes(m_sc); + if (r->isFeatureSupported(QRhi::Timestamps)) { + qDebug("[renderer %p] frame-to-frame: min %lld max %lld avg %f. " + "frame build: min %lld max %lld avg %f. " + "gpu frame time: min %f max %f avg %f", + this, + ff.minTime, ff.maxTime, ff.avgTime, + be.minTime, be.maxTime, be.avgTime, + gp.minTime, gp.maxTime, gp.avgTime); + } else { + qDebug("[renderer %p] frame-to-frame: min %lld max %lld avg %f. " + "frame build: min %lld max %lld avg %f. ", + this, + ff.minTime, ff.maxTime, ff.avgTime, + be.minTime, be.maxTime, be.avgTime); + } + } +} + +void Renderer::sendInit() +{ // main thread + InitEvent *e = new InitEvent; + thread->eventQueue.addEvent(e); +} + +void Renderer::sendRender(bool newlyExposed) +{ // main thread + RequestRenderEvent *e = new RequestRenderEvent(newlyExposed); + thread->eventQueue.addEvent(e); +} + +void Renderer::sendSurfaceGoingAway() +{ // main thread + SurfaceCleanupEvent *e = new SurfaceCleanupEvent; + + // cannot let this thread to proceed with tearing down the native window + // before the render thread completes the swapchain release + thread->mutex.lock(); + + thread->eventQueue.addEvent(e); + + thread->cond.wait(&thread->mutex); + thread->mutex.unlock(); +} + +void Renderer::sendSyncSurfaceSize() +{ // main thread + SyncSurfaceSizeEvent *e = new SyncSurfaceSizeEvent; + // must block to prevent surface size mess. the render thread will do a + // full rendering round before it unlocks which is good since it can thus + // pick up and the surface (window) size atomically. + thread->mutex.lock(); + thread->eventQueue.addEvent(e); + thread->cond.wait(&thread->mutex); + thread->mutex.unlock(); +} + +struct WindowAndRenderer +{ + QWindow *window; + Renderer *renderer; +}; + +QVector windows; + +void createWindow() +{ + static QColor colors[] = { Qt::red, Qt::green, Qt::blue, Qt::yellow, Qt::cyan, Qt::gray }; + const int n = windows.count(); + Window *w = new Window(QString::asprintf("Window+Thread #%d (%s)", n, qPrintable(graphicsApiName())), graphicsApi); + Renderer *renderer = new Renderer(w, colors[n % 6], n % 3);; + QObject::connect(w, &Window::initRequested, w, [renderer] { + renderer->sendInit(); + }); + QObject::connect(w, &Window::renderRequested, w, [w, renderer](bool newlyExposed) { + renderer->sendRender(newlyExposed); + w->requestUpdate(); + }); + QObject::connect(w, &Window::surfaceGoingAway, w, [renderer] { + renderer->sendSurfaceGoingAway(); + }); + QObject::connect(w, &Window::syncSurfaceSizeRequested, w, [renderer] { + renderer->sendSyncSurfaceSize(); + }); + windows.append({ w, renderer }); + w->show(); +} + +void closeWindow() +{ + WindowAndRenderer wr = windows.takeLast(); + delete wr.renderer; + delete wr.window; +} + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + +#if defined(Q_OS_WIN) + graphicsApi = D3D11; +#elif defined(Q_OS_DARWIN) + graphicsApi = Metal; +#elif QT_CONFIG(vulkan) + graphicsApi = Vulkan; +#else + graphicsApi = OpenGL; +#endif + + QCommandLineParser cmdLineParser; + cmdLineParser.addHelpOption(); + QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)")); + cmdLineParser.addOption(glOption); + QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); + cmdLineParser.addOption(vkOption); + QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3dOption); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + cmdLineParser.process(app); + if (cmdLineParser.isSet(glOption)) + graphicsApi = OpenGL; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = Vulkan; + if (cmdLineParser.isSet(d3dOption)) + graphicsApi = D3D11; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = Metal; + + qDebug("Selected graphics API is %s", qPrintable(graphicsApiName())); + qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText())); + +#if QT_CONFIG(vulkan) + instance = new QVulkanInstance; + if (graphicsApi == Vulkan) { +#ifndef Q_OS_ANDROID + instance->setLayers({ "VK_LAYER_LUNARG_standard_validation" }); +#else + instance->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 (!instance->create()) { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = OpenGL; + } + } +#endif + + int winCount = 0; + QWidget w; + w.resize(800, 600); + w.setWindowTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + graphicsApiName()); + QVBoxLayout *layout = new QVBoxLayout(&w); + + QPlainTextEdit *info = new QPlainTextEdit( + QLatin1String("This application tests rendering on a separate thread per window, with dedicated QRhi instances and resources. " + "\n\nThis is the same concept as the Qt Quick Scenegraph's threaded render loop. This should allow rendering to the different windows " + "without unintentionally throttling each other's threads." + "\n\nUsing API: ") + graphicsApiName()); + info->setReadOnly(true); + layout->addWidget(info); + QLabel *label = new QLabel(QLatin1String("Window and thread count: 0")); + layout->addWidget(label); + QPushButton *btn = new QPushButton(QLatin1String("New window")); + QObject::connect(btn, &QPushButton::clicked, btn, [label, &winCount] { + winCount += 1; + label->setText(QString::asprintf("Window count: %d", winCount)); + createWindow(); + }); + layout->addWidget(btn); + btn = new QPushButton(QLatin1String("Close window")); + QObject::connect(btn, &QPushButton::clicked, btn, [label, &winCount] { + if (winCount > 0) { + winCount -= 1; + label->setText(QString::asprintf("Window count: %d", winCount)); + closeWindow(); + } + }); + layout->addWidget(btn); + w.show(); + + int result = app.exec(); + + for (const WindowAndRenderer &wr : windows) { + delete wr.renderer; + delete wr.window; + } + +#if QT_CONFIG(vulkan) + delete instance; +#endif + + return result; +} diff --git a/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.pro b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.pro new file mode 100644 index 0000000000..0b78518b9b --- /dev/null +++ b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.pro @@ -0,0 +1,12 @@ +TEMPLATE = app + +QT += gui-private widgets + +SOURCES = \ + multiwindow_threaded.cpp \ + window.cpp + +HEADERS = \ + window.h + +RESOURCES = multiwindow_threaded.qrc diff --git a/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.qrc b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.qrc new file mode 100644 index 0000000000..742b2b77af --- /dev/null +++ b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.qrc @@ -0,0 +1,7 @@ + + + ../shared/qt256.png + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + + diff --git a/tests/manual/rhi/multiwindow_threaded/window.cpp b/tests/manual/rhi/multiwindow_threaded/window.cpp new file mode 100644 index 0000000000..aa7b479047 --- /dev/null +++ b/tests/manual/rhi/multiwindow_threaded/window.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "window.h" +#include + +#ifndef QT_NO_OPENGL +#include +#endif + +#if QT_CONFIG(vulkan) +extern QVulkanInstance *instance; +#endif + +Window::Window(const QString &title, GraphicsApi api) +{ + switch (api) { + case OpenGL: +#if QT_CONFIG(opengl) + setSurfaceType(OpenGLSurface); + setFormat(QRhiGles2InitParams::adjustedFormat()); +#endif + break; + case Vulkan: +#if QT_CONFIG(vulkan) + setSurfaceType(VulkanSurface); + setVulkanInstance(instance); +#endif + break; + case D3D11: + setSurfaceType(OpenGLSurface); // not a typo + break; + case Metal: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + setSurfaceType(MetalSurface); +#endif + break; + default: + break; + } + + resize(800, 600); + setTitle(title); +} + +Window::~Window() +{ +} + +void Window::exposeEvent(QExposeEvent *) +{ + if (isExposed()) { + if (!m_running) { + // initialize and start rendering when the window becomes usable for graphics purposes + m_running = true; + m_notExposed = false; + emit initRequested(); + emit renderRequested(true); + } else { + // continue when exposed again + if (m_notExposed) { + m_notExposed = false; + emit renderRequested(true); + } else { + // resize generates exposes - this is very important here (unlike in a single-threaded renderer) + emit syncSurfaceSizeRequested(); + } + } + } else { + // stop pushing frames when not exposed (on some platforms this is essential, optional on others) + if (m_running) + m_notExposed = true; + } +} + +bool Window::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::UpdateRequest: + if (!m_notExposed) + emit renderRequested(false); + break; + + case QEvent::PlatformSurface: + // this is the proper time to tear down the swapchain (while the native window and surface are still around) + if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) + emit surfaceGoingAway(); + break; + + default: + break; + } + + return QWindow::event(e); +} diff --git a/tests/manual/rhi/multiwindow_threaded/window.h b/tests/manual/rhi/multiwindow_threaded/window.h new file mode 100644 index 0000000000..7810858d0a --- /dev/null +++ b/tests/manual/rhi/multiwindow_threaded/window.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 WINDOW_H +#define WINDOW_H + +#include + +enum GraphicsApi +{ + OpenGL, + Vulkan, + D3D11, + Metal +}; + +class Window : public QWindow +{ + Q_OBJECT + +public: + Window(const QString &title, GraphicsApi api); + ~Window(); + + void exposeEvent(QExposeEvent *) override; + bool event(QEvent *) override; + +signals: + void initRequested(); + void renderRequested(bool newlyExposed); + void surfaceGoingAway(); + void syncSurfaceSizeRequested(); + +protected: + bool m_running = false; + bool m_notExposed = true; +}; + +#endif diff --git a/tests/manual/rhi/offscreen/offscreen.cpp b/tests/manual/rhi/offscreen/offscreen.cpp new file mode 100644 index 0000000000..838dc9d2c8 --- /dev/null +++ b/tests/manual/rhi/offscreen/offscreen.cpp @@ -0,0 +1,366 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 +#include +#include +#include +#include +#include +#include + +#include + +#ifndef QT_NO_OPENGL +#include +#include +#endif + +#if QT_CONFIG(vulkan) +#include +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_DARWIN +#include +#endif + +//#define TEST_FINISH + +static float vertexData[] = { // Y up (note m_proj), CCW + 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, +}; + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +enum GraphicsApi +{ + OpenGL, + Vulkan, + D3D11, + Metal, + Null +}; + +GraphicsApi graphicsApi; + +QString graphicsApiName() +{ + switch (graphicsApi) { + case OpenGL: + return QLatin1String("OpenGL 2.x"); + case Vulkan: + return QLatin1String("Vulkan"); + case D3D11: + return QLatin1String("Direct3D 11"); + case Metal: + return QLatin1String("Metal"); + case Null: + return QLatin1String("Null"); + default: + break; + } + return QString(); +} + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + +#if defined(Q_OS_WIN) + graphicsApi = D3D11; +#elif defined(Q_OS_DARWIN) + graphicsApi = Metal; +#elif QT_CONFIG(vulkan) + graphicsApi = Vulkan; +#else + graphicsApi = OpenGL; +#endif + + QCommandLineParser cmdLineParser; + cmdLineParser.addHelpOption(); + QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)")); + cmdLineParser.addOption(glOption); + QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); + cmdLineParser.addOption(vkOption); + QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3dOption); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); + cmdLineParser.addOption(nullOption); + cmdLineParser.process(app); + if (cmdLineParser.isSet(glOption)) + graphicsApi = OpenGL; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = Vulkan; + if (cmdLineParser.isSet(d3dOption)) + graphicsApi = D3D11; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = Metal; + if (cmdLineParser.isSet(nullOption)) + graphicsApi = Null; + + qDebug("Selected graphics API is %s", qPrintable(graphicsApiName())); + qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText())); + + QRhi *r = nullptr; + + if (graphicsApi == Null) { + QRhiNullInitParams params; + r = QRhi::create(QRhi::Null, ¶ms); + } + +#if QT_CONFIG(vulkan) + QVulkanInstance inst; + if (graphicsApi == Vulkan) { + 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()) { + QRhiVulkanInitParams params; + params.inst = &inst; + r = QRhi::create(QRhi::Vulkan, ¶ms); + } else { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = OpenGL; + } + } +#endif + +#ifndef QT_NO_OPENGL + QScopedPointer offscreenSurface; + if (graphicsApi == OpenGL) { + offscreenSurface.reset(QRhiGles2InitParams::newFallbackSurface()); + QRhiGles2InitParams params; + params.fallbackSurface = offscreenSurface.data(); + r = QRhi::create(QRhi::OpenGLES2, ¶ms); + } +#endif + +#ifdef Q_OS_WIN + if (graphicsApi == D3D11) { + QRhiD3D11InitParams params; + params.enableDebugLayer = true; + r = QRhi::create(QRhi::D3D11, ¶ms); + } +#endif + +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + QRhiMetalInitParams params; + r = QRhi::create(QRhi::Metal, ¶ms); + } +#endif + + if (!r) + qFatal("Failed to initialize RHI"); + + QRhiTexture *tex = r->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource); + tex->build(); + QRhiTextureRenderTarget *rt = r->newTextureRenderTarget({ tex }); + QRhiRenderPassDescriptor *rp = rt->newCompatibleRenderPassDescriptor(); + rt->setRenderPassDescriptor(rp); + rt->build(); + + QMatrix4x4 proj = r->clipSpaceCorrMatrix(); + proj.perspective(45.0f, 1280 / 720.f, 0.01f, 1000.0f); + proj.translate(0, 0, -4); + + QRhiBuffer *vbuf = r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); + vbuf->build(); + + QRhiBuffer *ubuf = r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + ubuf->build(); + + QRhiShaderResourceBindings *srb = r->newShaderResourceBindings(); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf) + }); + srb->build(); + + QRhiGraphicsPipeline *ps = r->newGraphicsPipeline(); + + QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; + premulAlphaBlend.enable = true; + ps->setTargetBlends({ premulAlphaBlend }); + + const QShader vs = getShader(QLatin1String(":/color.vert.qsb")); + if (!vs.isValid()) + qFatal("Failed to load shader pack (vertex)"); + const QShader fs = getShader(QLatin1String(":/color.frag.qsb")); + if (!fs.isValid()) + qFatal("Failed to load shader pack (fragment)"); + + ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + + ps->setVertexInputLayout(inputLayout); + ps->setShaderResourceBindings(srb); + ps->setRenderPassDescriptor(rp); + ps->build(); + + int frame = 0; + for (; frame < 20; ++frame) { + QRhiCommandBuffer *cb; + if (r->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) + break; + + qDebug("Generating offscreen frame %d", frame); + QRhiResourceUpdateBatch *u = r->nextResourceUpdateBatch(); + if (frame == 0) + u->uploadStaticBuffer(vbuf, vertexData); + + static float rotation = 0.0f; + QMatrix4x4 mvp = proj; + mvp.rotate(rotation, 0, 1, 0); + u->updateDynamicBuffer(ubuf, 0, 64, mvp.constData()); + rotation += 5.0f; + static float opacity = 1.0f; + static int opacityDir= 1; + u->updateDynamicBuffer(ubuf, 64, 4, &opacity); + opacity += opacityDir * 0.005f; + if (opacity < 0.0f || opacity > 1.0f) { + opacityDir *= -1; + opacity = qBound(0.0f, opacity, 1.0f); + } + + cb->beginPass(rt, { 0, 1, 0, 1 }, { 1, 0 }, u); + cb->setGraphicsPipeline(ps); + cb->setViewport({ 0, 0, 1280, 720 }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + u = r->nextResourceUpdateBatch(); + QRhiReadbackDescription rb(tex); + QRhiReadbackResult rbResult; + rbResult.completed = [frame] { qDebug(" - readback %d completed", frame); }; + u->readBackTexture(rb, &rbResult); + + cb->endPass(u); + + qDebug("Submit and wait"); +#ifdef TEST_FINISH + r->finish(); +#else + r->endOffscreenFrame(); +#endif + // The data should be ready either because endOffscreenFrame() waits + // for completion or because finish() did. + if (!rbResult.data.isEmpty()) { + const uchar *p = reinterpret_cast(rbResult.data.constData()); + QImage image(p, rbResult.pixelSize.width(), rbResult.pixelSize.height(), QImage::Format_RGBA8888); + QString fn = QString::asprintf("frame%d.png", frame); + fn = QFileInfo(fn).absoluteFilePath(); + qDebug("Saving into %s", qPrintable(fn)); + if (r->isYUpInFramebuffer()) + image.mirrored().save(fn); + else + image.save(fn); + } else { + qWarning("Readback failed!"); + } +#ifdef TEST_FINISH + r->endOffscreenFrame(); +#endif + } + + delete ps; + delete srb; + delete ubuf; + delete vbuf; + + delete rt; + delete rp; + delete tex; + + delete r; + + qDebug("\nRendered and read back %d frames using %s", frame, qPrintable(graphicsApiName())); + + return 0; +} diff --git a/tests/manual/rhi/offscreen/offscreen.pro b/tests/manual/rhi/offscreen/offscreen.pro new file mode 100644 index 0000000000..ae040a4b6c --- /dev/null +++ b/tests/manual/rhi/offscreen/offscreen.pro @@ -0,0 +1,9 @@ +TEMPLATE = app +CONFIG += console + +QT += gui-private + +SOURCES = \ + offscreen.cpp + +RESOURCES = offscreen.qrc diff --git a/tests/manual/rhi/offscreen/offscreen.qrc b/tests/manual/rhi/offscreen/offscreen.qrc new file mode 100644 index 0000000000..8cec4fa9ec --- /dev/null +++ b/tests/manual/rhi/offscreen/offscreen.qrc @@ -0,0 +1,6 @@ + + + ../shared/color.vert.qsb + ../shared/color.frag.qsb + + diff --git a/tests/manual/rhi/qrhiprof/qrhiprof.cpp b/tests/manual/rhi/qrhiprof/qrhiprof.cpp new file mode 100644 index 0000000000..7e3027a951 --- /dev/null +++ b/tests/manual/rhi/qrhiprof/qrhiprof.cpp @@ -0,0 +1,671 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// Simple QRhiProfiler receiver app. Start it and then in a QRhi-based +// application connect with a QTcpSocket to 127.0.0.1:30667 and set that as the +// QRhiProfiler's device. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int MIN_KNOWN_OP = 1; +const int MAX_KNOWN_OP = 18; + +class Parser : public QObject +{ + Q_OBJECT + +public: + void feed(const QByteArray &line); + + struct Event { + QRhiProfiler::StreamOp op; + qint64 timestamp; + quint64 resource; + QByteArray resourceName; + + struct Param { + enum ValueType { + Int64, + Float + }; + QByteArray key; + ValueType valueType; + union { + qint64 intValue; + float floatValue; + }; + }; + + QVector params; + + const Param *param(const char *key) const { + auto it = std::find_if(params.cbegin(), params.cend(), [key](const Param &p) { + return !strcmp(p.key.constData(), key); + }); + return it == params.cend() ? nullptr : &*it; + } + }; + +signals: + void eventReceived(const Event &e); +}; + +void Parser::feed(const QByteArray &line) +{ + const QList elems = line.split(','); + if (elems.count() < 4) { + qWarning("Malformed line '%s'", line.constData()); + return; + } + bool ok = false; + const int op = elems[0].toInt(&ok); + if (!ok) { + qWarning("Invalid op %s", elems[0].constData()); + return; + } + if (op < MIN_KNOWN_OP || op > MAX_KNOWN_OP) { + qWarning("Unknown op %d", op); + return; + } + + Event e; + e.op = QRhiProfiler::StreamOp(op); + e.timestamp = elems[1].toLongLong(); + e.resource = elems[2].toULongLong(); + e.resourceName = elems[3]; + + const int elemCount = elems.count(); + for (int i = 4; i < elemCount; i += 2) { + if (i + 1 < elemCount && !elems[i].isEmpty() && !elems[i + 1].isEmpty()) { + QByteArray key = elems[i]; + if (key.startsWith('F')) { + key = key.mid(1); + bool ok = false; + const float value = elems[i + 1].toFloat(&ok); + if (!ok) { + qWarning("Failed to parse float %s in line '%s'", elems[i + 1].constData(), line.constData()); + continue; + } + Event::Param param; + param.key = key; + param.valueType = Event::Param::Float; + param.floatValue = value; + e.params.append(param); + } else { + const qint64 value = elems[i + 1].toLongLong(); + Event::Param param; + param.key = key; + param.valueType = Event::Param::Int64; + param.intValue = value; + e.params.append(param); + } + } + } + + emit eventReceived(e); +} + +class Tracker : public QObject +{ + Q_OBJECT + +public slots: + void handleEvent(const Parser::Event &e); + +signals: + void buffersTouched(); + void texturesTouched(); + void swapchainsTouched(); + void frameTimeTouched(); + void gpuFrameTimeTouched(); + void gpuMemAllocStatsTouched(); + +public: + Tracker() { + reset(); + } + + static const int MAX_STAGING_SLOTS = 3; + + struct Buffer { + Buffer() + { + memset(stagingExtraSize, 0, sizeof(stagingExtraSize)); + } + quint64 lastTimestamp; + QByteArray resourceName; + qint64 effectiveSize = 0; + int backingGpuBufCount = 1; + qint64 stagingExtraSize[MAX_STAGING_SLOTS]; + }; + QHash m_buffers; + qint64 m_totalBufferApproxByteSize; + qint64 m_peakBufferApproxByteSize; + qint64 m_totalStagingBufferApproxByteSize; + qint64 m_peakStagingBufferApproxByteSize; + + struct Texture { + Texture() + { + memset(stagingExtraSize, 0, sizeof(stagingExtraSize)); + } + quint64 lastTimestamp; + QByteArray resourceName; + qint64 approxByteSize = 0; + bool ownsNativeResource = true; + qint64 stagingExtraSize[MAX_STAGING_SLOTS]; + }; + QHash m_textures; + qint64 m_totalTextureApproxByteSize; + qint64 m_peakTextureApproxByteSize; + qint64 m_totalTextureStagingBufferApproxByteSize; + qint64 m_peakTextureStagingBufferApproxByteSize; + + struct SwapChain { + quint64 lastTimestamp; + QByteArray resourceName; + qint64 approxByteSize = 0; + }; + QHash m_swapchains; + qint64 m_totalSwapChainApproxByteSize; + qint64 m_peakSwapChainApproxByteSize; + + struct FrameTime { + qint64 framesSinceResize = 0; + int minDelta = 0; + int maxDelta = 0; + float avgDelta = 0; + }; + FrameTime m_lastFrameTime; + + struct GpuFrameTime { + float minTime = 0; + float maxTime = 0; + float avgTime = 0; + }; + GpuFrameTime m_lastGpuFrameTime; + + struct GpuMemAllocStats { + qint64 realAllocCount; + qint64 subAllocCount; + qint64 totalSize; + qint64 unusedSize; + }; + GpuMemAllocStats m_lastGpuMemAllocStats; + + void reset() { + m_buffers.clear(); + m_textures.clear(); + m_totalBufferApproxByteSize = 0; + m_peakBufferApproxByteSize = 0; + m_totalStagingBufferApproxByteSize = 0; + m_peakStagingBufferApproxByteSize = 0; + m_totalTextureApproxByteSize = 0; + m_peakTextureApproxByteSize = 0; + m_totalTextureStagingBufferApproxByteSize = 0; + m_peakTextureStagingBufferApproxByteSize = 0; + m_totalSwapChainApproxByteSize = 0; + m_peakSwapChainApproxByteSize = 0; + m_lastFrameTime = FrameTime(); + m_lastGpuFrameTime = GpuFrameTime(); + m_lastGpuMemAllocStats = GpuMemAllocStats(); + emit buffersTouched(); + emit texturesTouched(); + emit swapchainsTouched(); + emit frameTimeTouched(); + emit gpuFrameTimeTouched(); + emit gpuMemAllocStatsTouched(); + } +}; + +Q_DECLARE_TYPEINFO(Tracker::Buffer, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(Tracker::Texture, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(Tracker::FrameTime, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(Tracker::GpuFrameTime, Q_MOVABLE_TYPE); + +void Tracker::handleEvent(const Parser::Event &e) +{ + switch (e.op) { + case QRhiProfiler::NewBuffer: + { + Buffer b; + b.lastTimestamp = e.timestamp; + b.resourceName = e.resourceName; + // type,0,usage,1,logical_size,84,effective_size,84,backing_gpu_buf_count,1,backing_cpu_buf_count,0 + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("effective_size")) + b.effectiveSize = p.intValue; + else if (p.key == QByteArrayLiteral("backing_gpu_buf_count")) + b.backingGpuBufCount = p.intValue; + } + m_totalBufferApproxByteSize += b.effectiveSize * b.backingGpuBufCount; + m_peakBufferApproxByteSize = qMax(m_peakBufferApproxByteSize, m_totalBufferApproxByteSize); + m_buffers.insert(e.resource, b); + emit buffersTouched(); + } + break; + case QRhiProfiler::ReleaseBuffer: + { + auto it = m_buffers.find(e.resource); + if (it != m_buffers.end()) { + m_totalBufferApproxByteSize -= it->effectiveSize * it->backingGpuBufCount; + m_buffers.erase(it); + emit buffersTouched(); + } + } + break; + + case QRhiProfiler::NewBufferStagingArea: + { + qint64 slot = -1; + qint64 size = 0; + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("slot")) + slot = p.intValue; + else if (p.key == QByteArrayLiteral("size")) + size = p.intValue; + } + if (slot >= 0 && slot < MAX_STAGING_SLOTS) { + auto it = m_buffers.find(e.resource); + if (it != m_buffers.end()) { + it->stagingExtraSize[slot] = size; + m_totalStagingBufferApproxByteSize += size; + m_peakStagingBufferApproxByteSize = qMax(m_peakStagingBufferApproxByteSize, m_totalStagingBufferApproxByteSize); + emit buffersTouched(); + } + } + } + break; + case QRhiProfiler::ReleaseBufferStagingArea: + { + qint64 slot = -1; + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("slot")) + slot = p.intValue; + } + if (slot >= 0 && slot < MAX_STAGING_SLOTS) { + auto it = m_buffers.find(e.resource); + if (it != m_buffers.end()) { + m_totalStagingBufferApproxByteSize -= it->stagingExtraSize[slot]; + it->stagingExtraSize[slot] = 0; + emit buffersTouched(); + } + } + } + break; + + case QRhiProfiler::NewTexture: + { + Texture t; + t.lastTimestamp = e.timestamp; + t.resourceName = e.resourceName; + // width,256,height,256,format,1,owns_native_resource,1,mip_count,9,layer_count,1,effective_sample_count,1,approx_byte_size,349524 + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("approx_byte_size")) + t.approxByteSize = p.intValue; + else if (p.key == QByteArrayLiteral("owns_native_resource")) + t.ownsNativeResource = p.intValue; + } + if (t.ownsNativeResource) { + m_totalTextureApproxByteSize += t.approxByteSize; + m_peakTextureApproxByteSize = qMax(m_peakTextureApproxByteSize, m_totalTextureApproxByteSize); + } + m_textures.insert(e.resource, t); + emit texturesTouched(); + } + break; + case QRhiProfiler::ReleaseTexture: + { + auto it = m_textures.find(e.resource); + if (it != m_textures.end()) { + if (it->ownsNativeResource) + m_totalTextureApproxByteSize -= it->approxByteSize; + m_textures.erase(it); + emit texturesTouched(); + } + } + break; + + case QRhiProfiler::NewTextureStagingArea: + { + qint64 slot = -1; + qint64 size = 0; + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("slot")) + slot = p.intValue; + else if (p.key == QByteArrayLiteral("size")) + size = p.intValue; + } + if (slot >= 0 && slot < MAX_STAGING_SLOTS) { + auto it = m_textures.find(e.resource); + if (it != m_textures.end()) { + it->stagingExtraSize[slot] = size; + m_totalTextureStagingBufferApproxByteSize += size; + m_peakTextureStagingBufferApproxByteSize = qMax(m_peakTextureStagingBufferApproxByteSize, m_totalTextureStagingBufferApproxByteSize); + emit texturesTouched(); + } + } + } + break; + case QRhiProfiler::ReleaseTextureStagingArea: + { + qint64 slot = -1; + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("slot")) + slot = p.intValue; + } + if (slot >= 0 && slot < MAX_STAGING_SLOTS) { + auto it = m_textures.find(e.resource); + if (it != m_textures.end()) { + m_totalTextureStagingBufferApproxByteSize -= it->stagingExtraSize[slot]; + it->stagingExtraSize[slot] = 0; + emit texturesTouched(); + } + } + } + break; + + case QRhiProfiler::ResizeSwapChain: + { + auto it = m_swapchains.find(e.resource); + if (it != m_swapchains.end()) + m_totalSwapChainApproxByteSize -= it->approxByteSize; + + SwapChain s; + s.lastTimestamp = e.timestamp; + s.resourceName = e.resourceName; + // width,1280,height,720,buffer_count,2,msaa_buffer_count,0,effective_sample_count,1,approx_total_byte_size,7372800 + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("approx_total_byte_size")) + s.approxByteSize = p.intValue; + } + m_totalSwapChainApproxByteSize += s.approxByteSize; + m_peakSwapChainApproxByteSize = qMax(m_peakSwapChainApproxByteSize, m_totalSwapChainApproxByteSize); + m_swapchains.insert(e.resource, s); + emit swapchainsTouched(); + } + break; + case QRhiProfiler::ReleaseSwapChain: + { + auto it = m_swapchains.find(e.resource); + if (it != m_swapchains.end()) { + m_totalSwapChainApproxByteSize -= it->approxByteSize; + m_swapchains.erase(it); + emit swapchainsTouched(); + } + } + break; + + case QRhiProfiler::GpuFrameTime: + { + // Fmin_ms_gpu_frame_time,0.15488,Fmax_ms_gpu_frame_time,0.494592,Favg_ms_gpu_frame_time,0.33462 + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("min_ms_gpu_frame_time")) + m_lastGpuFrameTime.minTime = p.floatValue; + else if (p.key == QByteArrayLiteral("max_ms_gpu_frame_time")) + m_lastGpuFrameTime.maxTime = p.floatValue; + else if (p.key == QByteArrayLiteral("avg_ms_gpu_frame_time")) + m_lastGpuFrameTime.avgTime = p.floatValue; + } + emit gpuFrameTimeTouched(); + } + break; + case QRhiProfiler::FrameToFrameTime: + { + // frames_since_resize,121,min_ms_frame_delta,9,max_ms_frame_delta,33,Favg_ms_frame_delta,16.1167 + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("frames_since_resize")) + m_lastFrameTime.framesSinceResize = p.intValue; + else if (p.key == QByteArrayLiteral("min_ms_frame_delta")) + m_lastFrameTime.minDelta = p.intValue; + else if (p.key == QByteArrayLiteral("max_ms_frame_delta")) + m_lastFrameTime.maxDelta = p.intValue; + else if (p.key == QByteArrayLiteral("avg_ms_frame_delta")) + m_lastFrameTime.avgDelta = p.floatValue; + } + emit frameTimeTouched(); + } + break; + + case QRhiProfiler::GpuMemAllocStats: + { + // real_alloc_count,2,sub_alloc_count,154,total_size,10752,unused_size,50320896 + for (const Parser::Event::Param &p : e.params) { + if (p.key == QByteArrayLiteral("real_alloc_count")) + m_lastGpuMemAllocStats.realAllocCount = p.intValue; + else if (p.key == QByteArrayLiteral("sub_alloc_count")) + m_lastGpuMemAllocStats.subAllocCount = p.intValue; + else if (p.key == QByteArrayLiteral("total_size")) + m_lastGpuMemAllocStats.totalSize = p.intValue; + else if (p.key == QByteArrayLiteral("unused_size")) + m_lastGpuMemAllocStats.unusedSize = p.intValue; + } + emit gpuMemAllocStatsTouched(); + } + break; + + default: + break; + } +} + +class Server : public QTcpServer +{ + Q_OBJECT + +protected: + void incomingConnection(qintptr socketDescriptor) override; + +signals: + void clientConnected(); + void clientDisconnected(); + void receiveStarted(); + void lineReceived(const QByteArray &line); + +private: + bool m_valid = false; + QTcpSocket m_socket; + QByteArray m_buf; +}; + +void Server::incomingConnection(qintptr socketDescriptor) +{ + if (m_valid) + return; + + m_socket.setSocketDescriptor(socketDescriptor); + m_valid = true; + emit clientConnected(); + connect(&m_socket, &QAbstractSocket::readyRead, this, [this] { + bool receiveStartedSent = false; + m_buf += m_socket.readAll(); + while (m_buf.contains('\n')) { + const int lfpos = m_buf.indexOf('\n'); + const QByteArray line = m_buf.left(lfpos).trimmed(); + m_buf = m_buf.mid(lfpos + 1); + if (!receiveStartedSent) { + receiveStartedSent = true; + emit receiveStarted(); + } + emit lineReceived(line); + } + }); + connect(&m_socket, &QAbstractSocket::disconnected, this, [this] { + if (m_valid) { + m_valid = false; + emit clientDisconnected(); + } + }); +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + Tracker tracker; + Parser parser; + QObject::connect(&parser, &Parser::eventReceived, &tracker, &Tracker::handleEvent); + + Server server; + if (!server.listen(QHostAddress::Any, 30667)) + qFatal("Failed to start server: %s", qPrintable(server.errorString())); + + QVBoxLayout *layout = new QVBoxLayout; + + QLabel *infoLabel = new QLabel(QLatin1String("Launch a Qt Quick application with QSG_RHI_PROFILE=1 and QSG_RHI_PROFILE_HOST set to the IP address.
" + "(resource memory usage reporting works best with the Vulkan backend)
")); + layout->addWidget(infoLabel); + + QGroupBox *groupBox = new QGroupBox(QLatin1String("RHI statistics")); + QVBoxLayout *groupLayout = new QVBoxLayout; + + QLabel *buffersLabel = new QLabel; + QObject::connect(&tracker, &Tracker::buffersTouched, buffersLabel, [buffersLabel, &tracker] { + const QString msg = QString::asprintf("%d buffers with ca. %lld bytes of current memory (sub)allocations (peak %lld) + %lld bytes of known staging buffers (peak %lld)", + tracker.m_buffers.count(), + tracker.m_totalBufferApproxByteSize, tracker.m_peakBufferApproxByteSize, + tracker.m_totalStagingBufferApproxByteSize, tracker.m_peakStagingBufferApproxByteSize); + buffersLabel->setText(msg); + }); + groupLayout->addWidget(buffersLabel); + + QLabel *texturesLabel = new QLabel; + QObject::connect(&tracker, &Tracker::texturesTouched, texturesLabel, [texturesLabel, &tracker] { + const QString msg = QString::asprintf("%d textures with ca. %lld bytes of current memory (sub)allocations (peak %lld) + %lld bytes of known staging buffers (peak %lld)", + tracker.m_textures.count(), + tracker.m_totalTextureApproxByteSize, tracker.m_peakTextureApproxByteSize, + tracker.m_totalTextureStagingBufferApproxByteSize, tracker.m_peakTextureStagingBufferApproxByteSize); + texturesLabel->setText(msg); + }); + groupLayout->addWidget(texturesLabel); + + QLabel *swapchainsLabel = new QLabel; + QObject::connect(&tracker, &Tracker::swapchainsTouched, swapchainsLabel, [swapchainsLabel, &tracker] { + const QString msg = QString::asprintf("Estimated total swapchain color buffer size is %lld bytes (peak %lld)", + tracker.m_totalSwapChainApproxByteSize, tracker.m_peakSwapChainApproxByteSize); + swapchainsLabel->setText(msg); + }); + groupLayout->addWidget(swapchainsLabel); + + QLabel *frameTimeLabel = new QLabel; + QObject::connect(&tracker, &Tracker::frameTimeTouched, frameTimeLabel, [frameTimeLabel, &tracker] { + const QString msg = QString::asprintf("Frames since resize %lld Frame delta min %d ms max %d ms avg %f ms", + tracker.m_lastFrameTime.framesSinceResize, + tracker.m_lastFrameTime.minDelta, + tracker.m_lastFrameTime.maxDelta, + tracker.m_lastFrameTime.avgDelta); + frameTimeLabel->setText(msg); + }); + groupLayout->addWidget(frameTimeLabel); + + QLabel *gpuFrameTimeLabel = new QLabel; + QObject::connect(&tracker, &Tracker::gpuFrameTimeTouched, gpuFrameTimeLabel, [gpuFrameTimeLabel, &tracker] { + const QString msg = QString::asprintf("GPU frame time min %f ms max %f ms avg %f ms", + tracker.m_lastGpuFrameTime.minTime, + tracker.m_lastGpuFrameTime.maxTime, + tracker.m_lastGpuFrameTime.avgTime); + gpuFrameTimeLabel->setText(msg); + }); + groupLayout->addWidget(gpuFrameTimeLabel); + + QLabel *gpuMemAllocStatsLabel = new QLabel; + QObject::connect(&tracker, &Tracker::gpuMemAllocStatsTouched, gpuMemAllocStatsLabel, [gpuMemAllocStatsLabel, &tracker] { + const QString msg = QString::asprintf("GPU memory allocator status: %lld real allocations %lld sub-allocations %lld total bytes %lld unused bytes", + tracker.m_lastGpuMemAllocStats.realAllocCount, + tracker.m_lastGpuMemAllocStats.subAllocCount, + tracker.m_lastGpuMemAllocStats.totalSize, + tracker.m_lastGpuMemAllocStats.unusedSize); + gpuMemAllocStatsLabel->setText(msg); + }); + groupLayout->addWidget(gpuMemAllocStatsLabel); + + groupBox->setLayout(groupLayout); + layout->addWidget(groupBox); + + QTextEdit *rawLog = new QTextEdit; + rawLog->setReadOnly(true); + layout->addWidget(rawLog); + + QObject::connect(&server, &Server::clientConnected, rawLog, [rawLog] { + rawLog->append(QLatin1String("\nCONNECTED\n")); + }); + QObject::connect(&server, &Server::clientDisconnected, rawLog, [rawLog, &tracker] { + rawLog->append(QLatin1String("\nDISCONNECTED\n")); + tracker.reset(); + }); + QObject::connect(&server, &Server::receiveStarted, rawLog, [rawLog] { + rawLog->setFontItalic(true); + rawLog->append(QLatin1String("[") + QTime::currentTime().toString() + QLatin1String("]")); + rawLog->setFontItalic(false); + }); + + QObject::connect(&server, &Server::lineReceived, rawLog, [rawLog, &parser](const QByteArray &line) { + rawLog->append(QString::fromUtf8(line)); + parser.feed(line); + }); + + QWidget w; + w.resize(800, 600); + w.setLayout(layout); + w.show(); + + return app.exec(); +} + +#include "qrhiprof.moc" diff --git a/tests/manual/rhi/qrhiprof/qrhiprof.pro b/tests/manual/rhi/qrhiprof/qrhiprof.pro new file mode 100644 index 0000000000..2939dd7ce2 --- /dev/null +++ b/tests/manual/rhi/qrhiprof/qrhiprof.pro @@ -0,0 +1,6 @@ +TEMPLATE = app + +QT += network widgets gui-private + +SOURCES = \ + qrhiprof.cpp diff --git a/tests/manual/rhi/rhi.pro b/tests/manual/rhi/rhi.pro new file mode 100644 index 0000000000..1a65f84a89 --- /dev/null +++ b/tests/manual/rhi/rhi.pro @@ -0,0 +1,22 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + hellominimalcrossgfxtriangle \ + compressedtexture_bc1 \ + compressedtexture_bc1_subupload \ + texuploads \ + msaatexture \ + msaarenderbuffer \ + cubemap \ + multiwindow \ + multiwindow_threaded \ + triquadcube \ + offscreen \ + floattexture \ + mrt \ + shadowmap + +qtConfig(widgets) { + SUBDIRS += \ + qrhiprof +} diff --git a/tests/manual/rhi/shadowmap/buildshaders.bat b/tests/manual/rhi/shadowmap/buildshaders.bat new file mode 100644 index 0000000000..7b84cc0952 --- /dev/null +++ b/tests/manual/rhi/shadowmap/buildshaders.bat @@ -0,0 +1,4 @@ +qsb --glsl "120,300 es" --hlsl 50 --msl 12 -c shadowmap.vert -o shadowmap.vert.qsb +qsb --glsl "120,300 es" --hlsl 50 --msl 12 -c shadowmap.frag -o shadowmap.frag.qsb +qsb --glsl "120,300 es" --hlsl 50 --msl 12 -c main.vert -o main.vert.qsb +qsb --glsl "120,300 es" --hlsl 50 --msl 12 -c main.frag -o main.frag.qsb diff --git a/tests/manual/rhi/shadowmap/buildshaders.sh b/tests/manual/rhi/shadowmap/buildshaders.sh new file mode 100755 index 0000000000..c4d17841e6 --- /dev/null +++ b/tests/manual/rhi/shadowmap/buildshaders.sh @@ -0,0 +1,4 @@ +qsb --glsl "120,300 es" --hlsl 50 --msl 12 shadowmap.vert -o shadowmap.vert.qsb +qsb --glsl "120,300 es" --hlsl 50 --msl 12 shadowmap.frag -o shadowmap.frag.qsb +qsb --glsl "120,300 es" --hlsl 50 --msl 12 main.vert -o main.vert.qsb +qsb --glsl "120,300 es" --hlsl 50 --msl 12 main.frag -o main.frag.qsb diff --git a/tests/manual/rhi/shadowmap/main.frag b/tests/manual/rhi/shadowmap/main.frag new file mode 100644 index 0000000000..8fefe2bb42 --- /dev/null +++ b/tests/manual/rhi/shadowmap/main.frag @@ -0,0 +1,30 @@ +#version 440 + +layout(location = 0) in vec4 vLCVertPos; + +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2DShadow shadowMap; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + mat4 lightMvp; + mat4 shadowBias; + int useShadow; +} ubuf; + +void main() +{ + vec4 adjustedLcVertPos = vLCVertPos; + adjustedLcVertPos.z -= 0.0001; // bias to avoid acne + + // no textureProj, that seems to end up not doing the perspective divide for z (?) + vec3 v = adjustedLcVertPos.xyz / adjustedLcVertPos.w; + float sc = texture(shadowMap, v); // sampler is comparison enabled so compares to z + + float shadowFactor = 0.2; + if (sc > 0 || ubuf.useShadow == 0) + shadowFactor = 1.0; + + fragColor = vec4(0.5, 0.3 + ubuf.useShadow * 0.2, 0.7, 1.0) * shadowFactor; +} diff --git a/tests/manual/rhi/shadowmap/main.frag.qsb b/tests/manual/rhi/shadowmap/main.frag.qsb new file mode 100644 index 0000000000..3001908b85 Binary files /dev/null and b/tests/manual/rhi/shadowmap/main.frag.qsb differ diff --git a/tests/manual/rhi/shadowmap/main.vert b/tests/manual/rhi/shadowmap/main.vert new file mode 100644 index 0000000000..9d6cade634 --- /dev/null +++ b/tests/manual/rhi/shadowmap/main.vert @@ -0,0 +1,20 @@ +#version 440 + +layout(location = 0) in vec4 position; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + mat4 lightMvp; + mat4 shadowBias; + int useShadow; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +layout(location = 0) out vec4 vLCVertPos; + +void main() +{ + vLCVertPos = ubuf.shadowBias * ubuf.lightMvp * position; + gl_Position = ubuf.mvp * position; +} diff --git a/tests/manual/rhi/shadowmap/main.vert.qsb b/tests/manual/rhi/shadowmap/main.vert.qsb new file mode 100644 index 0000000000..8b81a93c00 Binary files /dev/null and b/tests/manual/rhi/shadowmap/main.vert.qsb differ diff --git a/tests/manual/rhi/shadowmap/shadowmap.cpp b/tests/manual/rhi/shadowmap/shadowmap.cpp new file mode 100644 index 0000000000..c31674a520 --- /dev/null +++ b/tests/manual/rhi/shadowmap/shadowmap.cpp @@ -0,0 +1,304 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" +#include "../shared/cube.h" + +// Depth texture / shadow sampler / shadow map example. +// Not available on GLES 2.0. + +static float quadVertexData[] = +{ // Y up, CCW, x-y-z + -0.5f, 0.5f, 0.0f, + -0.5f, -0.5f, 0.0f, + 0.5f, -0.5f, 0.0f, + 0.5f, 0.5f, 0.0f, +}; + +static quint16 quadIndexData[] = +{ + 0, 1, 2, 0, 2, 3 +}; + +struct { + QVector releasePool; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiSampler *shadowSampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + QMatrix4x4 winProj; + float cubeRot = 0; + + QRhiTextureRenderTarget *rt = nullptr; + QRhiRenderPassDescriptor *rtRp = nullptr; + QRhiTexture *shadowMap = nullptr; + QRhiShaderResourceBindings *shadowSrb = nullptr; + QRhiGraphicsPipeline *shadowPs = nullptr; +} d; + +const int UBLOCK_SIZE = 64 * 3 + 4; +const int SHADOW_UBLOCK_SIZE = 64 * 1; +const int UBUF_SLOTS = 4; // 2 objects * 2 passes with different cameras + +void Window::customInit() +{ + if (!m_r->isTextureFormatSupported(QRhiTexture::D32F)) + qFatal("Depth texture is not supported"); + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVertexData) + sizeof(cube)); + d.vbuf->build(); + d.releasePool << d.vbuf; + + d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(quadIndexData)); + d.ibuf->build(); + d.releasePool << d.ibuf; + + const int oneRoundedUniformBlockSize = m_r->ubufAligned(UBLOCK_SIZE); + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SLOTS * oneRoundedUniformBlockSize); + d.ubuf->build(); + d.releasePool << d.ubuf; + + d.shadowMap = m_r->newTexture(QRhiTexture::D32F, QSize(1024, 1024), 1, QRhiTexture::RenderTarget); + d.releasePool << d.shadowMap; + d.shadowMap->build(); + + d.shadowSampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.shadowSampler; + d.shadowSampler->setTextureCompareOp(QRhiSampler::Less); + d.shadowSampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.releasePool << d.srb; + const QRhiShaderResourceBinding::StageFlags stages = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + d.srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, stages, d.ubuf, UBLOCK_SIZE), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.shadowMap, d.shadowSampler) }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/main.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/main.frag.qsb")) } + }); + d.ps->setDepthTest(true); + d.ps->setDepthWrite(true); + // fits both the quad and cube vertex data + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + }); + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + d.ps->build(); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(quadVertexData), quadVertexData); + d.initialUpdates->uploadStaticBuffer(d.vbuf, sizeof(quadVertexData), sizeof(cube), cube); + d.initialUpdates->uploadStaticBuffer(d.ibuf, quadIndexData); + + QRhiTextureRenderTargetDescription rtDesc; + rtDesc.setDepthTexture(d.shadowMap); + d.rt = m_r->newTextureRenderTarget(rtDesc); + d.releasePool << d.rt; + d.rtRp = d.rt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.rtRp; + d.rt->setRenderPassDescriptor(d.rtRp); + d.rt->build(); + + d.shadowSrb = m_r->newShaderResourceBindings(); + d.releasePool << d.shadowSrb; + d.shadowSrb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, stages, d.ubuf, SHADOW_UBLOCK_SIZE) }); + d.shadowSrb->build(); + + d.shadowPs = m_r->newGraphicsPipeline(); + d.releasePool << d.shadowPs; + d.shadowPs->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, getShader(QLatin1String(":/shadowmap.vert.qsb")) }, + { QRhiGraphicsShaderStage::Fragment, getShader(QLatin1String(":/shadowmap.frag.qsb")) } + }); + d.shadowPs->setDepthTest(true); + d.shadowPs->setDepthWrite(true); + inputLayout.setBindings({ + { 3 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 } + }); + d.shadowPs->setVertexInputLayout(inputLayout); + d.shadowPs->setShaderResourceBindings(d.shadowSrb); + d.shadowPs->setRenderPassDescriptor(d.rtRp); + d.shadowPs->build(); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +static void enqueueScene(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, int oneRoundedUniformBlockSize, int firstUbufSlot) +{ + QRhiCommandBuffer::DynamicOffset ubufOffset(0, quint32(firstUbufSlot * oneRoundedUniformBlockSize)); + // draw the ground (the quad) + cb->setShaderResources(srb, 1, &ubufOffset); + QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16); + cb->drawIndexed(6); + + // Draw the object (the cube). Both vertex and uniform data are in the same + // buffer, right after the quad's. + ubufOffset.second += oneRoundedUniformBlockSize; + cb->setShaderResources(srb, 1, &ubufOffset); + vbufBinding.second = sizeof(quadVertexData); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(36); +} + +void Window::customRender() +{ + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + const int oneRoundedUniformBlockSize = m_r->ubufAligned(UBLOCK_SIZE); + + QMatrix4x4 shadowBias; + // fill it in column-major order to keep our sanity (ctor would take row-major) + float *sbp = shadowBias.data(); + if (m_r->isClipDepthZeroToOne()) { + // convert x, y [-1, 1] -> [0, 1] + *sbp++ = 0.5f; *sbp++ = 0.0f; *sbp++ = 0.0f; *sbp++ = 0.0f; + *sbp++ = 0.0f; *sbp++ = m_r->isYUpInNDC() ? -0.5f : 0.5f; *sbp++ = 0.0f; *sbp++ = 0.0f; + *sbp++ = 0.0f; *sbp++ = 0.0f; *sbp++ = 1.0f; *sbp++ = 0.0f; + *sbp++ = 0.5f; *sbp++ = 0.5f; *sbp++ = 0.0f; *sbp++ = 1.0f; + } else { + // convert x, y, z [-1, 1] -> [0, 1] + *sbp++ = 0.5f; *sbp++ = 0.0f; *sbp++ = 0.0f; *sbp++ = 0.0f; + *sbp++ = 0.0f; *sbp++ = 0.5f; *sbp++ = 0.0f; *sbp++ = 0.0f; + *sbp++ = 0.0f; *sbp++ = 0.0f; *sbp++ = 0.5f; *sbp++ = 0.0f; + *sbp++ = 0.5f; *sbp++ = 0.5f; *sbp++ = 0.5f; *sbp++ = 1.0f; + } + + const QVector3D lightPos(5, 10, 10); + QMatrix4x4 lightViewProj = m_r->clipSpaceCorrMatrix(); + lightViewProj.perspective(45.0f, 1, 0.1f, 100.0f); + lightViewProj.lookAt(lightPos, QVector3D(0, 0, 0), QVector3D(0, 1, 0)); + + // uniform data for the ground + if (d.winProj != m_proj) { + d.winProj = m_proj; + + QMatrix4x4 m; + m.scale(4.0f); + m.rotate(-60, 1, 0, 0); + + // for the main pass + const QMatrix4x4 mvp = m_proj * m; // m_proj is in fact projection * view + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + const QMatrix4x4 shadowMvp = lightViewProj * m; + u->updateDynamicBuffer(d.ubuf, 64, 64, shadowMvp.constData()); + u->updateDynamicBuffer(d.ubuf, 128, 64, shadowBias.constData()); + qint32 useShadows = 1; + u->updateDynamicBuffer(d.ubuf, 192, 4, &useShadows); + + // for the shadow pass + u->updateDynamicBuffer(d.ubuf, 2 * oneRoundedUniformBlockSize, 64, shadowMvp.constData()); + } + + // uniform data for the rotating cube + QMatrix4x4 m; + m.translate(0, 0.5f, 2); + m.scale(0.2f); + m.rotate(d.cubeRot, 0, 1, 0); + m.rotate(45, 1, 0, 0); + d.cubeRot += 1; + + // for the main pass + const QMatrix4x4 mvp = m_proj * m; + u->updateDynamicBuffer(d.ubuf, oneRoundedUniformBlockSize, 64, mvp.constData()); + const QMatrix4x4 shadowMvp = lightViewProj * m; + u->updateDynamicBuffer(d.ubuf, oneRoundedUniformBlockSize + 64, 64, shadowMvp.constData()); + u->updateDynamicBuffer(d.ubuf, oneRoundedUniformBlockSize + 128, 64, shadowBias.constData()); + qint32 useShadows = 0; + u->updateDynamicBuffer(d.ubuf, oneRoundedUniformBlockSize + 192, 4, &useShadows); + + // for the shadow pass + u->updateDynamicBuffer(d.ubuf, 3 * oneRoundedUniformBlockSize, 64, shadowMvp.constData()); + + cb->resourceUpdate(u); + + // shadow pass + const QSize shadowMapSize = d.shadowMap->pixelSize(); + cb->beginPass(d.rt, QColor(), { 1.0f, 0 }); + cb->setGraphicsPipeline(d.shadowPs); + cb->setViewport({ 0, 0, float(shadowMapSize.width()), float(shadowMapSize.height()) }); + enqueueScene(cb, d.shadowSrb, oneRoundedUniformBlockSize, 2); + cb->endPass(); + + // main pass + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }); + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + enqueueScene(cb, d.srb, oneRoundedUniformBlockSize, 0); + cb->endPass(); +} diff --git a/tests/manual/rhi/shadowmap/shadowmap.frag b/tests/manual/rhi/shadowmap/shadowmap.frag new file mode 100644 index 0000000000..fa868f31a2 --- /dev/null +++ b/tests/manual/rhi/shadowmap/shadowmap.frag @@ -0,0 +1,5 @@ +#version 440 + +void main() +{ +} diff --git a/tests/manual/rhi/shadowmap/shadowmap.frag.qsb b/tests/manual/rhi/shadowmap/shadowmap.frag.qsb new file mode 100644 index 0000000000..3cad114cf4 Binary files /dev/null and b/tests/manual/rhi/shadowmap/shadowmap.frag.qsb differ diff --git a/tests/manual/rhi/shadowmap/shadowmap.pro b/tests/manual/rhi/shadowmap/shadowmap.pro new file mode 100644 index 0000000000..baf740927c --- /dev/null +++ b/tests/manual/rhi/shadowmap/shadowmap.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + shadowmap.cpp + +RESOURCES = shadowmap.qrc diff --git a/tests/manual/rhi/shadowmap/shadowmap.qrc b/tests/manual/rhi/shadowmap/shadowmap.qrc new file mode 100644 index 0000000000..8256d5ca25 --- /dev/null +++ b/tests/manual/rhi/shadowmap/shadowmap.qrc @@ -0,0 +1,8 @@ + + + shadowmap.vert.qsb + shadowmap.frag.qsb + main.vert.qsb + main.frag.qsb + + diff --git a/tests/manual/rhi/shadowmap/shadowmap.vert b/tests/manual/rhi/shadowmap/shadowmap.vert new file mode 100644 index 0000000000..72b5370aec --- /dev/null +++ b/tests/manual/rhi/shadowmap/shadowmap.vert @@ -0,0 +1,14 @@ +#version 440 + +layout(location = 0) in vec4 position; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + gl_Position = ubuf.mvp * position; +} diff --git a/tests/manual/rhi/shadowmap/shadowmap.vert.qsb b/tests/manual/rhi/shadowmap/shadowmap.vert.qsb new file mode 100644 index 0000000000..37a5e6ecbf Binary files /dev/null and b/tests/manual/rhi/shadowmap/shadowmap.vert.qsb differ diff --git a/tests/manual/rhi/shared/OpenfootageNET_fieldairport-512.hdr b/tests/manual/rhi/shared/OpenfootageNET_fieldairport-512.hdr new file mode 100644 index 0000000000..abdebdb74b Binary files /dev/null and b/tests/manual/rhi/shared/OpenfootageNET_fieldairport-512.hdr differ diff --git a/tests/manual/rhi/shared/buildshaders.bat b/tests/manual/rhi/shared/buildshaders.bat new file mode 100644 index 0000000000..dde5f7b3d2 --- /dev/null +++ b/tests/manual/rhi/shared/buildshaders.bat @@ -0,0 +1,5 @@ +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c color.vert -o color.vert.qsb +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c color.frag -o color.frag.qsb +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c texture.vert -o texture.vert.qsb +qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c texture.frag -o texture.frag.qsb +qsb --hlsl 50 --msl 12 -c texture_ms4.frag -o texture_ms4.frag.qsb diff --git a/tests/manual/rhi/shared/bwqt224_64.png b/tests/manual/rhi/shared/bwqt224_64.png new file mode 100644 index 0000000000..8020cbb2a8 Binary files /dev/null and b/tests/manual/rhi/shared/bwqt224_64.png differ diff --git a/tests/manual/rhi/shared/bwqt224_64_nomips.dds b/tests/manual/rhi/shared/bwqt224_64_nomips.dds new file mode 100644 index 0000000000..393cfec9db Binary files /dev/null and b/tests/manual/rhi/shared/bwqt224_64_nomips.dds differ diff --git a/tests/manual/rhi/shared/color.frag b/tests/manual/rhi/shared/color.frag new file mode 100644 index 0000000000..bb94a5a605 --- /dev/null +++ b/tests/manual/rhi/shared/color.frag @@ -0,0 +1,15 @@ +#version 440 + +layout(location = 0) in vec3 v_color; + +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +} ubuf; + +void main() +{ + fragColor = vec4(v_color * ubuf.opacity, ubuf.opacity); +} diff --git a/tests/manual/rhi/shared/color.frag.qsb b/tests/manual/rhi/shared/color.frag.qsb new file mode 100644 index 0000000000..3a965682eb Binary files /dev/null and b/tests/manual/rhi/shared/color.frag.qsb differ diff --git a/tests/manual/rhi/shared/color.vert b/tests/manual/rhi/shared/color.vert new file mode 100644 index 0000000000..43af543f44 --- /dev/null +++ b/tests/manual/rhi/shared/color.vert @@ -0,0 +1,19 @@ +#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; + float opacity; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + v_color = color; + gl_Position = ubuf.mvp * position; +} diff --git a/tests/manual/rhi/shared/color.vert.qsb b/tests/manual/rhi/shared/color.vert.qsb new file mode 100644 index 0000000000..e34eae79a0 Binary files /dev/null and b/tests/manual/rhi/shared/color.vert.qsb differ diff --git a/tests/manual/rhi/shared/cube.h b/tests/manual/rhi/shared/cube.h new file mode 100644 index 0000000000..252e80c56c --- /dev/null +++ b/tests/manual/rhi/shared/cube.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015-2016 The Khronos Group Inc. + * Copyright (c) 2015-2016 Valve Corporation + * Copyright (c) 2015-2016 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Chia-I Wu + * Author: Courtney Goeltzenleuchter + * Author: Ian Elliott + * Author: Ian Elliott + * Author: Jon Ashburn + * Author: Gwan-gyeong Mun + * Author: Tony Barbour + * Author: Bill Hollings + */ + +#ifndef CUBE_H +#define CUBE_H + +// clang-format off +static const float cube[] = { + -1.0f,-1.0f,-1.0f, // -X side + -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 side + 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 side + 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 side + -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 side + 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 side + -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, + + 0.0f, 1.0f, // -X side + 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 side + 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 side + 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 side + 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 side + 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 side + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, +}; +// clang-format on + +#endif diff --git a/tests/manual/rhi/shared/dds_bc1.h b/tests/manual/rhi/shared/dds_bc1.h new file mode 100644 index 0000000000..3c98a94cfc --- /dev/null +++ b/tests/manual/rhi/shared/dds_bc1.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +static const quint32 DDS_MAGIC = 0x20534444; // 'DDS ' +static const quint32 DDS_FOURCC = 4; + +#define FOURCC(c0, c1, c2, c3) ((c0) | ((c1) << 8) | ((c2) << 16) | ((c3 << 24))) + +struct DDS_PIXELFORMAT { + quint32 size; + quint32 flags; + quint32 fourCC; + quint32 rgbBitCount; + quint32 rBitMask; + quint32 gBitMask; + quint32 bBitMask; + quint32 aBitMask; +}; + +struct DDS_HEADER { + quint32 size; + quint32 flags; + quint32 height; + quint32 width; + quint32 pitch; + quint32 depth; + quint32 mipMapCount; + quint32 reserved1[11]; + DDS_PIXELFORMAT pixelFormat; + quint32 caps; + quint32 caps2; + quint32 caps3; + quint32 caps4; + quint32 reserved2; +}; + +static quint32 bc1size(const QSize &size) +{ + static const quint32 blockSize = 8; // 8 bytes for BC1 + const quint32 bytesPerLine = qMax(1, (size.width() + 3) / 4) * blockSize; + const quint32 ySize = qMax(1, (size.height() + 3) / 4); + return bytesPerLine * ySize; +} + +static QByteArrayList loadBC1(const QString &filename, QSize *size) +{ + QFile f(filename); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("Failed to open %s", qPrintable(filename)); + return QByteArrayList(); + } + + quint32 magic = 0; + f.read(reinterpret_cast(&magic), sizeof(magic)); + if (magic != DDS_MAGIC) { + qWarning("%s is not a DDS file", qPrintable(filename)); + return QByteArrayList(); + } + DDS_HEADER header; + f.read(reinterpret_cast(&header), sizeof(header)); + if (header.size != sizeof(DDS_HEADER)) { + qWarning("Invalid DDS header size"); + return QByteArrayList(); + } + if (header.pixelFormat.size != sizeof(DDS_PIXELFORMAT)) { + qWarning("Invalid DDS pixel format size"); + return QByteArrayList(); + } + if (!(header.pixelFormat.flags & DDS_FOURCC)) { + qWarning("Invalid DDS pixel format"); + return QByteArrayList(); + } + if (header.pixelFormat.fourCC != FOURCC('D', 'X', 'T', '1')) { + qWarning("Only DXT1 (BC1) is supported"); + return QByteArrayList(); + } + + QByteArrayList data; + QSize sz(header.width, header.height); + for (quint32 level = 0; level < header.mipMapCount; ++level) { + data.append(f.read(bc1size(sz))); + sz.setWidth(qMax(1, sz.width() / 2)); + sz.setHeight(qMax(1, sz.height() / 2)); + } + + if (size) + *size = QSize(header.width, header.height); + + return data; +} diff --git a/tests/manual/rhi/shared/examplefw.h b/tests/manual/rhi/shared/examplefw.h new file mode 100644 index 0000000000..450aa172c2 --- /dev/null +++ b/tests/manual/rhi/shared/examplefw.h @@ -0,0 +1,548 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +// Adapted from hellominimalcrossgfxtriangle with the frame rendering stripped out. +// Include this file and implement Window::customInit, release and render. +// Debug/validation layer is enabled for D3D and Vulkan. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef QT_NO_OPENGL +#include +#include +#endif + +#if QT_CONFIG(vulkan) +#include +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_DARWIN +#include +#endif + +QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +enum GraphicsApi +{ + Null, + OpenGL, + Vulkan, + D3D11, + Metal +}; + +GraphicsApi graphicsApi; + +QString graphicsApiName() +{ + switch (graphicsApi) { + case Null: + return QLatin1String("Null (no output)"); + case OpenGL: + return QLatin1String("OpenGL 2.x"); + case Vulkan: + return QLatin1String("Vulkan"); + case D3D11: + return QLatin1String("Direct3D 11"); + case Metal: + return QLatin1String("Metal"); + default: + break; + } + return QString(); +} + +QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers; +int sampleCount = 1; +QRhiSwapChain::Flags scFlags = 0; +QRhi::EndFrameFlags endFrameFlags = 0; + +class Window : public QWindow +{ +public: + Window(); + ~Window(); + +protected: + void init(); + void releaseResources(); + void resizeSwapChain(); + void releaseSwapChain(); + void render(); + + void customInit(); + void customRelease(); + void customRender(); + + void exposeEvent(QExposeEvent *) override; + bool event(QEvent *) override; + + bool m_running = false; + bool m_notExposed = false; + bool m_newlyExposed = false; + + QRhi *m_r = nullptr; + bool m_hasSwapChain = false; + QRhiSwapChain *m_sc = nullptr; + QRhiRenderBuffer *m_ds = nullptr; + QRhiRenderPassDescriptor *m_rp = nullptr; + + QMatrix4x4 m_proj; + + QElapsedTimer m_timer; + int m_frameCount; + +#ifndef QT_NO_OPENGL + QOffscreenSurface *m_fallbackSurface = nullptr; +#endif + + friend int main(int, char**); +}; + +Window::Window() +{ + // Tell the platform plugin what we want. + switch (graphicsApi) { + case OpenGL: +#if QT_CONFIG(opengl) + setSurfaceType(OpenGLSurface); + setFormat(QRhiGles2InitParams::adjustedFormat()); +#endif + break; + case Vulkan: + setSurfaceType(VulkanSurface); + break; + case D3D11: + setSurfaceType(OpenGLSurface); // not a typo + break; + case Metal: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + setSurfaceType(MetalSurface); +#endif + break; + default: + break; + } +} + +Window::~Window() +{ + releaseResources(); +} + +void Window::exposeEvent(QExposeEvent *) +{ + // initialize and start rendering when the window becomes usable for graphics purposes + if (isExposed() && !m_running) { + m_running = true; + init(); + resizeSwapChain(); + } + + const QSize surfaceSize = m_hasSwapChain ? m_sc->surfacePixelSize() : QSize(); + + // stop pushing frames when not exposed (or size is 0) + if ((!isExposed() || (m_hasSwapChain && surfaceSize.isEmpty())) && m_running) + m_notExposed = true; + + // continue when exposed again and the surface has a valid size. + // note that the surface size can be (0, 0) even though size() reports a valid one... + if (isExposed() && m_running && m_notExposed && !surfaceSize.isEmpty()) { + m_notExposed = false; + m_newlyExposed = true; + } + + // always render a frame on exposeEvent() (when exposed) in order to update + // immediately on window resize. + if (isExposed() && !surfaceSize.isEmpty()) + render(); +} + +bool Window::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::UpdateRequest: + render(); + break; + + case QEvent::PlatformSurface: + // this is the proper time to tear down the swapchain (while the native window and surface are still around) + if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) + releaseSwapChain(); + break; + + default: + break; + } + + return QWindow::event(e); +} + +void Window::init() +{ + if (graphicsApi == Null) { + QRhiNullInitParams params; + m_r = QRhi::create(QRhi::Null, ¶ms, rhiFlags); + } + +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) { + m_fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); + QRhiGles2InitParams params; + params.fallbackSurface = m_fallbackSurface; + params.window = this; + m_r = QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags); + } +#endif + +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) { + QRhiVulkanInitParams params; + params.inst = vulkanInstance(); + params.window = this; + m_r = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_WIN + if (graphicsApi == D3D11) { + QRhiD3D11InitParams params; + params.enableDebugLayer = true; + m_r = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + QRhiMetalInitParams params; + m_r = QRhi::create(QRhi::Metal, ¶ms, rhiFlags); + } +#endif + + if (!m_r) + qFatal("Failed to create RHI backend"); + + // now onto the backend-independent init + + m_sc = m_r->newSwapChain(); + // allow depth-stencil, although we do not actually enable depth test/write for the triangle + m_ds = m_r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), // no need to set the size yet + sampleCount, + QRhiRenderBuffer::UsedWithSwapChainOnly); + m_sc->setWindow(this); + m_sc->setDepthStencil(m_ds); + m_sc->setSampleCount(sampleCount); + m_sc->setFlags(scFlags); + m_rp = m_sc->newCompatibleRenderPassDescriptor(); + m_sc->setRenderPassDescriptor(m_rp); + + customInit(); +} + +void Window::releaseResources() +{ + customRelease(); + + delete m_rp; + m_rp = nullptr; + + delete m_ds; + m_ds = nullptr; + + delete m_sc; + m_sc = nullptr; + + delete m_r; + m_r = nullptr; + +#ifndef QT_NO_OPENGL + delete m_fallbackSurface; + m_fallbackSurface = nullptr; +#endif +} + +void Window::resizeSwapChain() +{ + const QSize outputSize = m_sc->surfacePixelSize(); + + m_ds->setPixelSize(outputSize); + m_ds->build(); // == m_ds->release(); m_ds->build(); + + m_hasSwapChain = m_sc->buildOrResize(); + + m_frameCount = 0; + m_timer.restart(); + + m_proj = m_r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); + m_proj.translate(0, 0, -4); +} + +void Window::releaseSwapChain() +{ + if (m_hasSwapChain) { + m_hasSwapChain = false; + m_sc->release(); + } +} + +void Window::render() +{ + if (!m_hasSwapChain || m_notExposed) + return; + + // If the window got resized or got newly exposed, resize the swapchain. + // (the newly-exposed case is not actually required by some + // platforms/backends, but f.ex. Vulkan on Windows seems to need it) + if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + m_newlyExposed = false; + } + + // Start a new frame. This is where we block when too far ahead of + // GPU/present, and that's what throttles the thread to the refresh rate. + // (except for OpenGL where it happens either in endFrame or somewhere else + // depending on the GL implementation) + QRhi::FrameOpResult r = m_r->beginFrame(m_sc); + if (r == QRhi::FrameOpSwapChainOutOfDate) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + r = m_r->beginFrame(m_sc); + } + if (r != QRhi::FrameOpSuccess) { + requestUpdate(); + return; + } + + m_frameCount += 1; + if (m_timer.elapsed() > 1000) { + if (rhiFlags.testFlag(QRhi::EnableProfiling)) { + const QRhiProfiler::CpuTime ff = m_r->profiler()->frameToFrameTimes(m_sc); + const QRhiProfiler::CpuTime be = m_r->profiler()->frameBuildTimes(m_sc); + const QRhiProfiler::GpuTime gp = m_r->profiler()->gpuFrameTimes(m_sc); + if (m_r->isFeatureSupported(QRhi::Timestamps)) { + qDebug("ca. %d fps. " + "frame-to-frame: min %lld max %lld avg %f. " + "frame build: min %lld max %lld avg %f. " + "gpu frame time: min %f max %f avg %f", + m_frameCount, + ff.minTime, ff.maxTime, ff.avgTime, + be.minTime, be.maxTime, be.avgTime, + gp.minTime, gp.maxTime, gp.avgTime); + } else { + qDebug("ca. %d fps. " + "frame-to-frame: min %lld max %lld avg %f. " + "frame build: min %lld max %lld avg %f. ", + m_frameCount, + ff.minTime, ff.maxTime, ff.avgTime, + be.minTime, be.maxTime, be.avgTime); + } + } else { + qDebug("ca. %d fps", m_frameCount); + } + + m_timer.restart(); + m_frameCount = 0; + } + + customRender(); + + m_r->endFrame(m_sc, endFrameFlags); + + if (!scFlags.testFlag(QRhiSwapChain::NoVSync)) + requestUpdate(); + else // try prevent all delays when NoVSync + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); +} + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + // Defaults. +#if defined(Q_OS_WIN) + graphicsApi = D3D11; +#elif defined(Q_OS_DARWIN) + graphicsApi = Metal; +#elif QT_CONFIG(vulkan) + graphicsApi = Vulkan; +#else + graphicsApi = OpenGL; +#endif + + // Allow overriding via the command line. + QCommandLineParser cmdLineParser; + cmdLineParser.addHelpOption(); + QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); + cmdLineParser.addOption(nullOption); + QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)")); + cmdLineParser.addOption(glOption); + QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); + cmdLineParser.addOption(vkOption); + QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3dOption); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + // Testing cleanup both with QWindow::close() (hitting X or Alt-F4) and + // QCoreApplication::quit() (e.g. what a menu widget would do) is important. + // Use this parameter for the latter. + QCommandLineOption sdOption({ "s", "self-destruct" }, QLatin1String("Self destruct after 5 seconds")); + cmdLineParser.addOption(sdOption); + + cmdLineParser.process(app); + if (cmdLineParser.isSet(nullOption)) + graphicsApi = Null; + if (cmdLineParser.isSet(glOption)) + graphicsApi = OpenGL; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = Vulkan; + if (cmdLineParser.isSet(d3dOption)) + graphicsApi = D3D11; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = Metal; + + qDebug("Selected graphics API is %s", qPrintable(graphicsApiName())); + qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText())); + +#ifdef EXAMPLEFW_PREINIT + void preInit(); + preInit(); +#endif + + // OpenGL specifics. + QSurfaceFormat fmt; + fmt.setDepthBufferSize(24); + fmt.setStencilBufferSize(8); + if (sampleCount > 1) + fmt.setSamples(sampleCount); + if (scFlags.testFlag(QRhiSwapChain::NoVSync)) + fmt.setSwapInterval(0); + if (scFlags.testFlag(QRhiSwapChain::sRGB)) + fmt.setColorSpace(QSurfaceFormat::sRGBColorSpace); + QSurfaceFormat::setDefaultFormat(fmt); + + // Vulkan setup. +#if QT_CONFIG(vulkan) + QVulkanInstance inst; + if (graphicsApi == Vulkan) { +#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 + inst.setExtensions(QByteArrayList() + << "VK_KHR_get_physical_device_properties2"); + if (!inst.create()) { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = OpenGL; + } + } +#endif + + // Create and show the window. + Window w; +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) + w.setVulkanInstance(&inst); +#endif + w.resize(1280, 720); + w.setTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + graphicsApiName()); + w.show(); + + if (cmdLineParser.isSet(sdOption)) + QTimer::singleShot(5000, qGuiApp, SLOT(quit())); + + int ret = app.exec(); + + // Window::event() will not get invoked when the + // PlatformSurfaceAboutToBeDestroyed event is sent during the QWindow + // destruction. That happens only when exiting via app::quit() instead of + // the more common QWindow::close(). Take care of it: if the + // QPlatformWindow is still around (there was no close() yet), get rid of + // the swapchain while it's not too late. + if (w.handle()) + w.releaseSwapChain(); + + return ret; +} diff --git a/tests/manual/rhi/shared/qt256.png b/tests/manual/rhi/shared/qt256.png new file mode 100644 index 0000000000..30c621c9c6 Binary files /dev/null and b/tests/manual/rhi/shared/qt256.png differ diff --git a/tests/manual/rhi/shared/qt256_bc1_9mips.dds b/tests/manual/rhi/shared/qt256_bc1_9mips.dds new file mode 100644 index 0000000000..734a5d184f Binary files /dev/null and b/tests/manual/rhi/shared/qt256_bc1_9mips.dds differ diff --git a/tests/manual/rhi/shared/texture.frag b/tests/manual/rhi/shared/texture.frag new file mode 100644 index 0000000000..5405444c68 --- /dev/null +++ b/tests/manual/rhi/shared/texture.frag @@ -0,0 +1,18 @@ +#version 440 + +layout(location = 0) in vec2 v_texcoord; + +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + int flip; +} ubuf; + +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/tests/manual/rhi/shared/texture.frag.qsb b/tests/manual/rhi/shared/texture.frag.qsb new file mode 100644 index 0000000000..31bcd7105e Binary files /dev/null and b/tests/manual/rhi/shared/texture.frag.qsb differ diff --git a/tests/manual/rhi/shared/texture.vert b/tests/manual/rhi/shared/texture.vert new file mode 100644 index 0000000000..ad7e783dfb --- /dev/null +++ b/tests/manual/rhi/shared/texture.vert @@ -0,0 +1,21 @@ +#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; + int flip; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + v_texcoord = vec2(texcoord.x, texcoord.y); + if (ubuf.flip != 0) + v_texcoord.y = 1.0 - v_texcoord.y; + gl_Position = ubuf.mvp * position; +} diff --git a/tests/manual/rhi/shared/texture.vert.qsb b/tests/manual/rhi/shared/texture.vert.qsb new file mode 100644 index 0000000000..1b9e52890d Binary files /dev/null and b/tests/manual/rhi/shared/texture.vert.qsb differ diff --git a/tests/manual/rhi/shared/texture_ms4.frag b/tests/manual/rhi/shared/texture_ms4.frag new file mode 100644 index 0000000000..d4f2bd3654 --- /dev/null +++ b/tests/manual/rhi/shared/texture_ms4.frag @@ -0,0 +1,20 @@ +#version 440 + +layout(location = 0) in vec2 v_texcoord; + +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + int flip; +} ubuf; + +layout(binding = 1) uniform sampler2DMS tex; + +void main() +{ + ivec2 tc = ivec2(floor(vec2(textureSize(tex)) * v_texcoord)); + vec4 c = texelFetch(tex, tc, 0) + texelFetch(tex, tc, 1) + texelFetch(tex, tc, 2) + texelFetch(tex, tc, 3); + c /= 4.0; + fragColor = vec4(c.rgb * c.a, c.a); +} diff --git a/tests/manual/rhi/shared/texture_ms4.frag.qsb b/tests/manual/rhi/shared/texture_ms4.frag.qsb new file mode 100644 index 0000000000..7f187ce7f0 Binary files /dev/null and b/tests/manual/rhi/shared/texture_ms4.frag.qsb differ diff --git a/tests/manual/rhi/texuploads/texuploads.cpp b/tests/manual/rhi/texuploads/texuploads.cpp new file mode 100644 index 0000000000..6576b763d5 --- /dev/null +++ b/tests/manual/rhi/texuploads/texuploads.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "../shared/examplefw.h" +#include "../shared/cube.h" +#include + +struct { + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QVector releasePool; + + float rotation = 0; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + int frameCount = 0; + QImage customImage; + QRhiTexture *newTex = nullptr; + QRhiTexture *importedTex = nullptr; + int testStage = 0; +} d; + +void Window::customInit() +{ + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + d.releasePool << d.vbuf; + d.vbuf->build(); + + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + d.releasePool << d.ubuf; + d.ubuf->build(); + + QImage baseImage(QLatin1String(":/qt256.png")); + d.tex = m_r->newTexture(QRhiTexture::RGBA8, baseImage.size(), 1, QRhiTexture::UsedAsTransferSource); + d.releasePool << d.tex; + d.tex->build(); + + // As an alternative to what some of the other examples do, prepare an + // update batch right here instead of relying on vbufReady and similar flags. + d.initialUpdates = m_r->nextResourceUpdateBatch(); + d.initialUpdates->uploadStaticBuffer(d.vbuf, cube); + qint32 flip = 0; + d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &flip); + d.initialUpdates->uploadTexture(d.tex, baseImage); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.sampler; + d.sampler->build(); + + d.srb = m_r->newShaderResourceBindings(); + d.releasePool << d.srb; + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->build(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + + d.ps->setDepthTest(true); + d.ps->setDepthWrite(true); + d.ps->setDepthOp(QRhiGraphicsPipeline::Less); + + d.ps->setCullMode(QRhiGraphicsPipeline::Back); + d.ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + const QShader vs = getShader(QLatin1String(":/texture.vert.qsb")); + if (!vs.isValid()) + qFatal("Failed to load shader pack (vertex)"); + const QShader fs = getShader(QLatin1String(":/texture.frag.qsb")); + if (!fs.isValid()) + qFatal("Failed to load shader pack (fragment)"); + + d.ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + + d.ps->build(); + + d.customImage = QImage(128, 64, QImage::Format_RGBA8888); + d.customImage.fill(Qt::red); + QPainter painter(&d.customImage); + // the text may look different on different platforms, so no guarantee the + // output on the screen will be identical everywhere + painter.drawText(5, 25, "Hello world"); + painter.end(); +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); +} + +void Window::customRender() +{ + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + + // take the initial set of updates, if this is the first frame + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + d.rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.scale(0.5f); + mvp.rotate(d.rotation, 0, 1, 0); + u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); + + if (d.frameCount > 0 && (d.frameCount % 100) == 0) { + d.testStage += 1; + qDebug("testStage = %d", d.testStage); + + // Partially change the texture. + if (d.testStage == 1) { + QRhiTextureSubresourceUploadDescription mipDesc(d.customImage); + // The image here is smaller than the original. Use a non-zero position + // to make it more interesting. + mipDesc.setDestinationTopLeft(QPoint(100, 20)); + QRhiTextureUploadDescription desc({ 0, 0, mipDesc }); + u->uploadTexture(d.tex, desc); + } + + // Exercise image copying. + if (d.testStage == 2) { + const QSize sz = d.tex->pixelSize(); + d.newTex = m_r->newTexture(QRhiTexture::RGBA8, sz); + d.releasePool << d.newTex; + d.newTex->build(); + + QImage empty(sz.width(), sz.height(), QImage::Format_RGBA8888); + empty.fill(Qt::blue); + u->uploadTexture(d.newTex, empty); + + // Copy the left-half of tex to the right-half of newTex, while + // leaving the left-half of newTex blue. Keep a 20 pixel gap at + // the top. + QRhiTextureCopyDescription desc; + desc.setSourceTopLeft(QPoint(0, 20)); + desc.setPixelSize(QSize(sz.width() / 2, sz.height() - 20)); + desc.setDestinationTopLeft(QPoint(sz.width() / 2, 20)); + u->copyTexture(d.newTex, d.tex, desc); + + // Now replace d.tex with d.newTex as the shader resource. + auto bindings = d.srb->bindings(); + bindings[1] = QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.newTex, d.sampler); + d.srb->setBindings(bindings); + // "rebuild", whatever that means for a given backend. This srb is + // already live as the ps in the setGraphicsPipeline references it, + // but that's fine. Changes will be picked up automatically. + d.srb->build(); + } + + // Exercise simple, full texture copy. + if (d.testStage == 4) + u->copyTexture(d.newTex, d.tex); + + // Now again upload customImage but this time only a part of it. + if (d.testStage == 5) { + QRhiTextureSubresourceUploadDescription mipDesc(d.customImage); + mipDesc.setDestinationTopLeft(QPoint(10, 120)); + mipDesc.setSourceSize(QSize(50, 40)); + mipDesc.setSourceTopLeft(QPoint(20, 10)); + QRhiTextureUploadDescription desc({ 0, 0, mipDesc }); + u->uploadTexture(d.newTex, desc); + } + + // Exercise texture object export/import. + if (d.testStage == 6) { + const QRhiNativeHandles *h = d.tex->nativeHandles(); + if (h) { +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + qDebug() << "Metal texture: " << static_cast(h)->texture; + // Now could cast to id and do something with + // it, keeping in mind that copy operations are only done + // in beginPass, while rendering into a texture may only + // have proper results in current_frame + 2, or after a + // finish(). The QRhiTexture still owns the native object. + } +#endif + // omit for other backends, the idea is the same + + d.importedTex = m_r->newTexture(QRhiTexture::RGBA8, d.tex->pixelSize()); + d.releasePool << d.importedTex; + if (!d.importedTex->buildFrom(h)) + qWarning("Texture import failed"); + + // now d.tex and d.importedTex use the same MTLTexture + // underneath (owned by d.tex) + + // switch to showing d.importedTex + auto bindings = d.srb->bindings(); + bindings[1] = QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.importedTex, d.sampler); + d.srb->setBindings(bindings); + d.srb->build(); + } else { + qWarning("Accessing native texture object is not supported"); + } + } + + // Exercise uploading uncompressed data without a QImage. + if (d.testStage == 7) { + auto bindings = d.srb->bindings(); + bindings[1] = QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.newTex, d.sampler); + d.srb->setBindings(bindings); + d.srb->build(); + + const QSize sz(221, 139); + QByteArray data; + data.resize(sz.width() * sz.height() * 4); + for (int y = 0; y < sz.height(); ++y) { + uchar *p = reinterpret_cast(data.data()) + y * sz.width() * 4; + for (int x = 0; x < sz.width(); ++x) { + *p++ = 0; *p++ = 0; *p++ = y * (255 / sz.height()); + *p++ = 255; + } + } + QRhiTextureSubresourceUploadDescription mipDesc(data.constData(), data.size()); + mipDesc.setSourceSize(sz); + mipDesc.setDestinationTopLeft(QPoint(5, 25)); + QRhiTextureUploadDescription desc({ 0, 0, mipDesc }); + u->uploadTexture(d.newTex, desc); + } + } + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSizeInPixels = m_sc->currentPixelSize(); + + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + + cb->setGraphicsPipeline(d.ps); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { d.vbuf, 0 }, + { d.vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); + + cb->endPass(); + + d.frameCount += 1; +} diff --git a/tests/manual/rhi/texuploads/texuploads.pro b/tests/manual/rhi/texuploads/texuploads.pro new file mode 100644 index 0000000000..64f204fb32 --- /dev/null +++ b/tests/manual/rhi/texuploads/texuploads.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + texuploads.cpp + +RESOURCES = texuploads.qrc diff --git a/tests/manual/rhi/texuploads/texuploads.qrc b/tests/manual/rhi/texuploads/texuploads.qrc new file mode 100644 index 0000000000..742b2b77af --- /dev/null +++ b/tests/manual/rhi/texuploads/texuploads.qrc @@ -0,0 +1,7 @@ + + + ../shared/qt256.png + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + + diff --git a/tests/manual/rhi/triquadcube/quadrenderer.cpp b/tests/manual/rhi/triquadcube/quadrenderer.cpp new file mode 100644 index 0000000000..6137fb1b9a --- /dev/null +++ b/tests/manual/rhi/triquadcube/quadrenderer.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "quadrenderer.h" +#include +#include + +// Renders a quad using indexed drawing. No QRhiGraphicsPipeline is created, it +// expects to reuse the one created by TriangleRenderer. A separate +// QRhiShaderResourceBindings is still needed, this will override the one the +// QRhiGraphicsPipeline references. + +static float vertexData[] = +{ // Y up (note m_proj), CCW + -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f +}; + +static quint16 indexData[] = +{ + 0, 1, 2, 0, 2, 3 +}; + +void QuadRenderer::initResources(QRhiRenderPassDescriptor *) +{ + m_vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); + m_vbuf->build(); + m_vbufReady = false; + + m_ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indexData)); + m_ibuf->build(); + + m_ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + m_ubuf->build(); + + m_srb = m_r->newShaderResourceBindings(); + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf) + }); + m_srb->build(); +} + +void QuadRenderer::setPipeline(QRhiGraphicsPipeline *ps) +{ + m_ps = ps; +} + +void QuadRenderer::resize(const QSize &pixelSize) +{ + m_proj = m_r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, pixelSize.width() / (float) pixelSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); +} + +void QuadRenderer::releaseResources() +{ + delete m_srb; + m_srb = nullptr; + + delete m_ubuf; + m_ubuf = nullptr; + + delete m_ibuf; + m_ibuf = nullptr; + + delete m_vbuf; + m_vbuf = nullptr; +} + +void QuadRenderer::queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates) +{ + if (!m_vbufReady) { + m_vbufReady = true; + resourceUpdates->uploadStaticBuffer(m_vbuf, vertexData); + resourceUpdates->uploadStaticBuffer(m_ibuf, indexData); + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.translate(m_translation); + mvp.rotate(m_rotation, 0, 1, 0); + resourceUpdates->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); + + if (!m_opacityReady) { + m_opacityReady = true; + const float opacity = 1.0f; + resourceUpdates->updateDynamicBuffer(m_ubuf, 64, 4, &opacity); + } +} + +void QuadRenderer::queueDraw(QRhiCommandBuffer *cb, const QSize &/*outputSizeInPixels*/) +{ + cb->setGraphicsPipeline(m_ps); + //cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); + cb->setShaderResources(m_srb); + const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding, m_ibuf, 0, QRhiCommandBuffer::IndexUInt16); + cb->drawIndexed(6); +} diff --git a/tests/manual/rhi/triquadcube/quadrenderer.h b/tests/manual/rhi/triquadcube/quadrenderer.h new file mode 100644 index 0000000000..79d5b8209d --- /dev/null +++ b/tests/manual/rhi/triquadcube/quadrenderer.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 QUADRENDERER_H +#define QUADRENDERER_H + +#include + +class QuadRenderer +{ +public: + void setRhi(QRhi *r) { m_r = r; } + void setSampleCount(int samples) { m_sampleCount = samples; } + int sampleCount() const { return m_sampleCount; } + void setTranslation(const QVector3D &v) { m_translation = v; } + void initResources(QRhiRenderPassDescriptor *rp); + void releaseResources(); + void setPipeline(QRhiGraphicsPipeline *ps); + void resize(const QSize &pixelSize); + void queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates); + void queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels); + +private: + QRhi *m_r; + + QRhiBuffer *m_vbuf = nullptr; + bool m_vbufReady = false; + QRhiBuffer *m_ibuf = nullptr; + bool m_opacityReady = false; + QRhiBuffer *m_ubuf = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + + QVector3D m_translation; + QMatrix4x4 m_proj; + float m_rotation = 0; + int m_sampleCount = 1; // no MSAA by default +}; + +#endif diff --git a/tests/manual/rhi/triquadcube/texturedcuberenderer.cpp b/tests/manual/rhi/triquadcube/texturedcuberenderer.cpp new file mode 100644 index 0000000000..d2e1f99923 --- /dev/null +++ b/tests/manual/rhi/triquadcube/texturedcuberenderer.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "texturedcuberenderer.h" +#include +#include + +#include "../shared/cube.h" + +const bool MIPMAP = true; +const bool AUTOGENMIPMAP = true; + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +void TexturedCubeRenderer::initResources(QRhiRenderPassDescriptor *rp) +{ + m_vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + m_vbuf->setName(QByteArrayLiteral("Cube vbuf (textured)")); + m_vbuf->build(); + m_vbufReady = false; + + m_ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4); + m_ubuf->setName(QByteArrayLiteral("Cube ubuf (textured)")); + m_ubuf->build(); + + m_image = QImage(QLatin1String(":/qt256.png")).convertToFormat(QImage::Format_RGBA8888); + QRhiTexture::Flags texFlags = 0; + if (MIPMAP) + texFlags |= QRhiTexture::MipMapped; + if (AUTOGENMIPMAP) + texFlags |= QRhiTexture::UsedWithGenerateMips; + m_tex = m_r->newTexture(QRhiTexture::RGBA8, QSize(m_image.width(), m_image.height()), 1, texFlags); + m_tex->setName(QByteArrayLiteral("Qt texture")); + m_tex->build(); + + m_sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, MIPMAP ? QRhiSampler::Linear : QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + m_sampler->build(); + + m_srb = m_r->newShaderResourceBindings(); + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, m_tex, m_sampler) + }); + m_srb->build(); + + m_ps = m_r->newGraphicsPipeline(); + + // No blending but the texture has alpha which we do not want to write out. + // Be nice. (would not matter for an onscreen window but makes a difference + // when reading back and saving into image files f.ex.) + QRhiGraphicsPipeline::TargetBlend blend; + blend.colorWrite = QRhiGraphicsPipeline::R | QRhiGraphicsPipeline::G | QRhiGraphicsPipeline::B; + m_ps->setTargetBlends({ blend }); + + m_ps->setDepthTest(true); + m_ps->setDepthWrite(true); + m_ps->setDepthOp(QRhiGraphicsPipeline::Less); + + m_ps->setCullMode(QRhiGraphicsPipeline::Back); + m_ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + m_ps->setSampleCount(m_sampleCount); + + QShader vs = getShader(QLatin1String(":/texture.vert.qsb")); + Q_ASSERT(vs.isValid()); + QShader fs = getShader(QLatin1String(":/texture.frag.qsb")); + Q_ASSERT(fs.isValid()); + m_ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + m_ps->setVertexInputLayout(inputLayout); + m_ps->setShaderResourceBindings(m_srb); + m_ps->setRenderPassDescriptor(rp); + + m_ps->build(); +} + +void TexturedCubeRenderer::resize(const QSize &pixelSize) +{ + m_proj = m_r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, pixelSize.width() / (float) pixelSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); +} + +void TexturedCubeRenderer::releaseResources() +{ + delete m_ps; + m_ps = nullptr; + + delete m_srb; + m_srb = nullptr; + + delete m_sampler; + m_sampler = nullptr; + + delete m_tex; + m_tex = nullptr; + + delete m_ubuf; + m_ubuf = nullptr; + + delete m_vbuf; + m_vbuf = nullptr; +} + +void TexturedCubeRenderer::queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates) +{ + if (!m_vbufReady) { + m_vbufReady = true; + resourceUpdates->uploadStaticBuffer(m_vbuf, cube); + qint32 flip = 0; + resourceUpdates->updateDynamicBuffer(m_ubuf, 64, 4, &flip); + } + + if (!m_image.isNull()) { + if (MIPMAP) { + QRhiTextureUploadDescription desc; + if (!AUTOGENMIPMAP) { + // the ghetto mipmap generator... + for (int i = 0, ie = m_r->mipLevelsForSize(m_image.size()); i != ie; ++i) { + QImage image = m_image.scaled(m_r->sizeForMipLevel(i, m_image.size())); + desc.append({ 0, i, image }); + } + } else { + desc.append({ 0, 0, m_image }); + } + resourceUpdates->uploadTexture(m_tex, desc); + if (AUTOGENMIPMAP) + resourceUpdates->generateMips(m_tex); + } else { + resourceUpdates->uploadTexture(m_tex, m_image); + } + m_image = QImage(); + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.translate(m_translation); + mvp.scale(0.5f); + mvp.rotate(m_rotation, 0, 1, 0); + resourceUpdates->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); +} + +void TexturedCubeRenderer::queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels) +{ + cb->setGraphicsPipeline(m_ps); + cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { m_vbuf, 0 }, + { m_vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); +} diff --git a/tests/manual/rhi/triquadcube/texturedcuberenderer.h b/tests/manual/rhi/triquadcube/texturedcuberenderer.h new file mode 100644 index 0000000000..879f875209 --- /dev/null +++ b/tests/manual/rhi/triquadcube/texturedcuberenderer.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 TEXTUREDCUBERENDERER_H +#define TEXTUREDCUBERENDERER_H + +#include + +class TexturedCubeRenderer +{ +public: + void setRhi(QRhi *r) { m_r = r; } + void setSampleCount(int samples) { m_sampleCount = samples; } + void setTranslation(const QVector3D &v) { m_translation = v; } + void initResources(QRhiRenderPassDescriptor *rp); + void releaseResources(); + void resize(const QSize &pixelSize); + void queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates); + void queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels); + +private: + QRhi *m_r; + + QRhiBuffer *m_vbuf = nullptr; + bool m_vbufReady = false; + QRhiBuffer *m_ubuf = nullptr; + QImage m_image; + QRhiTexture *m_tex = nullptr; + QRhiSampler *m_sampler = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + + QVector3D m_translation; + QMatrix4x4 m_proj; + float m_rotation = 0; + int m_sampleCount = 1; // no MSAA by default +}; + +#endif diff --git a/tests/manual/rhi/triquadcube/triangleoncuberenderer.cpp b/tests/manual/rhi/triquadcube/triangleoncuberenderer.cpp new file mode 100644 index 0000000000..40d9615aa1 --- /dev/null +++ b/tests/manual/rhi/triquadcube/triangleoncuberenderer.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "triangleoncuberenderer.h" +#include +#include + +// toggle to test the preserved content (no clear) path +const bool IMAGE_UNDER_OFFSCREEN_RENDERING = false; +const bool UPLOAD_UNDERLAY_ON_EVERY_FRAME = false; + +const bool DS_ATT = false; // have a depth-stencil attachment for the offscreen pass + +const bool DEPTH_TEXTURE = false; // offscreen pass uses a depth texture (verify with renderdoc etc., ignore valid.layer about ps slot 0) +const bool MRT = false; // two textures, the second is just cleared as the shader does not write anything (valid.layer may warn but for testing that's ok) + +#include "../shared/cube.h" + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +static const QSize OFFSCREEN_SIZE(512, 512); + +void TriangleOnCubeRenderer::initResources(QRhiRenderPassDescriptor *rp) +{ + m_vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + m_vbuf->setName(QByteArrayLiteral("Cube vbuf (textured with offscreen)")); + m_vbuf->build(); + m_vbufReady = false; + + m_ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4); + m_ubuf->setName(QByteArrayLiteral("Cube ubuf (textured with offscreen)")); + m_ubuf->build(); + + if (IMAGE_UNDER_OFFSCREEN_RENDERING) { + m_image = QImage(QLatin1String(":/qt256.png")).scaled(OFFSCREEN_SIZE).convertToFormat(QImage::Format_RGBA8888); + if (m_r->isYUpInFramebuffer()) + m_image = m_image.mirrored(); // just cause we'll flip texcoord Y when y up so accommodate our static background image as well + } + + m_tex = m_r->newTexture(QRhiTexture::RGBA8, OFFSCREEN_SIZE, 1, QRhiTexture::RenderTarget); + m_tex->setName(QByteArrayLiteral("Texture for offscreen content")); + m_tex->build(); + + if (MRT) { + m_tex2 = m_r->newTexture(QRhiTexture::RGBA8, OFFSCREEN_SIZE, 1, QRhiTexture::RenderTarget); + m_tex2->build(); + } + + if (DS_ATT) { + m_offscreenTriangle.setDepthWrite(true); + m_ds = m_r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_tex->pixelSize()); + m_ds->build(); + } + + if (DEPTH_TEXTURE) { + m_offscreenTriangle.setDepthWrite(true); + m_depthTex = m_r->newTexture(QRhiTexture::D32F, OFFSCREEN_SIZE, 1, QRhiTexture::RenderTarget); + m_depthTex->build(); + } + + m_sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + m_sampler->build(); + + m_srb = m_r->newShaderResourceBindings(); + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, m_tex, m_sampler) + }); + m_srb->build(); + + m_ps = m_r->newGraphicsPipeline(); + + m_ps->setDepthTest(true); + m_ps->setDepthWrite(true); + m_ps->setDepthOp(QRhiGraphicsPipeline::Less); + + m_ps->setCullMode(QRhiGraphicsPipeline::Back); + m_ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + m_ps->setSampleCount(m_sampleCount); + + QShader vs = getShader(QLatin1String(":/texture.vert.qsb")); + Q_ASSERT(vs.isValid()); + QShader fs = getShader(QLatin1String(":/texture.frag.qsb")); + Q_ASSERT(fs.isValid()); + m_ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + m_ps->setVertexInputLayout(inputLayout); + m_ps->setShaderResourceBindings(m_srb); + m_ps->setRenderPassDescriptor(rp); + + m_ps->build(); + + QRhiTextureRenderTarget::Flags rtFlags = 0; + if (IMAGE_UNDER_OFFSCREEN_RENDERING) + rtFlags |= QRhiTextureRenderTarget::PreserveColorContents; + + if (DEPTH_TEXTURE) { + QRhiTextureRenderTargetDescription desc; + desc.setDepthTexture(m_depthTex); + m_rt = m_r->newTextureRenderTarget(desc, rtFlags); + } else { + QRhiTextureRenderTargetDescription desc; + QRhiColorAttachment color0 { m_tex }; + if (DS_ATT) + desc.setDepthStencilBuffer(m_ds); + if (MRT) { + m_offscreenTriangle.setColorAttCount(2); + QRhiColorAttachment color1 { m_tex2 }; + desc.setColorAttachments({ color0, color1 }); + } else { + desc.setColorAttachments({ color0 }); + } + m_rt = m_r->newTextureRenderTarget(desc, rtFlags); + } + + m_rp = m_rt->newCompatibleRenderPassDescriptor(); + m_rt->setRenderPassDescriptor(m_rp); + + m_rt->build(); + + m_offscreenTriangle.setRhi(m_r); + m_offscreenTriangle.initResources(m_rp); + m_offscreenTriangle.setScale(2); + // m_tex and the offscreen triangle are never multisample +} + +void TriangleOnCubeRenderer::resize(const QSize &pixelSize) +{ + m_proj = m_r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, pixelSize.width() / (float) pixelSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); + + m_offscreenTriangle.resize(pixelSize); +} + +void TriangleOnCubeRenderer::releaseResources() +{ + m_offscreenTriangle.releaseResources(); + + delete m_ps; + m_ps = nullptr; + + delete m_srb; + m_srb = nullptr; + + delete m_rt; + m_rt = nullptr; + + delete m_rp; + m_rp = nullptr; + + delete m_sampler; + m_sampler = nullptr; + + delete m_depthTex; + m_depthTex = nullptr; + + delete m_tex2; + m_tex2 = nullptr; + + delete m_tex; + m_tex = nullptr; + + delete m_ds; + m_ds = nullptr; + + delete m_ubuf; + m_ubuf = nullptr; + + delete m_vbuf; + m_vbuf = nullptr; +} + +void TriangleOnCubeRenderer::queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates) +{ + if (!m_vbufReady) { + m_vbufReady = true; + resourceUpdates->uploadStaticBuffer(m_vbuf, cube); + qint32 flip = m_r->isYUpInFramebuffer() ? 1 : 0; + resourceUpdates->updateDynamicBuffer(m_ubuf, 64, 4, &flip); + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.translate(m_translation); + mvp.scale(0.5f); + mvp.rotate(m_rotation, 1, 0, 0); + resourceUpdates->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); + + // ### +// if (DEPTH_TEXTURE) { +// // m_tex is basically undefined here, be nice and transition the layout properly at least +// resourceUpdates->prepareTextureForUse(m_tex, QRhiResourceUpdateBatch::TextureRead); +// } +} + +void TriangleOnCubeRenderer::queueOffscreenPass(QRhiCommandBuffer *cb) +{ + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + m_offscreenTriangle.queueResourceUpdates(u); + + if (IMAGE_UNDER_OFFSCREEN_RENDERING && !m_image.isNull()) { + u->uploadTexture(m_tex, m_image); + if (!UPLOAD_UNDERLAY_ON_EVERY_FRAME) + m_image = QImage(); + } + + cb->beginPass(m_rt, QColor::fromRgbF(0.0f, 0.4f, 0.7f, 1.0f), { 1.0f, 0 }, u); + m_offscreenTriangle.queueDraw(cb, OFFSCREEN_SIZE); + cb->endPass(); +} + +void TriangleOnCubeRenderer::queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels) +{ + cb->setGraphicsPipeline(m_ps); + cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { m_vbuf, 0 }, + { m_vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); +} diff --git a/tests/manual/rhi/triquadcube/triangleoncuberenderer.h b/tests/manual/rhi/triquadcube/triangleoncuberenderer.h new file mode 100644 index 0000000000..5a56bf6bd1 --- /dev/null +++ b/tests/manual/rhi/triquadcube/triangleoncuberenderer.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 TRIANGLEONCUBERENDERER_H +#define TRIANGLEONCUBERENDERER_H + +#include "trianglerenderer.h" + +class TriangleOnCubeRenderer +{ +public: + void setRhi(QRhi *r) { m_r = r; } + void setSampleCount(int samples) { m_sampleCount = samples; } + void setTranslation(const QVector3D &v) { m_translation = v; } + void initResources(QRhiRenderPassDescriptor *rp); + void releaseResources(); + void resize(const QSize &pixelSize); + void queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates); + void queueOffscreenPass(QRhiCommandBuffer *cb); + void queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels); + +private: + QRhi *m_r; + + QRhiBuffer *m_vbuf = nullptr; + bool m_vbufReady = false; + QRhiBuffer *m_ubuf = nullptr; + QRhiTexture *m_tex = nullptr; + QRhiRenderBuffer *m_ds = nullptr; + QRhiTexture *m_tex2 = nullptr; + QRhiTexture *m_depthTex = nullptr; + QRhiSampler *m_sampler = nullptr; + QRhiTextureRenderTarget *m_rt = nullptr; + QRhiRenderPassDescriptor *m_rp = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + + QVector3D m_translation; + QMatrix4x4 m_proj; + float m_rotation = 0; + int m_sampleCount = 1; // no MSAA by default + + TriangleRenderer m_offscreenTriangle; + + QImage m_image; +}; + +#endif diff --git a/tests/manual/rhi/triquadcube/trianglerenderer.cpp b/tests/manual/rhi/triquadcube/trianglerenderer.cpp new file mode 100644 index 0000000000..aac76d4110 --- /dev/null +++ b/tests/manual/rhi/triquadcube/trianglerenderer.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "trianglerenderer.h" +#include +#include + +//#define VBUF_IS_DYNAMIC + +static float vertexData[] = { // Y up (note m_proj), CCW + 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f +}; + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +void TriangleRenderer::initResources(QRhiRenderPassDescriptor *rp) +{ +#ifdef VBUF_IS_DYNAMIC + m_vbuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, sizeof(vertexData)); +#else + m_vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); +#endif + m_vbuf->setName(QByteArrayLiteral("Triangle vbuf")); + m_vbuf->build(); + m_vbufReady = false; + + m_ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + m_ubuf->setName(QByteArrayLiteral("Triangle ubuf")); + m_ubuf->build(); + + m_srb = m_r->newShaderResourceBindings(); + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf) + }); + m_srb->build(); + + m_ps = m_r->newGraphicsPipeline(); + + QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; // convenient defaults... + premulAlphaBlend.enable = true; + QVector rtblends; + for (int i = 0; i < m_colorAttCount; ++i) + rtblends << premulAlphaBlend; + + m_ps->setTargetBlends(rtblends); + m_ps->setSampleCount(m_sampleCount); + + if (m_depthWrite) { // TriangleOnCube may want to exercise this + m_ps->setDepthTest(true); + m_ps->setDepthOp(QRhiGraphicsPipeline::Always); + m_ps->setDepthWrite(true); + } + + QShader vs = getShader(QLatin1String(":/color.vert.qsb")); + Q_ASSERT(vs.isValid()); + QShader fs = getShader(QLatin1String(":/color.frag.qsb")); + Q_ASSERT(fs.isValid()); + m_ps->setShaderStages({ + { QRhiGraphicsShaderStage::Vertex, vs }, + { QRhiGraphicsShaderStage::Fragment, fs } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 7 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + + m_ps->setVertexInputLayout(inputLayout); + m_ps->setShaderResourceBindings(m_srb); + m_ps->setRenderPassDescriptor(rp); + + m_ps->build(); +} + +void TriangleRenderer::resize(const QSize &pixelSize) +{ + m_proj = m_r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, pixelSize.width() / (float) pixelSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); +} + +void TriangleRenderer::releaseResources() +{ + delete m_ps; + m_ps = nullptr; + + delete m_srb; + m_srb = nullptr; + + delete m_ubuf; + m_ubuf = nullptr; + + delete m_vbuf; + m_vbuf = nullptr; +} + +void TriangleRenderer::queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates) +{ +#if 0 + static int messWithBufferTrigger = 0; + // recreate the underlying VkBuffer every second frame + // to exercise setShaderResources' built-in smartness + if (!(messWithBufferTrigger & 1)) { + m_ubuf->release(); + m_ubuf->build(); + } + ++messWithBufferTrigger; +#endif + + if (!m_vbufReady) { + m_vbufReady = true; +#ifdef VBUF_IS_DYNAMIC + resourceUpdates->updateDynamicBuffer(m_vbuf, 0, m_vbuf->size(), vertexData); +#else + resourceUpdates->uploadStaticBuffer(m_vbuf, vertexData); +#endif + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.translate(m_translation); + mvp.scale(m_scale); + mvp.rotate(m_rotation, 0, 1, 0); + resourceUpdates->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); + + m_opacity += m_opacityDir * 0.005f; + if (m_opacity < 0.0f || m_opacity > 1.0f) { + m_opacityDir *= -1; + m_opacity = qBound(0.0f, m_opacity, 1.0f); + } + resourceUpdates->updateDynamicBuffer(m_ubuf, 64, 4, &m_opacity); +} + +void TriangleRenderer::queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels) +{ + cb->setGraphicsPipeline(m_ps); + cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); +} diff --git a/tests/manual/rhi/triquadcube/trianglerenderer.h b/tests/manual/rhi/triquadcube/trianglerenderer.h new file mode 100644 index 0000000000..53d6926a62 --- /dev/null +++ b/tests/manual/rhi/triquadcube/trianglerenderer.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 TRIANGLERENDERER_H +#define TRIANGLERENDERER_H + +#include + +class TriangleRenderer +{ +public: + void setRhi(QRhi *r) { m_r = r; } + void setSampleCount(int samples) { m_sampleCount = samples; } + int sampleCount() const { return m_sampleCount; } + void setTranslation(const QVector3D &v) { m_translation = v; } + void setScale(float f) { m_scale = f; } + void setDepthWrite(bool enable) { m_depthWrite = enable; } + void setColorAttCount(int count) { m_colorAttCount = count; } + QRhiGraphicsPipeline *pipeline() const { return m_ps; } + void initResources(QRhiRenderPassDescriptor *rp); + void releaseResources(); + void resize(const QSize &pixelSize); + void queueResourceUpdates(QRhiResourceUpdateBatch *resourceUpdates); + void queueDraw(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels); + +private: + QRhi *m_r; + + QRhiBuffer *m_vbuf = nullptr; + bool m_vbufReady = false; + QRhiBuffer *m_ubuf = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + + QVector3D m_translation; + float m_scale = 1; + bool m_depthWrite = false; + int m_colorAttCount = 1; + QMatrix4x4 m_proj; + float m_rotation = 0; + float m_opacity = 1; + int m_opacityDir = -1; + int m_sampleCount = 1; // no MSAA by default +}; + +#endif diff --git a/tests/manual/rhi/triquadcube/triquadcube.cpp b/tests/manual/rhi/triquadcube/triquadcube.cpp new file mode 100644 index 0000000000..741e64be0a --- /dev/null +++ b/tests/manual/rhi/triquadcube/triquadcube.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +// An example exercising more than a single feature. Enables profiling +// (resource logging to a file) and inserts debug markers and sets some +// object names. Can also be used to test MSAA swapchains, swapchain image +// readback, requesting an sRGB swapchain, and some texture features. + +#define EXAMPLEFW_PREINIT +#include "../shared/examplefw.h" +#include "trianglerenderer.h" +#include "quadrenderer.h" +#include "texturedcuberenderer.h" +#include "triangleoncuberenderer.h" + +#include +#include +#include + +#define PROFILE_TO_FILE +//#define SKIP_PRESENT +//#define USE_MSAA +//#define USE_SRGB_SWAPCHAIN +//#define READBACK_SWAPCHAIN +//#define NO_VSYNC +//#define USE_MIN_SWAPCHAIN_BUFFERS + +struct { + TriangleRenderer triRenderer; + QuadRenderer quadRenderer; + TexturedCubeRenderer cubeRenderer; + TriangleOnCubeRenderer liveTexCubeRenderer; + bool onScreenOnly = false; + bool triangleOnly = false; + QSize lastOutputSize; + int frameCount = 0; + QFile profOut; +} d; + +void preInit() +{ +#ifdef PROFILE_TO_FILE + rhiFlags |= QRhi::EnableProfiling; + const QString profFn = QFileInfo(QLatin1String("rhiprof.txt")).absoluteFilePath(); + qDebug("Writing profiling output to %s", qPrintable(profFn)); + d.profOut.setFileName(profFn); + d.profOut.open(QIODevice::WriteOnly); +#endif + +#ifdef SKIP_PRESENT + endFrameFlags |= QRhi::SkipPresent; +#endif + +#ifdef USE_MSAA + sampleCount = 4; // enable 4x MSAA (except for the render-to-texture pass) +#endif + +#ifdef READBACK_SWAPCHAIN + scFlags |= QRhiSwapChain::UsedAsTransferSource; +#endif + +#ifdef USE_SRGB_SWAPCHAIN + scFlags |= QRhiSwapChain::sRGB; +#endif + +#ifdef NO_VSYNC + scFlags |= QRhiSwapChain::NoVSync; +#endif + +#ifdef USE_MIN_SWAPCHAIN_BUFFERS + scFlags |= QRhiSwapChain::MinimalBufferCount; +#endif + + // For OpenGL some of these are incorporated into the QSurfaceFormat by + // examplefw.h after returning from here as that is out of the RHI's control. +} + +void Window::customInit() +{ +#ifdef PROFILE_TO_FILE + m_r->profiler()->setDevice(&d.profOut); +#endif + + d.triRenderer.setRhi(m_r); + d.triRenderer.setSampleCount(sampleCount); + d.triRenderer.initResources(m_rp); + + if (!d.triangleOnly) { + d.triRenderer.setTranslation(QVector3D(0, 0.5f, 0)); + + d.quadRenderer.setRhi(m_r); + d.quadRenderer.setSampleCount(sampleCount); + d.quadRenderer.setPipeline(d.triRenderer.pipeline()); + d.quadRenderer.initResources(m_rp); + d.quadRenderer.setTranslation(QVector3D(1.5f, -0.5f, 0)); + + d.cubeRenderer.setRhi(m_r); + d.cubeRenderer.setSampleCount(sampleCount); + d.cubeRenderer.initResources(m_rp); + d.cubeRenderer.setTranslation(QVector3D(0, -0.5f, 0)); + } + + if (!d.onScreenOnly) { + d.liveTexCubeRenderer.setRhi(m_r); + d.liveTexCubeRenderer.setSampleCount(sampleCount); + d.liveTexCubeRenderer.initResources(m_rp); + d.liveTexCubeRenderer.setTranslation(QVector3D(-2.0f, 0, 0)); + } + + // Put the gpu mem allocator statistics to the profiling stream after doing + // all the init. (where applicable) + m_r->profiler()->addVMemAllocatorStats(); + + // Check some features/limits. + qDebug("isFeatureSupported(MultisampleTexture): %d", m_r->isFeatureSupported(QRhi::MultisampleTexture)); + qDebug("isFeatureSupported(MultisampleRenderBuffer): %d", m_r->isFeatureSupported(QRhi::MultisampleRenderBuffer)); + qDebug("isFeatureSupported(DebugMarkers): %d", m_r->isFeatureSupported(QRhi::DebugMarkers)); + qDebug("isFeatureSupported(Timestamps): %d", m_r->isFeatureSupported(QRhi::Timestamps)); + qDebug("isFeatureSupported(Instancing): %d", m_r->isFeatureSupported(QRhi::Instancing)); + qDebug("isFeatureSupported(CustomInstanceStepRate): %d", m_r->isFeatureSupported(QRhi::CustomInstanceStepRate)); + qDebug("isFeatureSupported(PrimitiveRestart): %d", m_r->isFeatureSupported(QRhi::PrimitiveRestart)); + qDebug("isFeatureSupported(NonDynamicUniformBuffers): %d", m_r->isFeatureSupported(QRhi::NonDynamicUniformBuffers)); + qDebug("isFeatureSupported(NonFourAlignedEffectiveIndexBufferOffset): %d", m_r->isFeatureSupported(QRhi::NonFourAlignedEffectiveIndexBufferOffset)); + qDebug("isFeatureSupported(NPOTTextureRepeat): %d", m_r->isFeatureSupported(QRhi::NPOTTextureRepeat)); + qDebug("isFeatureSupported(RedOrAlpha8IsRed): %d", m_r->isFeatureSupported(QRhi::RedOrAlpha8IsRed)); + qDebug("isFeatureSupported(ElementIndexUint): %d", m_r->isFeatureSupported(QRhi::ElementIndexUint)); + qDebug("Min 2D texture width/height: %d", m_r->resourceLimit(QRhi::TextureSizeMin)); + qDebug("Max 2D texture width/height: %d", m_r->resourceLimit(QRhi::TextureSizeMax)); + qDebug("Max color attachment count: %d", m_r->resourceLimit(QRhi::MaxColorAttachments)); +} + +void Window::customRelease() +{ + d.triRenderer.releaseResources(); + + if (!d.triangleOnly) { + d.quadRenderer.releaseResources(); + d.cubeRenderer.releaseResources(); + } + + if (!d.onScreenOnly) + d.liveTexCubeRenderer.releaseResources(); +} + +void Window::customRender() +{ + const QSize outputSize = m_sc->currentPixelSize(); + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + + if (outputSize != d.lastOutputSize) { + d.triRenderer.resize(outputSize); + if (!d.triangleOnly) { + d.quadRenderer.resize(outputSize); + d.cubeRenderer.resize(outputSize); + } + if (!d.onScreenOnly) + d.liveTexCubeRenderer.resize(outputSize); + d.lastOutputSize = outputSize; + } + + if (!d.onScreenOnly) { + cb->debugMarkBegin("Offscreen triangle pass"); + d.liveTexCubeRenderer.queueOffscreenPass(cb); + cb->debugMarkEnd(); + } + + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + d.triRenderer.queueResourceUpdates(u); + if (!d.triangleOnly) { + d.quadRenderer.queueResourceUpdates(u); + d.cubeRenderer.queueResourceUpdates(u); + } + if (!d.onScreenOnly) + d.liveTexCubeRenderer.queueResourceUpdates(u); + + cb->beginPass(m_sc->currentFrameRenderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); + cb->debugMarkBegin(QByteArrayLiteral("Triangle")); + d.triRenderer.queueDraw(cb, outputSize); + cb->debugMarkEnd(); + if (!d.triangleOnly) { + cb->debugMarkMsg(QByteArrayLiteral("More stuff")); + cb->debugMarkBegin(QByteArrayLiteral("Quad")); + d.quadRenderer.queueDraw(cb, outputSize); + cb->debugMarkEnd(); + cb->debugMarkBegin(QByteArrayLiteral("Cube")); + d.cubeRenderer.queueDraw(cb, outputSize); + cb->debugMarkEnd(); + } + if (!d.onScreenOnly) { + cb->debugMarkMsg(QByteArrayLiteral("Even more stuff")); + cb->debugMarkBegin(QByteArrayLiteral("Cube with offscreen triangle")); + d.liveTexCubeRenderer.queueDraw(cb, outputSize); + cb->debugMarkEnd(); + } + + QRhiResourceUpdateBatch *passEndUpdates = nullptr; +#ifdef READBACK_SWAPCHAIN + passEndUpdates = m_r->nextResourceUpdateBatch(); + QRhiReadbackDescription rb; // no texture given -> backbuffer + QRhiReadbackResult *rbResult = new QRhiReadbackResult; + int frameNo = d.frameCount; + rbResult->completed = [this, rbResult, frameNo] { + { + QImage::Format fmt = rbResult->format == QRhiTexture::BGRA8 ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGBA8888_Premultiplied; + const uchar *p = reinterpret_cast(rbResult->data.constData()); + QImage image(p, rbResult->pixelSize.width(), rbResult->pixelSize.height(), fmt); + QString fn = QString::asprintf("frame%d.png", frameNo); + fn = QFileInfo(fn).absoluteFilePath(); + qDebug("Saving into %s", qPrintable(fn)); + if (m_r->isYUpInFramebuffer()) + image.mirrored().save(fn); + else + image.save(fn); + } + delete rbResult; + }; + passEndUpdates->readBackTexture(rb, rbResult); +#endif + + cb->endPass(passEndUpdates); + + d.frameCount += 1; +} diff --git a/tests/manual/rhi/triquadcube/triquadcube.pro b/tests/manual/rhi/triquadcube/triquadcube.pro new file mode 100644 index 0000000000..0002b3cad1 --- /dev/null +++ b/tests/manual/rhi/triquadcube/triquadcube.pro @@ -0,0 +1,18 @@ +TEMPLATE = app + +QT += gui-private + +SOURCES = \ + triquadcube.cpp \ + trianglerenderer.cpp \ + quadrenderer.cpp \ + texturedcuberenderer.cpp \ + triangleoncuberenderer.cpp + +HEADERS = \ + trianglerenderer.h \ + quadrenderer.h \ + texturedcuberenderer.h \ + triangleoncuberenderer.h + +RESOURCES = triquadcube.qrc diff --git a/tests/manual/rhi/triquadcube/triquadcube.qrc b/tests/manual/rhi/triquadcube/triquadcube.qrc new file mode 100644 index 0000000000..41b72edc41 --- /dev/null +++ b/tests/manual/rhi/triquadcube/triquadcube.qrc @@ -0,0 +1,9 @@ + + + ../shared/color.vert.qsb + ../shared/color.frag.qsb + ../shared/texture.vert.qsb + ../shared/texture.frag.qsb + ../shared/qt256.png + + -- cgit v1.2.3