Commit b329a80e authored by Laszlo Agocs's avatar Laszlo Agocs
Browse files

Merge branch 'master' of git.qt.io:laagocs/qtrhi

parents a9f5f455 1dd15d71
Note: Further development is now happening at https://git.qt.io/laagocs/qtrhi
![](https://git.qt.io/laagocs/qtrhi/raw/master/rhi2.png)
Experiments for a Rendering Hardware Interface abstraction for Qt 6.
Experiments for a Rendering Hardware Interface abstraction for future Qt (QtRhi)
========================================================================
Relies on https://github.com/alpqr/qtshaderstack17 for shader management.
The API and its backends (Vulkan, OpenGL (ES) 2.0, Direct3D 11, Metal) are
reasonably complete in the sense that it should be possible to bring up a Qt
Quick renderer on top of them (using Vulkan-style GLSL as the "common" shading
language - translation seems to work pretty well for now, including to HLSL and
MSL). Other than that this is highly experimental with a long todo list, and the
API will change in arbitrary ways. It nonetheless shows what a possible future
direction for the Qt graphics stack could be.
The API and its backends (Vulkan, OpenGL (ES) 2.0, Direct3D 11) are reasonably complete
in the sense that it should be possible to bring up a Qt Quick renderer on top of them
(using Vulkan-style GLSL as the "common" shading language - translation seems to work
pretty well for now, even to HLSL). Next up is a Metal backend and some cleanup.
Experiments for more modern graphics shader management in future Qt (QtShaderTools)
===================================================================
Uses https://github.com/KhronosGroup/SPIRV-Cross and https://github.com/KhronosGroup/glslang
1. qmake && (n)make
2. QT += shadertools
3. Use QSpirvCompiler to compile (Vulkan-flavored) GLSL to SPIR-V. This can also optionally rewrite the input source to make it suitable for the batched pass of Qt Quick's default OpenGL renderer.
4. Use QSpirvShader parse a SPIR-V binary to get reflection data, to strip the binary, and to translate to GLSL suitable for various OpenGL (ES) versions. (or to HLSL/MSL)
5. The reflection data (QShaderDescription) can also be serialized to binary and human-readable JSON, and deserialized from binary JSON.
Alternatively,
3. Run the qsb tool to generate a single file with reflection info and multiple variants (SPIR-V, HLSL, various GLSL versions) of the input shader source.
4. Use QBakedShader to load and access those at run time.
The latter is what the RHI uses, and expects applications to provide QBakedShader packs.
In action
=========
Screenshots from the test application demonstrating basic drawing, pipeline
state (blending, depth), indexed drawing, texturing, and rendering into a
texture. All using the same code and the same two sets of vertex and fragment
shaders, with the only difference being in the QWindow setup.
![](https://git.qt.io/laagocs/qtrhi/raw/master/screenshot_d3d.png)
![](https://git.qt.io/laagocs/qtrhi/raw/master/screenshot_gl.png)
![](https://git.qt.io/laagocs/qtrhi/raw/master/screenshot_vk.png)
![](https://git.qt.io/laagocs/qtrhi/raw/master/screenshot_mtl.png)
Additionally, check
https://git.qt.io/laagocs/qtrhi/raw/master/examples/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.cpp
for a single-source, cross-API example of drawing a triangle.
The https://git.qt.io/laagocs/qtrhi/tree/master/examples/rhi/multiwindow_threaded demo, it being a minimal reimplementation of
QSGThreadedRenderLoop, represents a milestone in the sense that it proves that implementing the traditional threaded Qt Quick
rendering model is possible with QRhi as well.
TEMPLATE = subdirs
SUBDIRS += \
rhi \
shadertools
/****************************************************************************
**
** 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 QBakedShader vs = getShader(QLatin1String(":/texture.vert.qsb"));
if (!vs.isValid())
qFatal("Failed to load shader pack (vertex)");
const QBakedShader 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.bindings = {
{ 3 * sizeof(float) },
{ 2 * sizeof(float) }
};
inputLayout.attributes = {
{ 0, 0, QRhiVertexInputLayout::Attribute::Float3, 0 },
{ 1, 1, QRhiVertexInputLayout::Attribute::Float2, 0 }
};
d.ps->setVertexInputLayout(inputLayout);
d.ps->setShaderResourceBindings(d.srb);
d.ps->setRenderPassDescriptor(m_rp);
d.ps->build();
}
void Window::customRelease()
{
if (d.ps) {
d.ps->releaseAndDestroy();
d.ps = nullptr;
}
if (d.srb) {
d.srb->releaseAndDestroy();
d.srb = nullptr;
}
if (d.ubuf) {
d.ubuf->releaseAndDestroy();
d.ubuf = nullptr;
}
if (d.vbuf) {
d.vbuf->releaseAndDestroy();
d.vbuf = nullptr;
}
if (d.sampler) {
d.sampler->releaseAndDestroy();
d.sampler = nullptr;
}
if (d.tex) {
d.tex->releaseAndDestroy();
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;
QRhiTextureUploadDescription::Layer layer;
for (int i = 0; i < d.compressedData.count(); ++i) {
QRhiTextureUploadDescription::Layer::MipLevel image(d.compressedData[i]);
layer.mipImages.append(image);
}
desc.layers.append(layer);
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(), { 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->setVertexInput(0, { { d.vbuf, 0 }, { d.vbuf, 36 * 3 * sizeof(float) } });
cb->draw(36);
cb->endPass();
}
TEMPLATE = app
QT += shadertools rhi
SOURCES = \
compressedtexture_bc1.cpp
RESOURCES = compressedtexture_bc1.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/rhi/compressedtexture_bc1
INSTALLS += target
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="qt256_bc1_9mips.dds">../shared/qt256_bc1_9mips.dds</file>
<file alias="texture.vert.qsb">../shared/texture.vert.qsb</file>
<file alias="texture.frag.qsb">../shared/texture.frag.qsb</file>
</qresource>
</RCC>
/****************************************************************************
**
** 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 QBakedShader vs = getShader(QLatin1String(":/texture.vert.qsb"));
if (!vs.isValid())
qFatal("Failed to load shader pack (vertex)");
const QBakedShader 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.bindings = {
{ 3 * sizeof(float) },
{ 2 * sizeof(float) }
};
inputLayout.attributes = {
{ 0, 0, QRhiVertexInputLayout::Attribute::Float3, 0 },
{ 1, 1, QRhiVertexInputLayout::Attribute::Float2, 0 }
};
d.ps->setVertexInputLayout(inputLayout);
d.ps->setShaderResourceBindings(d.srb);
d.ps->setRenderPassDescriptor(m_rp);
d.ps->build();
}
void Window::customRelease()
{
if (d.ps) {
d.ps->releaseAndDestroy();
d.ps = nullptr;
}
if (d.srb) {
d.srb->releaseAndDestroy();
d.srb = nullptr;
}
if (d.ubuf) {
d.ubuf->releaseAndDestroy();
d.ubuf = nullptr;
}
if (d.vbuf) {
d.vbuf->releaseAndDestroy();
d.vbuf = nullptr;
}
if (d.sampler) {
d.sampler->releaseAndDestroy();
d.sampler = nullptr;
}
if (d.tex) {
d.tex->releaseAndDestroy();
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;
QRhiTextureUploadDescription::Layer layer;
QRhiTextureUploadDescription::Layer::MipLevel image(d.compressedData[0]);
layer.mipImages.append(image);
desc.layers.append(layer);
u->uploadTexture(d.tex, desc);
d.compressedData.clear();
}
// now exercise uploading a smaller compressed image into the same texture
{
QRhiTextureUploadDescription desc;
QRhiTextureUploadDescription::Layer layer;
QRhiTextureUploadDescription::Layer::MipLevel image(d.compressedData2[0]);
// positions and sizes must be multiples of 4 here (for BC1)
image.destinationTopLeft = QPoint(16, 32);
// the image is smaller than the subresource size (224x64 vs
// 256x256) so the size must be specified manually
image.sourceSize = QSize(224, 64);
layer.mipImages.append(image);
desc.layers.append(layer);
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(), { 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->setVertexInput(0, { { d.vbuf, 0 }, { d.vbuf, 36 * 3 * sizeof(float) } });
cb->draw(36);
cb->endPass();
}
TEMPLATE = app
QT += shadertools rhi
SOURCES = \
compressedtexture_bc1_subupload.cpp
RESOURCES = compressedtexture_bc1_subupload.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/rhi/compressedtexture_bc1_subupload
INSTALLS += target
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="qt256_bc1_9mips.dds">../shared/qt256_bc1_9mips.dds</file>
<file alias="bwqt224_64_nomips.dds">../shared/bwqt224_64_nomips.dds</file>
<file alias="texture.vert.qsb">../shared/texture.vert.qsb</file>
<file alias="texture.frag.qsb">../shared/texture.frag.qsb</file>
</qresource>
</RCC>
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
/****************************************************************************
**
** 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.
**