Commit be7bda45 authored by Laszlo Agocs's avatar Laszlo Agocs

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) {
......
This diff is collapsed.
TEMPLATE = app
QT += shadertools rhi
SOURCES = \
sharedresource.cpp
RESOURCES = sharedresource.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/rhi/sharedresource
INSTALLS += target
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="texture.vert.qsb">../shared/texture.vert.qsb</file>
<file alias="texture.frag.qsb">../shared/texture.frag.qsb</file>
<file alias="qt256.png">../shared/qt256.png</file>
</qresource>
</RCC>
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
texuploads.cpp
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
triquadcube.cpp \
......
TEMPLATE = app
QT += shadertools rhi
QT += rhi
SOURCES = \
main.cpp \
......
/*
** Copyright (c) 2014-2016 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and/or associated documentation files (the "Materials"),
** to deal in the Materials without restriction, including without limitation
** the rights to use, copy, modify, merge, publish, distribute, sublicense,
** and/or sell copies of the Materials, and to permit persons to whom the
** Materials are furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Materials.
**
** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
** IN THE MATERIALS.
*/
#ifndef GLSLstd450_H
#define GLSLstd450_H
static const int GLSLstd450Version = 100;
static const int GLSLstd450Revision = 3;
enum GLSLstd450 {
GLSLstd450Bad = 0, // Don't use
GLSLstd450Round = 1,
GLSLstd450RoundEven = 2,
GLSLstd450Trunc = 3,
GLSLstd450FAbs = 4,
GLSLstd450SAbs = 5,
GLSLstd450FSign = 6,
GLSLstd450SSign = 7,
GLSLstd450Floor = 8,
GLSLstd450Ceil = 9,
GLSLstd450Fract = 10,
GLSLstd450Radians = 11,
GLSLstd450Degrees = 12,
GLSLstd450Sin = 13,
GLSLstd450Cos = 14,
GLSLstd450Tan = 15,
GLSLstd450Asin = 16,
GLSLstd450Acos = 17,
GLSLstd450Atan = 18,
GLSLstd450Sinh = 19,
GLSLstd450Cosh = 20,
GLSLstd450Tanh = 21,
GLSLstd450Asinh = 22,
GLSLstd450Acosh = 23,
GLSLstd450Atanh = 24,
GLSLstd450Atan2 = 25,
GLSLstd450Pow = 26,
GLSLstd450Exp = 27,
GLSLstd450Log = 28,
GLSLstd450Exp2 = 29,
GLSLstd450Log2 = 30,
GLSLstd450Sqrt = 31,
GLSLstd450InverseSqrt = 32,
GLSLstd450Determinant = 33,
GLSLstd450MatrixInverse = 34,
GLSLstd450Modf = 35, // second operand needs an OpVariable to write to
GLSLstd450ModfStruct = 36, // no OpVariable operand
GLSLstd450FMin = 37,
GLSLstd450UMin = 38,
GLSLstd450SMin = 39,
GLSLstd450FMax = 40,
GLSLstd450UMax = 41,
GLSLstd450SMax = 42,
GLSLstd450FClamp = 43,
GLSLstd450UClamp = 44,
GLSLstd450SClamp = 45,
GLSLstd450FMix = 46,
GLSLstd450IMix = 47, // Reserved
GLSLstd450Step = 48,
GLSLstd450SmoothStep = 49,
GLSLstd450Fma = 50,
GLSLstd450Frexp = 51, // second operand needs an OpVariable to write to
GLSLstd450FrexpStruct = 52, // no OpVariable operand
GLSLstd450Ldexp = 53,
GLSLstd450PackSnorm4x8 = 54,
GLSLstd450PackUnorm4x8 = 55,
GLSLstd450PackSnorm2x16 = 56,
GLSLstd450PackUnorm2x16 = 57,
GLSLstd450PackHalf2x16 = 58,
GLSLstd450PackDouble2x32 = 59,
GLSLstd450UnpackSnorm2x16 = 60,
GLSLstd450UnpackUnorm2x16 = 61,
GLSLstd450UnpackHalf2x16 = 62,
GLSLstd450UnpackSnorm4x8 = 63,
GLSLstd450UnpackUnorm4x8 = 64,
GLSLstd450UnpackDouble2x32 = 65,
GLSLstd450Length = 66,
GLSLstd450Distance = 67,
GLSLstd450Cross = 68,
GLSLstd450Normalize = 69,
GLSLstd450FaceForward = 70,
GLSLstd450Reflect = 71,
GLSLstd450Refract = 72,
GLSLstd450FindILsb = 73,
GLSLstd450FindSMsb = 74,
GLSLstd450FindUMsb = 75,
GLSLstd450InterpolateAtCentroid = 76,
GLSLstd450InterpolateAtSample = 77,
GLSLstd450InterpolateAtOffset = 78,
GLSLstd450NMin = 79,
GLSLstd450NMax = 80,
GLSLstd450NClamp = 81,
GLSLstd450Count
};
#endif // #ifndef GLSLstd450_H
This diff is collapsed.
TARGET := spirv-cross
SOURCES := $(wildcard spirv_*.cpp)
CLI_SOURCES := main.cpp
OBJECTS := $(SOURCES:.cpp=.o)
CLI_OBJECTS := $(CLI_SOURCES:.cpp=.o)
STATIC_LIB := lib$(TARGET).a
DEPS := $(OBJECTS:.o=.d) $(CLI_OBJECTS:.o=.d)
CXXFLAGS += -std=c++11 -Wall -Wextra -Wshadow -D__STDC_LIMIT_MACROS
ifeq ($(DEBUG), 1)
CXXFLAGS += -O0 -g
else
CXXFLAGS += -O2 -DNDEBUG
endif
ifeq ($(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS), 1)
CXXFLAGS += -DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS -fno-exceptions
endif
all: $(TARGET)
-include $(DEPS)
$(TARGET): $(CLI_OBJECTS) $(STATIC_LIB)
$(CXX) -o $@ $(CLI_OBJECTS) $(STATIC_LIB) $(LDFLAGS)
$(STATIC_LIB): $(OBJECTS)
$(AR) rcs $@ $(OBJECTS)
%.o: %.cpp
$(CXX) -c -o $@ $< $(CXXFLAGS) -MMD
clean:
rm -f $(TARGET) $(OBJECTS) $(CLI_OBJECTS) $(STATIC_LIB) $(DEPS)
.PHONY: clean
[
{
"Id": "SpirvCross",
"Name": "SPIRV-Cross",
"QDocModule": "qtshadertools",
"Description": "A practical tool and library for performing reflection on SPIR-V and disassembling SPIR-V back to high level languages.",
"QtUsage": "Shader code generation.",
"Homepage": "https://github.com/KhronosGroup/SPIRV-Cross",
"Version": "bfeb388edfb63c9a927e517b634abaaa5bfb1caf",
"License": "Apache License 2.0",
"LicenseId": "Apache-2.0",
"LicenseFile": "LICENSE",
"Copyright": "Copyright 2016-2018 ARM Limited"
}
]
This diff is collapsed.
/*
* Copyright 2016-2018 ARM Limited
*
* 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.
*/
#include "spirv_cfg.hpp"
#include "spirv_cross.hpp"
#include <algorithm>
#include <assert.h>
using namespace std;
namespace spirv_cross
{
CFG::CFG(Compiler &compiler_, const SPIRFunction &func_)
: compiler(compiler_)
, func(func_)
{
preceding_edges.resize(compiler.get_current_id_bound());
succeeding_edges.resize(compiler.get_current_id_bound());
visit_order.resize(compiler.get_current_id_bound());
immediate_dominators.resize(compiler.get_current_id_bound());
build_post_order_visit_order();
build_immediate_dominators();
}
uint32_t CFG::find_common_dominator(uint32_t a, uint32_t b) const
{
while (a != b)
{
if (visit_order[a] < visit_order[b])
a = immediate_dominators[a];
else
b = immediate_dominators[b];
}
return a;
}
void CFG::build_immediate_dominators()
{
// Traverse the post-order in reverse and build up the immediate dominator tree.
fill(begin(immediate_dominators), end(immediate_dominators), 0);
immediate_dominators[func.entry_block] = func.entry_block;
for (auto i = post_order.size(); i; i--)
{
uint32_t block = post_order[i - 1];
auto &pred = preceding_edges[block];
if (pred.empty()) // This is for the entry block, but we've already set up the dominators.
continue;
for (auto &edge : pred)
{
if (immediate_dominators[block])
{
assert(immediate_dominators[edge]);
immediate_dominators[block] = find_common_dominator(block, edge);
}
else
immediate_dominators[block] = edge;
}
}
}
bool CFG::is_back_edge(uint32_t to) const
{
// We have a back edge if the visit order is set with the temporary magic value 0.
// Crossing edges will have already been recorded with a visit order.
return visit_order[to] == 0;
}
bool CFG::post_order_visit(uint32_t block_id)
{
// If we have already branched to this block (back edge), stop recursion.
// If our branches are back-edges, we do not record them.
// We have to record crossing edges however.
if (visit_order[block_id] >= 0)
return !is_back_edge(block_id);
// Block back-edges from recursively revisiting ourselves.
visit_order[block_id] = 0;
// First visit our branch targets.
auto &block = compiler.get<SPIRBlock>(block_id);
switch (block.terminator)
{
case SPIRBlock::Direct:
if (post_order_visit(block.next_block))
add_branch(block_id, block.next_block);
break;
case SPIRBlock::Select:
if (post_order_visit(block.true_block))
add_branch(block_id, block.true_block);
if (post_order_visit(block.false_block))
add_branch(block_id, block.false_block);
break;
case SPIRBlock::MultiSelect:
for (auto &target : block.cases)
{
if (post_order_visit(target.block))
add_branch(block_id, target.block);
}
if (block.default_block && post_order_visit(block.default_block))
add_branch(block_id, block.default_block);
break;
default:
break;
}
// If this is a loop header, add an implied branch to the merge target.
// This is needed to avoid annoying cases with do { ... } while(false) loops often generated by inliners.
// To the CFG, this is linear control flow, but we risk picking the do/while scope as our dominating block.
// This makes sure that if we are accessing a variable outside the do/while, we choose the loop header as dominator.
if (block.merge == SPIRBlock::MergeLoop)
add_branch(block_id, block.merge_block);
// Then visit ourselves. Start counting at one, to let 0 be a magic value for testing back vs. crossing edges.
visit_order[block_id] = ++visit_count;
post_order.push_back(block_id);
return true;
}
void CFG::build_post_order_visit_order()
{
uint32_t block = func.entry_block;
visit_count = 0;
fill(begin(visit_order), end(visit_order), -1);
post_order.clear();
post_order_visit(block);
}