summaryrefslogtreecommitdiffstats
path: root/src/shadertools/qspirvshader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/shadertools/qspirvshader.cpp')
-rw-r--r--src/shadertools/qspirvshader.cpp468
1 files changed, 468 insertions, 0 deletions
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