diff options
Diffstat (limited to 'src/shadertools')
-rw-r--r-- | src/shadertools/doc/doc.pri | 3 | ||||
-rw-r--r-- | src/shadertools/doc/qtshadertools.qdocconf | 49 | ||||
-rw-r--r-- | src/shadertools/doc/snippets/color.frag | 16 | ||||
-rw-r--r-- | src/shadertools/doc/snippets/color.vert | 20 | ||||
-rw-r--r-- | src/shadertools/doc/src/qtshadertools-copyright.qdoc | 37 | ||||
-rw-r--r-- | src/shadertools/doc/src/qtshadertools-cpp.qdoc | 45 | ||||
-rw-r--r-- | src/shadertools/doc/src/qtshadertools-index.qdoc | 63 | ||||
-rw-r--r-- | src/shadertools/qshaderbaker.cpp | 421 | ||||
-rw-r--r-- | src/shadertools/qshaderbaker.h | 78 | ||||
-rw-r--r-- | src/shadertools/qshaderbatchablerewriter.cpp | 223 | ||||
-rw-r--r-- | src/shadertools/qshaderbatchablerewriter_p.h | 64 | ||||
-rw-r--r-- | src/shadertools/qspirvcompiler.cpp | 390 | ||||
-rw-r--r-- | src/shadertools/qspirvcompiler_p.h | 89 | ||||
-rw-r--r-- | src/shadertools/qspirvshader.cpp | 468 | ||||
-rw-r--r-- | src/shadertools/qspirvshader_p.h | 101 | ||||
-rw-r--r-- | src/shadertools/qtshadertoolsglobal.h | 62 | ||||
-rw-r--r-- | src/shadertools/qtshadertoolsglobal_p.h | 48 | ||||
-rw-r--r-- | src/shadertools/shadertools.pro | 41 |
18 files changed, 2218 insertions, 0 deletions
diff --git a/src/shadertools/doc/doc.pri b/src/shadertools/doc/doc.pri new file mode 100644 index 0000000..3fa7632 --- /dev/null +++ b/src/shadertools/doc/doc.pri @@ -0,0 +1,3 @@ +QMAKE_DOCS = $$PWD/qtshadertools.qdocconf + +OTHER_FILES += $$PWD/src/*.qdoc diff --git a/src/shadertools/doc/qtshadertools.qdocconf b/src/shadertools/doc/qtshadertools.qdocconf new file mode 100644 index 0000000..9afd3f7 --- /dev/null +++ b/src/shadertools/doc/qtshadertools.qdocconf @@ -0,0 +1,49 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QtShaderTools +description = Qt Shader Tools Reference Documentation +version = $QT_VERSION + +examplesinstallpath = shadertools + +qhp.projects = QtShaderTools + +qhp.QtShaderTools.file = qtshadertools.qhp +qhp.QtShaderTools.namespace = org.qt-project.qtshadertools.$QT_VERSION_TAG +qhp.QtShaderTools.virtualFolder = qtshadertools +qhp.QtShaderTools.indexTitle = Qt Shader Tools +qhp.QtShaderTools.indexRoot = + +qhp.QtShaderTools.filterAttributes = qtshadertools $QT_VERSION qtrefdoc +qhp.QtShaderTools.customFilters.Qt.name = QtShaderTools $QT_VERSION +qhp.QtShaderTools.customFilters.Qt.filterAttributes = qtshadertools $QT_VERSION + +qhp.QtShaderTools.subprojects = classes examples + +qhp.QtShaderTools.subprojects.classes.title = C++ Classes +qhp.QtShaderTools.subprojects.classes.indexTitle = Qt Shader Tools C++ Classes +qhp.QtShaderTools.subprojects.classes.selectors = class fake:headerfile +qhp.QtShaderTools.subprojects.classes.sortPages = true + +qhp.QtShaderTools.subprojects.examples.title = Examples +qhp.QtShaderTools.subprojects.examples.indexTitle = Qt Shader Tools Examples and Tutorials +qhp.QtShaderTools.subprojects.examples.selectors = fake:example + +tagfile = ../../../doc/qtshadertools/qtshadertools.tags + +depends += qtcore qtgui + +headerdirs += .. + +sourcedirs += .. + +exampledirs += ../../../examples/shadertools \ + snippets + +imagedirs += images + +navigation.landingpage = "Qt Shader Tools" +navigation.landingtitle = "Shader Tools" +navigation.cppclassespage = "Qt Shader Tools C++ Classes" + +Cpp.ignoretokens += Q_SHADERTOOLS_EXPORT diff --git a/src/shadertools/doc/snippets/color.frag b/src/shadertools/doc/snippets/color.frag new file mode 100644 index 0000000..859946a --- /dev/null +++ b/src/shadertools/doc/snippets/color.frag @@ -0,0 +1,16 @@ +//! [0] +#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); +} +//! [0] diff --git a/src/shadertools/doc/snippets/color.vert b/src/shadertools/doc/snippets/color.vert new file mode 100644 index 0000000..9e21794 --- /dev/null +++ b/src/shadertools/doc/snippets/color.vert @@ -0,0 +1,20 @@ +//! [0] +#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; +} +//! [0] diff --git a/src/shadertools/doc/src/qtshadertools-copyright.qdoc b/src/shadertools/doc/src/qtshadertools-copyright.qdoc new file mode 100644 index 0000000..f7a5e7c --- /dev/null +++ b/src/shadertools/doc/src/qtshadertools-copyright.qdoc @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:FDL$ +** 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. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page copyright-notices.html +\title Copyright Notices +\section1 Third-party Licenses + +The following table lists parts (modules) of Qt Rendering Hardware Interface that +incorporate code licensed under third-party open-source licenses: + +\annotatedlist attributions-qtshadertools +*/ diff --git a/src/shadertools/doc/src/qtshadertools-cpp.qdoc b/src/shadertools/doc/src/qtshadertools-cpp.qdoc new file mode 100644 index 0000000..2d1ea91 --- /dev/null +++ b/src/shadertools/doc/src/qtshadertools-cpp.qdoc @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:FDL$ +** 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. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \module QtShaderTools + \title Qt Shader Tools C++ Classes + \ingroup modules + \qtvariable shadertools + + To include the definitions of the module's classes, use the following directive: + + \badcode + #include <QtShaderTools> + \endcode + + To link against the module, add this line to your \l qmake \c .pro file: + + \badcode + QT += shadertools + \endcode +*/ diff --git a/src/shadertools/doc/src/qtshadertools-index.qdoc b/src/shadertools/doc/src/qtshadertools-index.qdoc new file mode 100644 index 0000000..4e0d8aa --- /dev/null +++ b/src/shadertools/doc/src/qtshadertools-index.qdoc @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:FDL$ +** 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. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\title Qt Shader Tools +\page qtshadertools-index.html + +\section1 Introduction + +The Qt Shader Tools module builds on the SPIR-V Open Source Ecosystem as +described at \l{https://www.khronos.org/spir/}{the Khronos SPIR-V web +site}. For compiling into SPIR-V +\l{https://github.com/KhronosGroup/glslang}{glslang} is used, while +translating and reflecting is done via +\l{https://github.com/KhronosGroup/SPIRV-Cross}{SPIRV-Cross}. + +In order to allow shader code to be written once in Qt applications and +libraries, all shaders are expected to be written in a single language +which is then compiled into SPIR-V. Versions for various shading language +are then generated from that, together with reflection information (inputs, +outputs, shader resources). This is then packed into easily and efficiently +serializable QRhiShader instances. The Qt Rendering Hardware Interface +consumes QRhiShader instances directly. + +The two main components are: + +\list +\li QShaderBaker and the \c qsb command-line tool, and +\li QRhiShader (part of the Qt Rhi module) +\endlist + +\section1 Table of Contents + +\list + \li \l {Qt Shader Tools C++ Classes} + \li \l {Copyright Notices} +\endlist + +*/ diff --git a/src/shadertools/qshaderbaker.cpp b/src/shadertools/qshaderbaker.cpp new file mode 100644 index 0000000..fbb0a2b --- /dev/null +++ b/src/shadertools/qshaderbaker.cpp @@ -0,0 +1,421 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qshaderbaker.h" +#include "qspirvcompiler_p.h" +#include "qspirvshader_p.h" +#include <QFileInfo> +#include <QFile> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \class QShaderBaker + \inmodule QtShaderTools + + \brief Compiles a GLSL/Vulkan shader into SPIR-V, translates into other + shading languages, and gathers reflection metadata. + + QShaderBaker takes a graphics (vertex, fragment, etc.) or compute shader, + and produces multiple - either source or bytecode - variants of it, + together with reflection information. The results are represented by a + QRhiShader instance, which also provides simple and fast serialization + and deserialization. + + \note Applications and libraries are recommended to avoid using this class + directly. Rather, all Qt users are encouraged to rely on offline + compilation by invoking the \c qsb command-line tool at build time. This + tool uses QShaderBaker itself and writes the serialized version of the + generated QRhiShader into a file. The usage of this class should be + restricted to cases where run time compilation cannot be avoided, such as + when working with user-provided shader source strings. + + The input format is always assumed to be Vulkan-flavored GLSL at the + moment. See the + \l{https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_vulkan_glsl.txt}{GL_KHR_vulkan_glsl + specification} for an overview, keeping in mind that the Qt Shader Tools + module is meant to be used in combination with the QRhi classes from Qt + Rendering Hardware Interface module, and therefore a number of concepts and + constructs (push constants, storage buffers, subpasses, etc.) are not + applicable at the moment. Additional options may be introduced in the + future, for example, by enabling + \l{https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl}{HLSL} + as a source format, once HLSL to SPIR-V compilation is deemed suitable. + + The reflection metadata is retrievable from the resulting QRhiShader by + calling QRhiShader::description(). This is essential when having to + discover what set of vertex inputs and shader resources a shader expects, + and what the layouts of those are, as many modern graphics APIs offer no + built-in shader reflection capabilities. + + \section2 Typical Workflow + + Let's assume an application has a vertex and fragment shader like the following: + + Vertex shader: + \snippet color.vert 0 + + Fragment shader: + \snippet color.frag 0 + + To get QRhiShader instances that can be passed as-is to a + QRhiGraphicsPipeline, there are two options: doing the shader pack + generation off line, or at run time. + + The former involves running the \c qsb tool: + + \badcode + qsb --glsl "100 es,120" --hlsl 50 --msl 12 color.vert -o color.vert.qsb + qsb --glsl "100 es,120" --hlsl 50 --msl 12 color.frag -o color.frag.qsb + \endcode + + The example uses the translation targets as appropriate for QRhi. This + means GLSL/ES 100, GLSL 120, HLSL Shader Model 5.0, and Metal Shading + Language 1.2. + + Note how the command line options correspond to what can be specified via + setGeneratedShaders(). Once the resulting files are available, they can be + shipped with the application (typically embedded into the executable the + the Qt Resource System), and can be loaded and passed to + QRhiShader::fromSerialized() at run time. + + While not shown here, \c qsb can do more: it is also able to invoke \c fxc + on Windows or the appropriate XCode tools on macOS to compile the generated + HLSL or Metal shader code into bytecode and include the compiled versions + in the QRhiShader. After a baked shader pack is written into a file, its + contents can be examined by running \c{qsb -d} on it. Run \c qsb with + \c{--help} for more information. + + The alternative approach is to perform the same at run time. This involves + creating a QShaderBaker instance, calling setSourceFileName(), and then + setting up the translation targets via setGeneratedShaders(): + + \badcode + baker.setGeneratedShaderVariants({ QRhiShaderKey::StandardShader }); + QVector<QShaderBaker::GeneratedShader> targets; + targets.append({ QRhiShaderKey::SpirvShader, QRhiShaderVersion(100) }); + targets.append({ QRhiShaderKey::GlslShader, QRhiShaderVersion(100, QRhiShaderVersion::GlslEs) }); + targets.append({ QRhiShaderKey::SpirvShader, QRhiShaderVersion(120) }); + targets.append({ QRhiShaderKey::HlslShader, QRhiShaderVersion(50) }); + targets.append({ QRhiShaderKey::MslShader, QRhiShaderVersion(12) }); + baker.setGeneratedShaders(targets); + QRhiShader shaders = baker.bake(); + if (!shaders.isValid()) + qWarning() << baker.errorMessage(); + \endcode + + \sa QRhiShader + */ + +struct QShaderBakerPrivate +{ + bool readFile(const QString &fn); + + QString sourceFileName; + QByteArray source; + QRhiShader::ShaderStage stage; + QVector<QShaderBaker::GeneratedShader> reqVersions; + QVector<QRhiShaderKey::ShaderVariant> variants; + QSpirvCompiler compiler; + QString errorMessage; +}; + +bool QShaderBakerPrivate::readFile(const QString &fn) +{ + QFile f(fn); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("QShaderBaker: Failed to open %s", qPrintable(fn)); + return false; + } + source = f.readAll(); + sourceFileName = fn; + return true; +} + +/*! + Constructs a new QShaderBaker. + */ +QShaderBaker::QShaderBaker() + : d(new QShaderBakerPrivate) +{ +} + +/*! + Destructor. + */ +QShaderBaker::~QShaderBaker() +{ + delete d; +} + +/*! + Sets the name of the shader source file to \a fileName. This is the file + that will be read when calling bake(). The shader stage is deduced + automatically from the file extension. When this is not desired or not + possible, use the overload with the stage argument instead. + + The supported file extensions are: + \list + \li \c{.vert} - vertex shader + \li \c{.frag} - fragment (pixel) shader + \li \c{.tesc} - tessellation control (hull) shader + \li \c{.tese} - tessellation evaluation (domain) shader + \li \c{.geom} - geometry shader + \li \c{.comp} - compute shader + \endlist + */ +void QShaderBaker::setSourceFileName(const QString &fileName) +{ + if (!d->readFile(fileName)) + return; + + const QString suffix = QFileInfo(fileName).suffix(); + if (suffix == QStringLiteral("vert")) { + d->stage = QRhiShader::VertexStage; + } else if (suffix == QStringLiteral("frag")) { + d->stage = QRhiShader::FragmentStage; + } else if (suffix == QStringLiteral("tesc")) { + d->stage = QRhiShader::TessControlStage; + } else if (suffix == QStringLiteral("tese")) { + d->stage = QRhiShader::TessEvaluationStage; + } else if (suffix == QStringLiteral("geom")) { + d->stage = QRhiShader::GeometryStage; + } else if (suffix == QStringLiteral("comp")) { + d->stage = QRhiShader::ComputeStage; + } else { + qWarning("QShaderBaker: Unknown shader stage, defaulting to vertex"); + d->stage = QRhiShader::VertexStage; + } +} + +/*! + Sets the name of the shader source file to \a fileName. This is the file + that will be read when calling bake(). The shader stage is specified by \a + stage. + */ +void QShaderBaker::setSourceFileName(const QString &fileName, QRhiShader::ShaderStage stage) +{ + if (d->readFile(fileName)) + d->stage = stage; +} + +/*! + Sets the source \a device. This allows using any QIODevice instead of just + files. \a stage specifies the shader stage, while the optional \a fileName + contains a filename that is used in the error messages. + */ +void QShaderBaker::setSourceDevice(QIODevice *device, QRhiShader::ShaderStage stage, const QString &fileName) +{ + setSourceString(device->readAll(), stage, fileName); +} + +/*! + Sets the input shader \a sourceString. \a stage specified the shader stage, + while the optional \a fileName contains a filename that is used in the + error messages. + */ +void QShaderBaker::setSourceString(const QByteArray &sourceString, QRhiShader::ShaderStage stage, const QString &fileName) +{ + d->sourceFileName = fileName; // for error messages, include handling, etc. + d->source = sourceString; + d->stage = stage; +} + +/*! + \typedef QShaderBaker::GeneratedShader + + Synonym for QPair<QRhiShaderKey::ShaderSource, QRhiShaderVersion>. +*/ + +/*! + Specifies what kind of shaders to compile or translate to. Nothing is + generated by default so calling this function before bake() is mandatory + + \note when this function is not called or \a v is empty or contains only invalid + entries, the resulting QRhiShader will be empty and thus invalid. + + For example, the minimal possible baking target is SPIR-V, without any + additional translations to other languages. To request this, do: + + \badcode + baker.setGeneratedShaders({ QRhiShaderKey::SpirvShader, QRhiShaderVersion(100) }); + \endcode + */ +void QShaderBaker::setGeneratedShaders(const QVector<GeneratedShader> &v) +{ + d->reqVersions = v; +} + +/*! + Specifies which shader variants are genetated. Each shader version can have + multiple variants in the resulting QRhiShader. + + In most cases \a v contains a single entry, QRhiShaderKey::StandardShader. + + \note when no variants are set, the resulting QRhiShader will be empty and + thus invalid. + */ +void QShaderBaker::setGeneratedShaderVariants(const QVector<QRhiShaderKey::ShaderVariant> &v) +{ + d->variants = v; +} + +/*! + Runs the compilation and translation process. + + \return a QRhiShader instance. To check if the process was successful, + call QRhiShader::isValid(). When that indicates \c false, call + errorMessage() to retrieve the log. + + This is an expensive operation. When calling this from applications, it can + be advisable to do it on a separate thread. + + \note QShaderBaker instances are reusable: after calling bake(), the same + instance can be used with different inputs again. However, a QShaderBaker + instance should only be used on one single thread during its lifetime. + */ +QRhiShader QShaderBaker::bake() +{ + d->errorMessage.clear(); + + if (d->source.isEmpty()) { + d->errorMessage = QLatin1String("QShaderBaker: No source specified"); + return QRhiShader(); + } + + d->compiler.setSourceString(d->source, d->stage, d->sourceFileName); + d->compiler.setFlags(0); + QByteArray spirv = d->compiler.compileToSpirv(); + if (spirv.isEmpty()) { + d->errorMessage = d->compiler.errorMessage(); + return QRhiShader(); + } + + QByteArray batchableSpirv; + if (d->stage == QRhiShader::VertexStage && d->variants.contains(QRhiShaderKey::BatchableVertexShader)) { + d->compiler.setFlags(QSpirvCompiler::RewriteToMakeBatchableForSG); + batchableSpirv = d->compiler.compileToSpirv(); + if (batchableSpirv.isEmpty()) { + d->errorMessage = d->compiler.errorMessage(); + return QRhiShader(); + } + } + + QRhiShader bs; + bs.setStage(d->stage); + + QSpirvShader spirvShader; + spirvShader.setSpirvBinary(spirv); + + QSpirvShader batchableSpirvShader; + if (!batchableSpirv.isEmpty()) { + batchableSpirvShader.setSpirvBinary(batchableSpirv); + bs.setDescription(batchableSpirvShader.shaderDescription()); + } else { + bs.setDescription(spirvShader.shaderDescription()); + } + + for (const GeneratedShader &req: d->reqVersions) { + for (const QRhiShaderKey::ShaderVariant &v : d->variants) { + QByteArray *currentSpirv = &spirv; + QSpirvShader *currentSpirvShader = &spirvShader; + if (v == QRhiShaderKey::BatchableVertexShader) { + if (!batchableSpirv.isEmpty()) { + currentSpirv = &batchableSpirv; + currentSpirvShader = &batchableSpirvShader; + } else { + continue; + } + } + const QRhiShaderKey key(req.first, req.second, v); + QRhiShaderCode shader; + shader.setEntryPoint(QByteArrayLiteral("main")); + switch (req.first) { + case QRhiShaderKey::SpirvShader: + shader.setShader(*currentSpirv); + break; + case QRhiShaderKey::GlslShader: + { + QSpirvShader::GlslFlags flags = 0; + if (req.second.flags().testFlag(QRhiShaderVersion::GlslEs)) + flags |= QSpirvShader::GlslEs; + shader.setShader(currentSpirvShader->translateToGLSL(req.second.version(), flags)); + if (shader.shader().isEmpty()) { + d->errorMessage = currentSpirvShader->translationErrorMessage(); + return QRhiShader(); + } + } + break; + case QRhiShaderKey::HlslShader: + shader.setShader(currentSpirvShader->translateToHLSL(req.second.version())); + if (shader.shader().isEmpty()) { + d->errorMessage = currentSpirvShader->translationErrorMessage(); + return QRhiShader(); + } + break; + case QRhiShaderKey::MslShader: + shader.setShader(currentSpirvShader->translateToMSL(req.second.version())); + if (shader.shader().isEmpty()) { + d->errorMessage = currentSpirvShader->translationErrorMessage(); + return QRhiShader(); + } + shader.setEntryPoint(QByteArrayLiteral("main0")); + break; + default: + Q_UNREACHABLE(); + } + bs.setShader(key, shader); + } + } + + return bs; +} + +/*! + \return the error message from the last bake() run, or an empty string if + there was no error. + + \note Errors include file read errors, compilation, and translation + failures. Not requesting any targets or variants does not count as an error + even though the resulting QRhiShader is invalid. + */ +QString QShaderBaker::errorMessage() const +{ + return d->errorMessage; +} + +QT_END_NAMESPACE diff --git a/src/shadertools/qshaderbaker.h b/src/shadertools/qshaderbaker.h new file mode 100644 index 0000000..de6159b --- /dev/null +++ b/src/shadertools/qshaderbaker.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSHADERBAKER_H +#define QSHADERBAKER_H + +#include <QtShaderTools/qtshadertoolsglobal.h> +#include <QtGui/qrhishader.h> + +QT_BEGIN_NAMESPACE + +struct QShaderBakerPrivate; +class QIODevice; + +class Q_SHADERTOOLS_EXPORT QShaderBaker +{ +public: + QShaderBaker(); + ~QShaderBaker(); + + void setSourceFileName(const QString &fileName); + void setSourceFileName(const QString &fileName, QRhiShader::ShaderStage stage); + + void setSourceDevice(QIODevice *device, QRhiShader::ShaderStage stage, + const QString &fileName = QString()); + + void setSourceString(const QByteArray &sourceString, QRhiShader::ShaderStage stage, + const QString &fileName = QString()); + + typedef QPair<QRhiShaderKey::ShaderSource, QRhiShaderVersion> GeneratedShader; + void setGeneratedShaders(const QVector<GeneratedShader> &v); + void setGeneratedShaderVariants(const QVector<QRhiShaderKey::ShaderVariant> &v); + + QRhiShader bake(); + + QString errorMessage() const; + +private: + Q_DISABLE_COPY(QShaderBaker) + QShaderBakerPrivate *d = nullptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shadertools/qshaderbatchablerewriter.cpp b/src/shadertools/qshaderbatchablerewriter.cpp new file mode 100644 index 0000000..72b369f --- /dev/null +++ b/src/shadertools/qshaderbatchablerewriter.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qshaderbatchablerewriter_p.h" + +// This is a slightly modified version of qsgshaderrewriter.cpp from +// qtdeclarative/src/quick/scenegraph/coreapi. Here we insert an extra vertex +// attribute (_qt_order) at location 7. + +QT_BEGIN_NAMESPACE + +namespace QShaderBatchableRewriter { + +struct Tokenizer { + + enum Token { + Token_Void, + Token_OpenBrace, + Token_CloseBrace, + Token_SemiColon, + Token_Identifier, + Token_Macro, + Token_Unspecified, + + Token_EOF + }; + + static const char *NAMES[]; + + void initialize(const QByteArray &input); + Token next(); + + const char *stream; + const char *pos; + const char *identifier; +}; + +const char *Tokenizer::NAMES[] = { + "Void", + "OpenBrace", + "CloseBrace", + "SemiColon", + "Identifier", + "Macro", + "Unspecified", + "EOF" +}; + +void Tokenizer::initialize(const QByteArray &input) +{ + stream = input.constData(); + pos = input; + identifier = input; +} + +Tokenizer::Token Tokenizer::next() +{ + while (*pos != 0) { + char c = *pos++; + switch (c) { + case '/': + + if (*pos == '/') { + // '//' comment + ++pos; + while (*pos != 0 && *pos != '\n') ++pos; + if (*pos != 0) ++pos; // skip the newline + + } else if (*pos == '*') { + // /* */ comment + ++pos; + while (*pos != 0 && *pos != '*' && pos[1] != '/') ++pos; + if (*pos != 0) pos += 2; + } + break; + + case '#': { + while (*pos != 0) { + if (*pos == '\n') { + ++pos; + break; + } else if (*pos == '\\') { + ++pos; + while (*pos != 0 && (*pos == ' ' || *pos == '\t')) + ++pos; + if (*pos != 0 && (*pos == '\n' || (*pos == '\r' && pos[1] == '\n'))) + pos+=2; + } else { + ++pos; + } + } + break; + } + + case 'v': { + if (*pos == 'o' && pos[1] == 'i' && pos[2] == 'd') { + pos += 3; + return Token_Void; + } + Q_FALLTHROUGH(); + } + + case ';': return Token_SemiColon; + case 0: return Token_EOF; + case '{': return Token_OpenBrace; + case '}': return Token_CloseBrace; + + case ' ': + case '\n': + case '\r': break; + default: + // Identifier... + if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') { + identifier = pos - 1; + while (*pos != 0 && ((*pos >= 'a' && *pos <= 'z') + || (*pos >= 'A' && *pos <= 'Z') + || *pos == '_' + || (*pos >= '0' && *pos <= '9'))) { + ++pos; + } + return Token_Identifier; + } else { + return Token_Unspecified; + } + } + } + + return Token_EOF; +} + +QByteArray addZAdjustment(const QByteArray &input) +{ + Tokenizer tok; + tok.initialize(input); + + Tokenizer::Token lt = tok.next(); + Tokenizer::Token t = tok.next(); + + // First find "void main() { ... " + const char* voidPos = input.constData(); + while (t != Tokenizer::Token_EOF) { + if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) { + if (qstrncmp("main", tok.identifier, 4) == 0) + break; + } + voidPos = tok.pos - 4; + lt = t; + t = tok.next(); + } + + QByteArray result; + result.reserve(1024); + result += QByteArray::fromRawData(input.constData(), voidPos - input.constData()); + + result += QByteArrayLiteral("layout(location = 7) in float _qt_order;\n"); + + // Find first brace '{' + while (t != Tokenizer::Token_EOF && t != Tokenizer::Token_OpenBrace) t = tok.next(); + int braceDepth = 1; + t = tok.next(); + + // Find matching brace and insert our code there... + while (t != Tokenizer::Token_EOF) { + switch (t) { + case Tokenizer::Token_CloseBrace: + braceDepth--; + if (braceDepth == 0) { + result += QByteArray::fromRawData(voidPos, tok.pos - 1 - voidPos); + result += QByteArrayLiteral(" gl_Position.z = _qt_order * gl_Position.w;\n"); + result += QByteArray(tok.pos - 1); + return result; + } + break; + case Tokenizer::Token_OpenBrace: + ++braceDepth; + break; + default: + break; + } + t = tok.next(); + } + return QByteArray(); +} + +} // namespace + +QT_END_NAMESPACE diff --git a/src/shadertools/qshaderbatchablerewriter_p.h b/src/shadertools/qshaderbatchablerewriter_p.h new file mode 100644 index 0000000..3ee8eca --- /dev/null +++ b/src/shadertools/qshaderbatchablerewriter_p.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSHADERBATCHABLEREWRITER_P_H +#define QSHADERBATCHABLEREWRITER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QByteArray> + +QT_BEGIN_NAMESPACE + +namespace QShaderBatchableRewriter { +QByteArray addZAdjustment(const QByteArray &input); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/shadertools/qspirvcompiler.cpp b/src/shadertools/qspirvcompiler.cpp new file mode 100644 index 0000000..22547e9 --- /dev/null +++ b/src/shadertools/qspirvcompiler.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qspirvcompiler_p.h" +#include "qshaderbatchablerewriter_p.h" +#include <QFile> +#include <QFileInfo> + +#include <glslang/Public/ShaderLang.h> +#include <SPIRV/GlslangToSpv.h> + +QT_BEGIN_NAMESPACE + +static const TBuiltInResource resourceLimits = +{ + /* .MaxLights = */ 32, + /* .MaxClipPlanes = */ 6, + /* .MaxTextureUnits = */ 32, + /* .MaxTextureCoords = */ 32, + /* .MaxVertexAttribs = */ 64, + /* .MaxVertexUniformComponents = */ 4096, + /* .MaxVaryingFloats = */ 64, + /* .MaxVertexTextureImageUnits = */ 32, + /* .MaxCombinedTextureImageUnits = */ 80, + /* .MaxTextureImageUnits = */ 32, + /* .MaxFragmentUniformComponents = */ 4096, + /* .MaxDrawBuffers = */ 32, + /* .MaxVertexUniformVectors = */ 128, + /* .MaxVaryingVectors = */ 8, + /* .MaxFragmentUniformVectors = */ 16, + /* .MaxVertexOutputVectors = */ 16, + /* .MaxFragmentInputVectors = */ 15, + /* .MinProgramTexelOffset = */ -8, + /* .MaxProgramTexelOffset = */ 7, + /* .MaxClipDistances = */ 8, + /* .MaxComputeWorkGroupCountX = */ 65535, + /* .MaxComputeWorkGroupCountY = */ 65535, + /* .MaxComputeWorkGroupCountZ = */ 65535, + /* .MaxComputeWorkGroupSizeX = */ 1024, + /* .MaxComputeWorkGroupSizeY = */ 1024, + /* .MaxComputeWorkGroupSizeZ = */ 64, + /* .MaxComputeUniformComponents = */ 1024, + /* .MaxComputeTextureImageUnits = */ 16, + /* .MaxComputeImageUniforms = */ 8, + /* .MaxComputeAtomicCounters = */ 8, + /* .MaxComputeAtomicCounterBuffers = */ 1, + /* .MaxVaryingComponents = */ 60, + /* .MaxVertexOutputComponents = */ 64, + /* .MaxGeometryInputComponents = */ 64, + /* .MaxGeometryOutputComponents = */ 128, + /* .MaxFragmentInputComponents = */ 128, + /* .MaxImageUnits = */ 8, + /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8, + /* .MaxCombinedShaderOutputResources = */ 8, + /* .MaxImageSamples = */ 0, + /* .MaxVertexImageUniforms = */ 0, + /* .MaxTessControlImageUniforms = */ 0, + /* .MaxTessEvaluationImageUniforms = */ 0, + /* .MaxGeometryImageUniforms = */ 0, + /* .MaxFragmentImageUniforms = */ 8, + /* .MaxCombinedImageUniforms = */ 8, + /* .MaxGeometryTextureImageUnits = */ 16, + /* .MaxGeometryOutputVertices = */ 256, + /* .MaxGeometryTotalOutputComponents = */ 1024, + /* .MaxGeometryUniformComponents = */ 1024, + /* .MaxGeometryVaryingComponents = */ 64, + /* .MaxTessControlInputComponents = */ 128, + /* .MaxTessControlOutputComponents = */ 128, + /* .MaxTessControlTextureImageUnits = */ 16, + /* .MaxTessControlUniformComponents = */ 1024, + /* .MaxTessControlTotalOutputComponents = */ 4096, + /* .MaxTessEvaluationInputComponents = */ 128, + /* .MaxTessEvaluationOutputComponents = */ 128, + /* .MaxTessEvaluationTextureImageUnits = */ 16, + /* .MaxTessEvaluationUniformComponents = */ 1024, + /* .MaxTessPatchComponents = */ 120, + /* .MaxPatchVertices = */ 32, + /* .MaxTessGenLevel = */ 64, + /* .MaxViewports = */ 16, + /* .MaxVertexAtomicCounters = */ 0, + /* .MaxTessControlAtomicCounters = */ 0, + /* .MaxTessEvaluationAtomicCounters = */ 0, + /* .MaxGeometryAtomicCounters = */ 0, + /* .MaxFragmentAtomicCounters = */ 8, + /* .MaxCombinedAtomicCounters = */ 8, + /* .MaxAtomicCounterBindings = */ 1, + /* .MaxVertexAtomicCounterBuffers = */ 0, + /* .MaxTessControlAtomicCounterBuffers = */ 0, + /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, + /* .MaxGeometryAtomicCounterBuffers = */ 0, + /* .MaxFragmentAtomicCounterBuffers = */ 1, + /* .MaxCombinedAtomicCounterBuffers = */ 1, + /* .MaxAtomicCounterBufferSize = */ 16384, + /* .MaxTransformFeedbackBuffers = */ 4, + /* .MaxTransformFeedbackInterleavedComponents = */ 64, + /* .MaxCullDistances = */ 8, + /* .MaxCombinedClipAndCullDistances = */ 8, + /* .MaxSamples = */ 4, + /* .maxMeshOutputVerticesNV = */ 256, + /* .maxMeshOutputPrimitivesNV = */ 512, + /* .maxMeshWorkGroupSizeX_NV = */ 32, + /* .maxMeshWorkGroupSizeY_NV = */ 1, + /* .maxMeshWorkGroupSizeZ_NV = */ 1, + /* .maxTaskWorkGroupSizeX_NV = */ 32, + /* .maxTaskWorkGroupSizeY_NV = */ 1, + /* .maxTaskWorkGroupSizeZ_NV = */ 1, + /* .maxMeshViewCountNV = */ 4, + + /* .limits = */ { + /* .nonInductiveForLoops = */ 1, + /* .whileLoops = */ 1, + /* .doWhileLoops = */ 1, + /* .generalUniformIndexing = */ 1, + /* .generalAttributeMatrixVectorIndexing = */ 1, + /* .generalVaryingIndexing = */ 1, + /* .generalSamplerIndexing = */ 1, + /* .generalVariableIndexing = */ 1, + /* .generalConstantMatrixVectorIndexing = */ 1, + } +}; + +struct QSpirvCompilerPrivate +{ + bool readFile(const QString &fn); + bool compile(); + + QString sourceFileName; + QByteArray source; + QByteArray batchableSource; + EShLanguage stage = EShLangVertex; + QSpirvCompiler::Flags flags = 0; + QByteArray spirv; + QString log; +}; + +bool QSpirvCompilerPrivate::readFile(const QString &fn) +{ + QFile f(fn); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("QSpirvCompiler: Failed to open %s", qPrintable(fn)); + return false; + } + source = f.readAll(); + batchableSource.clear(); + sourceFileName = fn; + f.close(); + return true; +} + +class Includer : public glslang::TShader::Includer +{ +public: + IncludeResult *includeLocal(const char *headerName, + const char *includerName, + size_t inclusionDepth) override + { + Q_UNUSED(inclusionDepth); + return readFile(headerName, includerName); + } + + IncludeResult *includeSystem(const char *headerName, + const char *includerName, + size_t inclusionDepth) override + { + Q_UNUSED(inclusionDepth); + return readFile(headerName, includerName); + } + + void releaseInclude(IncludeResult *result) override + { + if (result) { + delete static_cast<QByteArray *>(result->userData); + delete result; + } + } + +private: + IncludeResult *readFile(const char *headerName, const char *includerName); +}; + +glslang::TShader::Includer::IncludeResult *Includer::readFile(const char *headerName, const char *includerName) +{ + // Just treat the included name as relative to the includer: + // Take the path from the includer, append the included name, remove redundancies. + // This should work also for qrc (source filenames with qrc:/ or :/ prefix). + + QString includer = QString::fromUtf8(includerName); + if (includer.isEmpty()) + includer = QLatin1String("."); + QString included = QFileInfo(includer).canonicalPath() + QLatin1Char('/') + QString::fromUtf8(headerName); + included = QFileInfo(included).canonicalFilePath(); + if (included.isEmpty()) { + qWarning("QSpirvCompiler: Failed to find include file %s", headerName); + return nullptr; + } + QFile f(included); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("QSpirvCompiler: Failed to read include file %s", qPrintable(included)); + return nullptr; + } + + QByteArray *data = new QByteArray; + *data = f.readAll(); + return new IncludeResult(included.toStdString(), data->constData(), data->size(), data); +} + +class GlobalInit +{ +public: + GlobalInit() { glslang::InitializeProcess(); } + ~GlobalInit() { glslang::FinalizeProcess(); } +}; + +bool QSpirvCompilerPrivate::compile() +{ + log.clear(); + + const bool useBatchable = (stage == EShLangVertex && flags.testFlag(QSpirvCompiler::RewriteToMakeBatchableForSG)); + const QByteArray *actualSource = useBatchable ? &batchableSource : &source; + if (actualSource->isEmpty()) + return false; + + static GlobalInit globalInit; + + glslang::TShader shader(stage); + const QByteArray fn = sourceFileName.toUtf8(); + const char *fnStr = fn.constData(); + const char *srcStr = actualSource->constData(); + const int size = actualSource->size(); + shader.setStringsWithLengthsAndNames(&srcStr, &size, &fnStr, 1); + + shader.setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, 100); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0); + shader.setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetSpv_1_0); + + Includer includer; + if (!shader.parse(&resourceLimits, 100, false, EShMsgDefault, includer)) { + qWarning("QSpirvCompiler: Failed to parse shader"); + log = QString::fromUtf8(shader.getInfoLog()).trimmed(); + return false; + } + + glslang::TProgram program; + program.addShader(&shader); + if (!program.link(EShMsgDefault)) { + qWarning("QSpirvCompiler: Link failed"); + log = QString::fromUtf8(shader.getInfoLog()).trimmed(); + return false; + } + + std::vector<unsigned int> spv; + glslang::GlslangToSpv(*program.getIntermediate(stage), spv); + if (!spv.size()) { + qWarning("Failed to generate SPIR-V"); + return false; + } + + spirv.resize(int(spv.size() * 4)); + memcpy(spirv.data(), spv.data(), spirv.size()); + + return true; +} + +QSpirvCompiler::QSpirvCompiler() + : d(new QSpirvCompilerPrivate) +{ +} + +QSpirvCompiler::~QSpirvCompiler() +{ + delete d; +} + +void QSpirvCompiler::setSourceFileName(const QString &fileName) +{ + if (!d->readFile(fileName)) + return; + + const QString suffix = QFileInfo(fileName).suffix(); + if (suffix == QStringLiteral("vert")) { + d->stage = EShLangVertex; + } else if (suffix == QStringLiteral("frag")) { + d->stage = EShLangFragment; + } else if (suffix == QStringLiteral("tesc")) { + d->stage = EShLangTessControl; + } else if (suffix == QStringLiteral("tese")) { + d->stage = EShLangTessEvaluation; + } else if (suffix == QStringLiteral("geom")) { + d->stage = EShLangGeometry; + } else if (suffix == QStringLiteral("comp")) { + d->stage = EShLangCompute; + } else { + qWarning("QSpirvCompiler: Unknown shader stage, defaulting to vertex"); + d->stage = EShLangVertex; + } +} + +static inline EShLanguage mapShaderStage(QRhiShader::ShaderStage stage) +{ + switch (stage) { + case QRhiShader::VertexStage: + return EShLangVertex; + case QRhiShader::TessControlStage: + return EShLangTessControl; + case QRhiShader::TessEvaluationStage: + return EShLangTessEvaluation; + case QRhiShader::GeometryStage: + return EShLangGeometry; + case QRhiShader::FragmentStage: + return EShLangFragment; + case QRhiShader::ComputeStage: + return EShLangCompute; + default: + return EShLangVertex; + } +} + +void QSpirvCompiler::setSourceFileName(const QString &fileName, QRhiShader::ShaderStage stage) +{ + if (!d->readFile(fileName)) + return; + + d->stage = mapShaderStage(stage); +} + +void QSpirvCompiler::setSourceDevice(QIODevice *device, QRhiShader::ShaderStage stage, const QString &fileName) +{ + setSourceString(device->readAll(), stage, fileName); +} + +void QSpirvCompiler::setSourceString(const QByteArray &sourceString, QRhiShader::ShaderStage stage, const QString &fileName) +{ + d->sourceFileName = fileName; // for error messages, include handling, etc. + d->source = sourceString; + d->batchableSource.clear(); + d->stage = mapShaderStage(stage); +} + +void QSpirvCompiler::setFlags(Flags flags) +{ + d->flags = flags; +} + +QByteArray QSpirvCompiler::compileToSpirv() +{ + if (d->stage == EShLangVertex && d->flags.testFlag(RewriteToMakeBatchableForSG) && d->batchableSource.isEmpty()) + d->batchableSource = QShaderBatchableRewriter::addZAdjustment(d->source); + + return d->compile() ? d->spirv : QByteArray(); +} + +QString QSpirvCompiler::errorMessage() const +{ + return d->log; +} + +QT_END_NAMESPACE diff --git a/src/shadertools/qspirvcompiler_p.h b/src/shadertools/qspirvcompiler_p.h new file mode 100644 index 0000000..2af56b2 --- /dev/null +++ b/src/shadertools/qspirvcompiler_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPIRVCOMPILER_P_H +#define QSPIRVCOMPILER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtShaderTools/private/qtshadertoolsglobal_p.h> +#include <QtGui/qrhishader.h> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +struct QSpirvCompilerPrivate; +class QIODevice; + +class Q_SHADERTOOLS_PRIVATE_EXPORT QSpirvCompiler +{ +public: + QSpirvCompiler(); + ~QSpirvCompiler(); + + enum Flag { + RewriteToMakeBatchableForSG = 0x01 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + void setSourceFileName(const QString &fileName); + void setSourceFileName(const QString &fileName, QRhiShader::ShaderStage stage); + void setSourceDevice(QIODevice *device, QRhiShader::ShaderStage stage, const QString &fileName = QString()); + void setSourceString(const QByteArray &sourceString, QRhiShader::ShaderStage stage, const QString &fileName = QString()); + void setFlags(Flags flags); + + QByteArray compileToSpirv(); + QString errorMessage() const; + +private: + Q_DISABLE_COPY(QSpirvCompiler) + QSpirvCompilerPrivate *d = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpirvCompiler::Flags) + +QT_END_NAMESPACE + +#endif diff --git a/src/shadertools/qspirvshader.cpp b/src/shadertools/qspirvshader.cpp new file mode 100644 index 0000000..7e0057e --- /dev/null +++ b/src/shadertools/qspirvshader.cpp @@ -0,0 +1,468 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qspirvshader_p.h" +#include <QtGui/private/qrhishaderdescription_p.h> +#include <QFile> +#include <QDebug> + +#include <SPIRV/SPVRemapper.h> + +#include <spirv_glsl.hpp> +#include <spirv_hlsl.hpp> +#include <spirv_msl.hpp> + +QT_BEGIN_NAMESPACE + +struct QSpirvShaderPrivate +{ + ~QSpirvShaderPrivate(); + + void createGLSLCompiler(); + void processResources(); + + QRhiShaderDescription::InOutVariable inOutVar(const spirv_cross::Resource &r); + QRhiShaderDescription::BlockVariable blockVar(uint32_t typeId, + uint32_t memberIdx, + uint32_t memberTypeId); + + void remapErrorHandler(const std::string &s); + void remapLogHandler(const std::string &s); + + QByteArray ir; + QRhiShaderDescription shaderDescription; + + spirv_cross::CompilerGLSL *glslGen = nullptr; + spirv_cross::CompilerHLSL *hlslGen = nullptr; + spirv_cross::CompilerMSL *mslGen = nullptr; + + QString spirvCrossErrorMsg; + QString remapErrorMsg; +}; + +QSpirvShaderPrivate::~QSpirvShaderPrivate() +{ + delete mslGen; + delete hlslGen; + delete glslGen; +} + +void QSpirvShaderPrivate::createGLSLCompiler() +{ + delete glslGen; + glslGen = new spirv_cross::CompilerGLSL(reinterpret_cast<const uint32_t *>(ir.constData()), ir.size() / 4); +} + +static QRhiShaderDescription::VarType matVarType(const spirv_cross::SPIRType &t, QRhiShaderDescription::VarType compType) +{ + switch (t.columns) { + case 2: + return QRhiShaderDescription::VarType(compType + 4 + (t.vecsize == 3 ? 1 : t.vecsize == 4 ? 2 : 0)); + case 3: + return QRhiShaderDescription::VarType(compType + 7 + (t.vecsize == 2 ? 1 : t.vecsize == 4 ? 2 : 0)); + case 4: + return QRhiShaderDescription::VarType(compType + 10 + (t.vecsize == 2 ? 1 : t.vecsize == 3 ? 2 : 0)); + default: + return QRhiShaderDescription::Unknown; + } +} + +static QRhiShaderDescription::VarType vecVarType(const spirv_cross::SPIRType &t, QRhiShaderDescription::VarType compType) +{ + switch (t.vecsize) { + case 1: + return compType; + case 2: + return QRhiShaderDescription::VarType(compType + 1); + case 3: + return QRhiShaderDescription::VarType(compType + 2); + case 4: + return QRhiShaderDescription::VarType(compType + 3); + default: + return QRhiShaderDescription::Unknown; + } +} + +static QRhiShaderDescription::VarType imageVarType(const spirv_cross::SPIRType &t) +{ + switch (t.image.dim) { + case spv::Dim1D: + return t.image.arrayed ? QRhiShaderDescription::Sampler1DArray : QRhiShaderDescription::Sampler1D; + case spv::Dim2D: + return t.image.arrayed + ? (t.image.ms ? QRhiShaderDescription::Sampler2DMSArray : QRhiShaderDescription::Sampler2DArray) + : (t.image.ms ? QRhiShaderDescription::Sampler2DMS : QRhiShaderDescription::Sampler2D); + case spv::Dim3D: + return t.image.arrayed ? QRhiShaderDescription::Sampler3DArray : QRhiShaderDescription::Sampler3D; + case spv::DimCube: + return t.image.arrayed ? QRhiShaderDescription::SamplerCubeArray : QRhiShaderDescription::SamplerCube; + default: + return QRhiShaderDescription::Unknown; + } +} + +static QRhiShaderDescription::VarType varType(const spirv_cross::SPIRType &t) +{ + QRhiShaderDescription::VarType vt = QRhiShaderDescription::Unknown; + switch (t.basetype) { + case spirv_cross::SPIRType::Float: + vt = t.columns > 1 ? matVarType(t, QRhiShaderDescription::Float) : vecVarType(t, QRhiShaderDescription::Float); + break; + case spirv_cross::SPIRType::Double: + vt = t.columns > 1 ? matVarType(t, QRhiShaderDescription::Double) : vecVarType(t, QRhiShaderDescription::Double); + break; + case spirv_cross::SPIRType::UInt: + vt = vecVarType(t, QRhiShaderDescription::Uint); + break; + case spirv_cross::SPIRType::Int: + vt = vecVarType(t, QRhiShaderDescription::Int); + break; + case spirv_cross::SPIRType::Boolean: + vt = vecVarType(t, QRhiShaderDescription::Uint); + break; + case spirv_cross::SPIRType::SampledImage: + vt = imageVarType(t); + break; + case spirv_cross::SPIRType::Struct: + vt = QRhiShaderDescription::Struct; + break; + // ### separate image/sampler, atomic counter, ... + default: + break; + } + return vt; +} + +QRhiShaderDescription::InOutVariable QSpirvShaderPrivate::inOutVar(const spirv_cross::Resource &r) +{ + QRhiShaderDescription::InOutVariable v; + v.name = QString::fromStdString(r.name); + + const spirv_cross::SPIRType &t = glslGen->get_type(r.base_type_id); + v.type = varType(t); + + if (glslGen->has_decoration(r.id, spv::DecorationLocation)) + v.location = glslGen->get_decoration(r.id, spv::DecorationLocation); + + if (glslGen->has_decoration(r.id, spv::DecorationBinding)) + v.binding = glslGen->get_decoration(r.id, spv::DecorationBinding); + + if (glslGen->has_decoration(r.id, spv::DecorationDescriptorSet)) + v.descriptorSet = glslGen->get_decoration(r.id, spv::DecorationDescriptorSet); + + return v; +} + +QRhiShaderDescription::BlockVariable QSpirvShaderPrivate::blockVar(uint32_t typeId, + uint32_t memberIdx, + uint32_t memberTypeId) +{ + QRhiShaderDescription::BlockVariable v; + v.name = QString::fromStdString(glslGen->get_member_name(typeId, memberIdx)); + + const spirv_cross::SPIRType &memberType(glslGen->get_type(memberTypeId)); + v.type = varType(memberType); + + const spirv_cross::SPIRType &t = glslGen->get_type(typeId); + v.offset = glslGen->type_struct_member_offset(t, memberIdx); + v.size = int(glslGen->get_declared_struct_member_size(t, memberIdx)); + + for (uint32_t dimSize : memberType.array) + v.arrayDims.append(dimSize); + + if (glslGen->has_member_decoration(typeId, memberIdx, spv::DecorationArrayStride)) + v.arrayStride = glslGen->type_struct_member_array_stride(t, memberIdx); + + if (glslGen->has_member_decoration(typeId, memberIdx, spv::DecorationMatrixStride)) + v.matrixStride = glslGen->type_struct_member_matrix_stride(t, memberIdx); + + if (glslGen->has_member_decoration(typeId, memberIdx, spv::DecorationRowMajor)) + v.matrixIsRowMajor = true; + + if (v.type == QRhiShaderDescription::Struct) { + uint32_t memberMemberIdx = 0; + for (uint32_t memberMemberType : memberType.member_types) { + v.structMembers.append(blockVar(memberType.self, memberMemberIdx, memberMemberType)); + ++memberMemberIdx; + } + } + + return v; +} + +void QSpirvShaderPrivate::processResources() +{ + shaderDescription = QRhiShaderDescription(); + QRhiShaderDescriptionPrivate *dd = QRhiShaderDescriptionPrivate::get(&shaderDescription); + + spirv_cross::ShaderResources resources = glslGen->get_shader_resources(); + + /* ### + std::vector<Resource> uniform_buffers; + std::vector<Resource> storage_buffers; + std::vector<Resource> stage_inputs; + std::vector<Resource> stage_outputs; + std::vector<Resource> subpass_inputs; + std::vector<Resource> storage_images; + std::vector<Resource> sampled_images; + std::vector<Resource> atomic_counters; + std::vector<Resource> push_constant_buffers; + std::vector<Resource> separate_images; + std::vector<Resource> separate_samplers; + */ + + for (const spirv_cross::Resource &r : resources.stage_inputs) { + const QRhiShaderDescription::InOutVariable v = inOutVar(r); + if (v.type != QRhiShaderDescription::Unknown) + dd->inVars.append(v); + } + + for (const spirv_cross::Resource &r : resources.stage_outputs) { + const QRhiShaderDescription::InOutVariable v = inOutVar(r); + if (v.type != QRhiShaderDescription::Unknown) + dd->outVars.append(v); + } + + // uniform blocks map to either a uniform buffer or a plain struct + for (const spirv_cross::Resource &r : resources.uniform_buffers) { + const spirv_cross::SPIRType &t = glslGen->get_type(r.base_type_id); + QRhiShaderDescription::UniformBlock block; + block.blockName = QString::fromStdString(r.name); + block.structName = QString::fromStdString(glslGen->get_name(r.id)); + block.size = int(glslGen->get_declared_struct_size(t)); + if (glslGen->has_decoration(r.id, spv::DecorationBinding)) + block.binding = glslGen->get_decoration(r.id, spv::DecorationBinding); + if (glslGen->has_decoration(r.id, spv::DecorationDescriptorSet)) + block.descriptorSet = glslGen->get_decoration(r.id, spv::DecorationDescriptorSet); + uint32_t idx = 0; + for (uint32_t memberTypeId : t.member_types) { + const QRhiShaderDescription::BlockVariable v = blockVar(r.base_type_id, idx, memberTypeId); + ++idx; + if (v.type != QRhiShaderDescription::Unknown) + block.members.append(v); + } + dd->uniformBlocks.append(block); + } + + // push constant blocks map to a plain GLSL struct regardless of version + for (const spirv_cross::Resource &r : resources.push_constant_buffers) { + const spirv_cross::SPIRType &t = glslGen->get_type(r.base_type_id); + QRhiShaderDescription::PushConstantBlock block; + block.name = QString::fromStdString(glslGen->get_name(r.id)); + block.size = int(glslGen->get_declared_struct_size(t)); + uint32_t idx = 0; + for (uint32_t memberTypeId : t.member_types) { + const QRhiShaderDescription::BlockVariable v = blockVar(r.base_type_id, idx, memberTypeId); + ++idx; + if (v.type != QRhiShaderDescription::Unknown) + block.members.append(v); + } + dd->pushConstantBlocks.append(block); + } + + for (const spirv_cross::Resource &r : resources.sampled_images) { + const QRhiShaderDescription::InOutVariable v = inOutVar(r); + if (v.type != QRhiShaderDescription::Unknown) + dd->combinedImageSamplers.append(v); + } +} + +QSpirvShader::QSpirvShader() + : d(new QSpirvShaderPrivate) +{ +} + +QSpirvShader::~QSpirvShader() +{ + delete d; +} + +void QSpirvShader::setFileName(const QString &fileName) +{ + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("QSpirvShader: Failed to open %s", qPrintable(fileName)); + return; + } + setDevice(&f); +} + +void QSpirvShader::setDevice(QIODevice *device) +{ + d->ir = device->readAll(); + d->createGLSLCompiler(); + d->processResources(); +} + +void QSpirvShader::setSpirvBinary(const QByteArray &spirv) +{ + d->ir = spirv; + d->createGLSLCompiler(); + d->processResources(); +} + +QRhiShaderDescription QSpirvShader::shaderDescription() const +{ + return d->shaderDescription; +} + +void QSpirvShaderPrivate::remapErrorHandler(const std::string &s) +{ + if (!remapErrorMsg.isEmpty()) + remapErrorMsg.append(QLatin1Char('\n')); + remapErrorMsg.append(QString::fromStdString(s)); +} + +void QSpirvShaderPrivate::remapLogHandler(const std::string &) +{ +} + +QByteArray QSpirvShader::strippedSpirvBinary(StripFlags flags, QString *errorMessage) const +{ + if (d->ir.isEmpty()) + return QByteArray(); + + spv::spirvbin_t b; + + d->remapErrorMsg.clear(); + b.registerErrorHandler(std::bind(&QSpirvShaderPrivate::remapErrorHandler, d, std::placeholders::_1)); + b.registerLogHandler(std::bind(&QSpirvShaderPrivate::remapLogHandler, d, std::placeholders::_1)); + + const uint32_t opts = flags.testFlag(Remap) ? spv::spirvbin_t::DO_EVERYTHING : spv::spirvbin_t::STRIP; + + std::vector<uint32_t> v; + v.resize(d->ir.size() / 4); + memcpy(v.data(), d->ir.constData(), d->ir.size()); + + b.remap(v, opts); + + if (!d->remapErrorMsg.isEmpty()) { + if (errorMessage) + *errorMessage = d->remapErrorMsg; + return QByteArray(); + } + + return QByteArray(reinterpret_cast<const char *>(v.data()), int(v.size()) * 4); +} + +QByteArray QSpirvShader::translateToGLSL(int version, GlslFlags flags) const +{ + d->spirvCrossErrorMsg.clear(); + + try { + // create a new instance every time since option handling seem to be problematic + // (won't pick up new options on the second and subsequent compile()) + d->createGLSLCompiler(); + + spirv_cross::CompilerGLSL::Options options; + options.version = version; + options.es = flags.testFlag(GlslEs); + options.vertex.fixup_clipspace = flags.testFlag(FixClipSpace); + options.fragment.default_float_precision = flags.testFlag(FragDefaultMediump) + ? spirv_cross::CompilerGLSL::Options::Mediump + : spirv_cross::CompilerGLSL::Options::Highp; + d->glslGen->set_common_options(options); + + const std::string glsl = d->glslGen->compile(); + + QByteArray src = QByteArray::fromStdString(glsl); + + // Fix it up by adding #extension GL_ARB_separate_shader_objects : require + // as well in order to make Mesa and perhaps others happy. + const QByteArray searchStr = QByteArrayLiteral("#extension GL_ARB_shading_language_420pack : require\n#endif\n"); + int pos = src.indexOf(searchStr); + if (pos >= 0) { + src.insert(pos + searchStr.count(), QByteArrayLiteral("#ifdef GL_ARB_separate_shader_objects\n" + "#extension GL_ARB_separate_shader_objects : require\n" + "#endif\n")); + } + + return src; + } catch (const std::runtime_error &e) { + d->spirvCrossErrorMsg = QString::fromUtf8(e.what()); + return QByteArray(); + } +} + +QByteArray QSpirvShader::translateToHLSL(int version) const +{ + d->spirvCrossErrorMsg.clear(); + + try { + if (!d->hlslGen) + d->hlslGen = new spirv_cross::CompilerHLSL(reinterpret_cast<const uint32_t *>(d->ir.constData()), d->ir.size() / 4); + + spirv_cross::CompilerHLSL::Options options; + options.shader_model = version; + d->hlslGen->set_hlsl_options(options); + + const std::string hlsl = d->hlslGen->compile(); + + return QByteArray::fromStdString(hlsl); + } catch (const std::runtime_error &e) { + d->spirvCrossErrorMsg = QString::fromUtf8(e.what()); + return QByteArray(); + } +} + +QByteArray QSpirvShader::translateToMSL(int version) const +{ + d->spirvCrossErrorMsg.clear(); + + try { + if (!d->mslGen) + d->mslGen = new spirv_cross::CompilerMSL(reinterpret_cast<const uint32_t *>(d->ir.constData()), d->ir.size() / 4); + + spirv_cross::CompilerMSL::Options options; + options.msl_version = spirv_cross::CompilerMSL::Options::make_msl_version(version / 10, version % 10); + // leave platform set to macOS, it won't matter in practice (hopefully) + d->mslGen->set_msl_options(options); + + const std::string msl = d->mslGen->compile(); + + return QByteArray::fromStdString(msl); + } catch (const std::runtime_error &e) { + d->spirvCrossErrorMsg = QString::fromUtf8(e.what()); + return QByteArray(); + } +} + +QString QSpirvShader::translationErrorMessage() const +{ + return d->spirvCrossErrorMsg; +} + +QT_END_NAMESPACE diff --git a/src/shadertools/qspirvshader_p.h b/src/shadertools/qspirvshader_p.h new file mode 100644 index 0000000..3191173 --- /dev/null +++ b/src/shadertools/qspirvshader_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPIRVSHADER_P_H +#define QSPIRVSHADER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtShaderTools/private/qtshadertoolsglobal_p.h> +#include <QtGui/qrhishaderdescription.h> + +QT_BEGIN_NAMESPACE + +class QIODevice; +struct QSpirvShaderPrivate; + +class Q_SHADERTOOLS_PRIVATE_EXPORT QSpirvShader +{ +public: + enum GlslFlag { + GlslEs = 0x01, + FixClipSpace = 0x02, + FragDefaultMediump = 0x04 + }; + Q_DECLARE_FLAGS(GlslFlags, GlslFlag) + + enum StripFlag { + Remap = 0x01 + }; + Q_DECLARE_FLAGS(StripFlags, StripFlag) + + QSpirvShader(); + ~QSpirvShader(); + + void setFileName(const QString &fileName); + void setDevice(QIODevice *device); + void setSpirvBinary(const QByteArray &spirv); + + QRhiShaderDescription shaderDescription() const; + + QByteArray strippedSpirvBinary(StripFlags flags = StripFlags(), QString *errorMessage = nullptr) const; + + QByteArray translateToGLSL(int version = 120, GlslFlags flags = GlslFlags()) const; + QByteArray translateToHLSL(int version = 50) const; + QByteArray translateToMSL(int version = 12) const; + + QString translationErrorMessage() const; + +private: + Q_DISABLE_COPY(QSpirvShader) + QSpirvShaderPrivate *d = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpirvShader::GlslFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QSpirvShader::StripFlags) + +QT_END_NAMESPACE + +#endif diff --git a/src/shadertools/qtshadertoolsglobal.h b/src/shadertools/qtshadertoolsglobal.h new file mode 100644 index 0000000..fda42ff --- /dev/null +++ b/src/shadertools/qtshadertoolsglobal.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTSHADERTOOLSGLOBAL_H +#define QTSHADERTOOLSGLOBAL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +#ifndef Q_SHADERTOOLS_EXPORT +# if !defined(QT_STATIC) +# if defined(QT_BUILD_SHADERTOOLS_LIB) +# define Q_SHADERTOOLS_EXPORT Q_DECL_EXPORT +# else +# define Q_SHADERTOOLS_EXPORT Q_DECL_IMPORT +# endif +# else +# define Q_SHADERTOOLS_EXPORT +# endif +#endif + +#ifdef Q_CLANG_QDOC +#define Q_SHADERTOOLS_EXPORT +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/shadertools/qtshadertoolsglobal_p.h b/src/shadertools/qtshadertoolsglobal_p.h new file mode 100644 index 0000000..3516624 --- /dev/null +++ b/src/shadertools/qtshadertoolsglobal_p.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Shader Tools module +** +** $QT_BEGIN_LICENSE:GPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTSHADERTOOLSGLOBAL_P_H +#define QTSHADERTOOLSGLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtshadertoolsglobal.h" + +#define Q_SHADERTOOLS_PRIVATE_EXPORT Q_SHADERTOOLS_EXPORT + +#endif diff --git a/src/shadertools/shadertools.pro b/src/shadertools/shadertools.pro new file mode 100644 index 0000000..e9123b3 --- /dev/null +++ b/src/shadertools/shadertools.pro @@ -0,0 +1,41 @@ +TARGET = QtShaderTools + +QT += gui-private + +DEFINES += QT_BUILD_SHADERTOOLS_LIB + +HEADERS += \ + $$PWD/qtshadertoolsglobal.h \ + $$PWD/qshaderbaker.h \ + $$PWD/qspirvshader_p.h \ + $$PWD/qspirvcompiler_p.h \ + $$PWD/qshaderbatchablerewriter_p.h + +SOURCES += \ + $$PWD/qshaderbaker.cpp \ + $$PWD/qspirvshader.cpp \ + $$PWD/qspirvcompiler.cpp \ + $$PWD/qshaderbatchablerewriter.cpp + +INCLUDEPATH += $$PWD/../3rdparty/SPIRV-Cross $$PWD/../3rdparty/glslang + +# Exceptions must be enabled since that is the only sane way to get errors reported from SPIRV-Cross. +# They will not propagate outside of this module though so should be safe enough. +CONFIG += exceptions + +!exists($$[QT_HOST_DATA]/.qmake.cache) { + LIBLOC = $$shadowed($$dirname(_QMAKE_CONF_))/lib +} else { + LIBLOC = $$[QT_HOST_LIBS] +} + +STATICLIBS = qtspirv-cross qtglslang-glslang qtglslang-spirv qtglslang-osdependent qtglslang-oglcompiler # qtglslang-hlsl +for(libname, STATICLIBS) { + staticlib = $$LIBLOC/$${QMAKE_PREFIX_STATICLIB}$$qtLibraryTarget($$libname).$${QMAKE_EXTENSION_STATICLIB} + LIBS_PRIVATE += $$staticlib + PRE_TARGETDEPS += $$staticlib +} + +include($$PWD/doc/doc.pri) + +load(qt_module) |