1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qspirvcompiler_p.h"
5#include "qshaderrewriter_p.h"
6#include <QFile>
7#include <QFileInfo>
8
9QT_WARNING_PUSH
10QT_WARNING_DISABLE_GCC("-Wsuggest-override")
11#include <glslang/Public/ShaderLang.h>
12#include <glslang/Public/ResourceLimits.h>
13#include <SPIRV/GlslangToSpv.h>
14QT_WARNING_POP
15
16//#define TOKENIZER_DEBUG
17
18QT_BEGIN_NAMESPACE
19
20struct QSpirvCompilerPrivate
21{
22 bool readFile(const QString &fn);
23 bool compile();
24
25 QString sourceFileName;
26 QByteArray source;
27 QByteArray batchableSource;
28 EShLanguage stage = EShLangVertex;
29 QSpirvCompiler::Flags flags;
30 QByteArray preamble;
31 int batchAttrLoc = 7;
32 QByteArray spirv;
33 QString log;
34};
35
36bool QSpirvCompilerPrivate::readFile(const QString &fn)
37{
38 QFile f(fn);
39 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
40 qWarning(msg: "QSpirvCompiler: Failed to open %s", qPrintable(fn));
41 return false;
42 }
43 source = f.readAll();
44 batchableSource.clear();
45 sourceFileName = fn;
46 f.close();
47 return true;
48}
49
50using namespace QtShaderTools;
51
52class Includer : public glslang::TShader::Includer
53{
54public:
55 IncludeResult *includeLocal(const char *headerName,
56 const char *includerName,
57 size_t inclusionDepth) override
58 {
59 Q_UNUSED(inclusionDepth);
60 return readFile(headerName, includerName);
61 }
62
63 IncludeResult *includeSystem(const char *headerName,
64 const char *includerName,
65 size_t inclusionDepth) override
66 {
67 Q_UNUSED(inclusionDepth);
68 return readFile(headerName, includerName);
69 }
70
71 void releaseInclude(IncludeResult *result) override
72 {
73 if (result) {
74 delete static_cast<QByteArray *>(result->userData);
75 delete result;
76 }
77 }
78
79private:
80 IncludeResult *readFile(const char *headerName, const char *includerName);
81};
82
83glslang::TShader::Includer::IncludeResult *Includer::readFile(const char *headerName, const char *includerName)
84{
85 // Just treat the included name as relative to the includer:
86 // Take the path from the includer, append the included name, remove redundancies.
87 // This should work also for qrc (source filenames with qrc:/ or :/ prefix).
88
89 QString includer = QString::fromUtf8(utf8: includerName);
90 if (includer.isEmpty())
91 includer = QLatin1String(".");
92 QString included = QFileInfo(includer).canonicalPath() + QLatin1Char('/') + QString::fromUtf8(utf8: headerName);
93 included = QFileInfo(included).canonicalFilePath();
94 if (included.isEmpty()) {
95 qWarning(msg: "QSpirvCompiler: Failed to find include file %s", headerName);
96 return nullptr;
97 }
98 QFile f(included);
99 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
100 qWarning(msg: "QSpirvCompiler: Failed to read include file %s", qPrintable(included));
101 return nullptr;
102 }
103
104 QByteArray *data = new QByteArray;
105 *data = f.readAll();
106 return new IncludeResult(included.toStdString(), data->constData(), data->size(), data);
107}
108
109class GlobalInit
110{
111public:
112 GlobalInit() { glslang::InitializeProcess(); }
113 ~GlobalInit() { glslang::FinalizeProcess(); }
114};
115
116bool QSpirvCompilerPrivate::compile()
117{
118 log.clear();
119
120 const bool useBatchable = (stage == EShLangVertex && flags.testFlag(flag: QSpirvCompiler::RewriteToMakeBatchableForSG));
121 const QByteArray *actualSource = useBatchable ? &batchableSource : &source;
122 if (actualSource->isEmpty())
123 return false;
124
125 static GlobalInit globalInit;
126
127 glslang::TShader shader(stage);
128 const QByteArray fn = sourceFileName.toUtf8();
129 const char *fnStr = fn.constData();
130 const char *srcStr = actualSource->constData();
131 const int size = actualSource->size();
132 shader.setStringsWithLengthsAndNames(s: &srcStr, l: &size, names: &fnStr, n: 1);
133 if (!preamble.isEmpty()) {
134 // Line numbers in errors and #version are not affected by having a
135 // preamble, which is just what we need.
136 shader.setPreamble(preamble.constData());
137 }
138
139 shader.setEnvInput(lang: glslang::EShSourceGlsl, envStage: stage, client: glslang::EShClientVulkan, version: 100);
140 shader.setEnvClient(client: glslang::EShClientVulkan, version: glslang::EShTargetVulkan_1_0);
141 shader.setEnvTarget(lang: glslang::EshTargetSpv, version: glslang::EShTargetSpv_1_0);
142
143 int messages = EShMsgDefault;
144 if (flags.testFlag(flag: QSpirvCompiler::FullDebugInfo)) // embed source
145 messages |= EShMsgDebugInfo;
146
147 Includer includer;
148 if (!shader.parse(builtInResources: GetDefaultResources(), defaultVersion: 100, forwardCompatible: false, messages: EShMessages(messages), includer)) {
149 qWarning(msg: "QSpirvCompiler: Failed to parse shader");
150 log = QString::fromUtf8(utf8: shader.getInfoLog()).trimmed();
151 return false;
152 }
153
154 glslang::TProgram program;
155 program.addShader(shader: &shader);
156 if (!program.link(EShMsgDefault)) {
157 qWarning(msg: "QSpirvCompiler: Link failed");
158 log = QString::fromUtf8(utf8: shader.getInfoLog()).trimmed();
159 return false;
160 }
161
162 // The only interesting option here is the debug info, optimizations and
163 // such do not happen at this level.
164 glslang::SpvOptions options;
165 options.generateDebugInfo = flags.testFlag(flag: QSpirvCompiler::FullDebugInfo);
166
167 std::vector<unsigned int> spv;
168 glslang::GlslangToSpv(intermediate: *program.getIntermediate(stage), spirv&: spv, options: &options);
169 if (!spv.size()) {
170 qWarning(msg: "Failed to generate SPIR-V");
171 return false;
172 }
173
174 spirv.resize(size: int(spv.size() * 4));
175 memcpy(dest: spirv.data(), src: spv.data(), n: spirv.size());
176
177 return true;
178}
179
180QSpirvCompiler::QSpirvCompiler()
181 : d(new QSpirvCompilerPrivate)
182{
183}
184
185QSpirvCompiler::~QSpirvCompiler()
186{
187 delete d;
188}
189
190void QSpirvCompiler::setSourceFileName(const QString &fileName)
191{
192 if (!d->readFile(fn: fileName))
193 return;
194
195 const QString suffix = QFileInfo(fileName).suffix();
196 if (suffix == QStringLiteral("vert")) {
197 d->stage = EShLangVertex;
198 } else if (suffix == QStringLiteral("frag")) {
199 d->stage = EShLangFragment;
200 } else if (suffix == QStringLiteral("tesc")) {
201 d->stage = EShLangTessControl;
202 } else if (suffix == QStringLiteral("tese")) {
203 d->stage = EShLangTessEvaluation;
204 } else if (suffix == QStringLiteral("geom")) {
205 d->stage = EShLangGeometry;
206 } else if (suffix == QStringLiteral("comp")) {
207 d->stage = EShLangCompute;
208 } else {
209 qWarning(msg: "QSpirvCompiler: Unknown shader stage, defaulting to vertex");
210 d->stage = EShLangVertex;
211 }
212}
213
214static inline EShLanguage mapShaderStage(QShader::Stage stage)
215{
216 switch (stage) {
217 case QShader::VertexStage:
218 return EShLangVertex;
219 case QShader::TessellationControlStage:
220 return EShLangTessControl;
221 case QShader::TessellationEvaluationStage:
222 return EShLangTessEvaluation;
223 case QShader::GeometryStage:
224 return EShLangGeometry;
225 case QShader::FragmentStage:
226 return EShLangFragment;
227 case QShader::ComputeStage:
228 return EShLangCompute;
229 default:
230 return EShLangVertex;
231 }
232}
233
234void QSpirvCompiler::setSourceFileName(const QString &fileName, QShader::Stage stage)
235{
236 if (!d->readFile(fn: fileName))
237 return;
238
239 d->stage = mapShaderStage(stage);
240}
241
242void QSpirvCompiler::setSourceDevice(QIODevice *device, QShader::Stage stage, const QString &fileName)
243{
244 setSourceString(sourceString: device->readAll(), stage, fileName);
245}
246
247void QSpirvCompiler::setSourceString(const QByteArray &sourceString, QShader::Stage stage, const QString &fileName)
248{
249 d->sourceFileName = fileName; // for error messages, include handling, etc.
250 d->source = sourceString;
251 d->batchableSource.clear();
252 d->stage = mapShaderStage(stage);
253}
254
255void QSpirvCompiler::setFlags(Flags flags)
256{
257 d->flags = flags;
258}
259
260void QSpirvCompiler::setPreamble(const QByteArray &preamble)
261{
262 d->preamble = preamble;
263}
264
265void QSpirvCompiler::setSGBatchingVertexInputLocation(int location)
266{
267 d->batchAttrLoc = location;
268}
269
270QByteArray QSpirvCompiler::compileToSpirv()
271{
272#ifdef TOKENIZER_DEBUG
273 QShaderRewriter::debugTokenizer(d->source);
274#endif
275
276 if (d->stage == EShLangVertex && d->flags.testFlag(flag: RewriteToMakeBatchableForSG) && d->batchableSource.isEmpty())
277 d->batchableSource = QShaderRewriter::addZAdjustment(input: d->source, vertexInputLocation: d->batchAttrLoc);
278
279 return d->compile() ? d->spirv : QByteArray();
280}
281
282QString QSpirvCompiler::errorMessage() const
283{
284 return d->log;
285}
286
287QT_END_NAMESPACE
288

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtshadertools/src/shadertools/qspirvcompiler.cpp