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

Standalone qtrhi with qbakedshader

parent db048497
![](https://git.qt.io/laagocs/qtrhi/raw/master/rhi2.png)
Experiments for a Rendering Hardware Interface abstraction for future Qt (QtRhi)
========================================================================
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.
Experiments for more modern graphics shader management in future Qt (QtShaderTools)
===================================================================
Uses https://github.com/KhronosGroup/SPIRV-Cross and https://github.com/KhronosGroup/glslang
QShaderBaker: Compile (Vulkan-flavor) GLSL to SPIR-V. Generate reflection info.
Translate to HLSL, MSL, and various GLSL versions. Optionally rewrite vertex
shaders to make them suitable for Qt Quick scenegraph batching. Pack all this
into conveniently (de)serializable QBakedShader instances. Complemented by a
command-line tool (qsb) to allow doing the expensive work offline. This
optionally allows invoking fxc or metal/metallib to include compiled bytecode
for HLSL and MSL as well.
Documentation
=============
Generated docs are now online at https://alpqr.github.io
In action
=========
Needs Qt 5.12. Tested on Windows 10 with MSVC2015 and 2017, and macOS 10.14 with XCode 10.
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/blob/master/examples/rhi/hellominimalcrossgfxtriangle/hellominimalcrossgfxtriangle.cpp
for a single-source, cross-API example of drawing a triangle.
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
compressedtexture_bc1.cpp
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
compressedtexture_bc1_subupload.cpp
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
cubemap.cpp
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
hellominimalcrossgfxtriangle.cpp
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
imguidemo.cpp \
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
msaarenderbuffer.cpp
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
msaatexture.cpp
......
TEMPLATE = app
QT += shadertools rhi widgets
QT += rhi widgets
SOURCES = \
multiwindow.cpp
......
......@@ -338,9 +338,6 @@ Renderer::~Renderer()
#endif
}
bool useRsh = false;
QRhiResourceSharingHost *rsh = nullptr;
void Renderer::createRhi()
{
if (r)
......@@ -349,17 +346,9 @@ void Renderer::createRhi()
qDebug() << "renderer" << this << "creating rhi";
QRhi::Flags rhiFlags = QRhi::EnableProfiling;
if (useRsh) {
qDebug("Using QRhiResourceSharingHost");
if (!rsh)
rsh = new QRhiResourceSharingHost;
}
#ifndef QT_NO_OPENGL
if (graphicsApi == OpenGL) {
QRhiGles2InitParams params;
if (useRsh)
params.resourceSharingHost = rsh;
params.fallbackSurface = fallbackSurface;
params.window = window;
r = QRhi::create(QRhi::OpenGLES2, &params, rhiFlags);
......@@ -369,8 +358,6 @@ void Renderer::createRhi()
#if QT_CONFIG(vulkan)
if (graphicsApi == Vulkan) {
QRhiVulkanInitParams params;
if (useRsh)
params.resourceSharingHost = rsh;
params.inst = instance;
params.window = window;
r = QRhi::create(QRhi::Vulkan, &params, rhiFlags);
......@@ -380,8 +367,6 @@ void Renderer::createRhi()
#ifdef Q_OS_WIN
if (graphicsApi == D3D11) {
QRhiD3D11InitParams params;
if (useRsh)
params.resourceSharingHost = rsh;
params.enableDebugLayer = true;
r = QRhi::create(QRhi::D3D11, &params, rhiFlags);
}
......@@ -390,8 +375,6 @@ void Renderer::createRhi()
#ifdef Q_OS_DARWIN
if (graphicsApi == Metal) {
QRhiMetalInitParams params;
if (useRsh)
params.resourceSharingHost = rsh;
r = QRhi::create(QRhi::Metal, &params, rhiFlags);
}
#endif
......@@ -808,20 +791,13 @@ int main(int argc, char **argv)
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\nCan also be used to exercise creating sets of windows that can \"see\" each others' resources by using the same graphics device. "
"(although no application-side QRhiResource is reused here)"
"\n\nUsing API: ") + graphicsApiName());
info->setReadOnly(true);
layout->addWidget(info);
QCheckBox *rshCb = new QCheckBox(QLatin1String("Use QRhiResourceSharingHost for new window\n(use same device, "
"e.g. VkDevice+VkQueue on Vulkan, ID3D11Device+Context on D3D; or sharing contexts on OpenGL)"));
rshCb->setChecked(false);
layout->addWidget(rshCb);
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, rshCb] {
useRsh = rshCb->isChecked();
QObject::connect(btn, &QPushButton::clicked, btn, [label, &winCount] {
winCount += 1;
label->setText(QString::asprintf("Window count: %d", winCount));
createWindow();
......@@ -845,8 +821,6 @@ int main(int argc, char **argv)
delete wr.window;
}
delete rsh;
#if QT_CONFIG(vulkan)
delete instance;
#endif
......
TEMPLATE = app
QT += shadertools rhi widgets
QT += rhi widgets
SOURCES = \
multiwindow_threaded.cpp \
......
TEMPLATE = app
CONFIG += console
QT += shadertools rhi
QT += rhi
SOURCES = \
offscreen.cpp
......
......@@ -12,7 +12,6 @@ SUBDIRS += \
multiwindow_threaded \
imguidemo \
triquadcube \
sharedresource \
offscreen
qtConfig(vulkan) {
......
/****************************************************************************
**
** 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$
**
****************************************************************************/
// Demonstrates using the same QRhiTexture with two QRhi instances.
#include <QGuiApplication>
#include <QCommandLineParser>
#include <QWindow>
#include <QPlatformSurfaceEvent>
#include <QElapsedTimer>
#include <QBakedShader>
#include <QFile>
#include <QOffscreenSurface>
#ifndef QT_NO_OPENGL
#include <QRhiGles2InitParams>
#endif
#if QT_CONFIG(vulkan)
#include <QLoggingCategory>
#include <QRhiVulkanInitParams>
#endif
#ifdef Q_OS_WIN
#include <QRhiD3D11InitParams>
#endif
#ifdef Q_OS_DARWIN
#include <QRhiMetalInitParams>
#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();
}
#if QT_CONFIG(vulkan)
QVulkanInstance *vkinst = nullptr;
int activeRhiCount = 0;
QRhiResourceSharingHost *rsh = nullptr;
QRhiTexture *tex = nullptr;
#endif
void createRhi(QWindow *window, QRhi **rhi, QOffscreenSurface **fallbackSurface)
{
// This is what makes the difference here - create a single
// QRhiResourceSharingHost and associate all QRhis with it.
if (!rsh)
rsh = new QRhiResourceSharingHost;
#ifndef QT_NO_OPENGL
if (graphicsApi == OpenGL) {
*fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
QRhiGles2InitParams params;
params.resourceSharingHost = rsh;
params.fallbackSurface = *fallbackSurface;
params.window = window;
*rhi = QRhi::create(QRhi::OpenGLES2, &params);
}
#endif
#if QT_CONFIG(vulkan)
if (graphicsApi == Vulkan) {
QRhiVulkanInitParams params;
params.resourceSharingHost = rsh;
params.inst = vkinst;
params.window = window;
*rhi = QRhi::create(QRhi::Vulkan, &params);
}
#endif
#ifdef Q_OS_WIN
if (graphicsApi == D3D11) {
QRhiD3D11InitParams params;
params.resourceSharingHost = rsh;
params.enableDebugLayer = true;
*rhi = QRhi::create(QRhi::D3D11, &params);
}
#endif
#ifdef Q_OS_DARWIN
if (graphicsApi == Metal) {
QRhiMetalInitParams params;
params.resourceSharingHost = rsh;
*rhi = QRhi::create(QRhi::Metal, &params);
}
#endif
if (!*rhi)
qFatal("Failed to create RHI backend");
}
class Window : public QWindow
{
public:
Window(const QString &title, const QColor &bgColor, int windowNumber);
~Window();
protected:
void init();
void releaseResources();
void resizeSwapChain();
void releaseSwapChain();
void render();
void exposeEvent(QExposeEvent *) override;
bool event(QEvent *) override;
QRhi *m_rhi = nullptr;
QOffscreenSurface *m_fallbackSurface = nullptr;
QColor m_bgColor;
int m_windowNumber;
bool m_running = false;
bool m_notExposed = false;
bool m_newlyExposed = false;
QMatrix4x4 m_proj;
float m_rotation = 0;
QVector<QRhiResource *> m_releasePool;
bool m_hasSwapChain = false;
QRhiSwapChain *m_sc = nullptr;
QRhiRenderBuffer *m_ds = nullptr;
QRhiRenderPassDescriptor *m_rp = nullptr;
QRhiResourceUpdateBatch *initialUpdates = nullptr;
QRhiBuffer *vbuf = nullptr;
QRhiBuffer *ibuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiSampler *sampler = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
};
Window::Window(const QString &title, const QColor &bgColor, int windowNumber)
: m_bgColor(bgColor),
m_windowNumber(windowNumber)
{
switch (graphicsApi) {
case OpenGL:
setSurfaceType(OpenGLSurface);
break;
case Vulkan:
setSurfaceType(VulkanSurface);
setVulkanInstance(vkinst);
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();
render();
}
// 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;
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<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
releaseSwapChain();
break;
default:
break;
}
return QWindow::event(e);
}
static float quadVert[] =
{
-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 quadIndex[] =
{
0, 1, 2, 0, 2, 3
};
QBakedShader getShader(const QString &name)
{
QFile f(name);
if (f.open(QIODevice::ReadOnly))
return QBakedShader::fromSerialized(f.readAll());
return QBakedShader();
}
void Window::init()
{
createRhi(this, &m_rhi, &m_fallbackSurface);
++activeRhiCount;
m_sc = m_rhi->newSwapChain();
m_ds = m_rhi->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);
m_rp = m_sc->newCompatibleRenderPassDescriptor();
m_releasePool << m_rp;
m_sc->setRenderPassDescriptor(m_rp);
vbuf = m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVert));
m_releasePool << vbuf;
vbuf->build();
ibuf = m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(quadIndex));
m_releasePool << ibuf;
ibuf->build();
ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
ubuf->build();
m_releasePool << ubuf;
QImage image;
bool newTex = false;
if (!tex) {
newTex = true;
image.load(QLatin1String(":/qt256.png"));
tex = m_rhi->newTexture(QRhiTexture::RGBA8, image.size());
Q_ASSERT(tex->isShareable());
tex->build();
}
sampler = m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
m_releasePool << sampler;
sampler->build();
srb = m_rhi->newShaderResourceBindings();
m_releasePool << srb;
srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, tex, sampler)
});
srb->build();
ps = m_rhi->newGraphicsPipeline();
m_releasePool << ps;
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) }
});
ps->setVertexInputLayout(inputLayout);
ps->setShaderResourceBindings(srb);
ps->setRenderPassDescriptor(m_rp);
ps->build();
initialUpdates = m_rhi->nextResourceUpdateBatch();
initialUpdates->uploadStaticBuffer(vbuf, 0, sizeof(quadVert), quadVert);
initialUpdates->uploadStaticBuffer(ibuf, quadIndex);