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 <SPIRV/GlslangToSpv.h>
13QT_WARNING_POP
14
15//#define TOKENIZER_DEBUG
16
17QT_BEGIN_NAMESPACE
18
19const TBuiltInResource resourceLimits = {
20 /* .MaxLights = */ .maxLights: 32,
21 /* .MaxClipPlanes = */ .maxClipPlanes: 6,
22 /* .MaxTextureUnits = */ .maxTextureUnits: 32,
23 /* .MaxTextureCoords = */ .maxTextureCoords: 32,
24 /* .MaxVertexAttribs = */ .maxVertexAttribs: 64,
25 /* .MaxVertexUniformComponents = */ .maxVertexUniformComponents: 4096,
26 /* .MaxVaryingFloats = */ .maxVaryingFloats: 64,
27 /* .MaxVertexTextureImageUnits = */ .maxVertexTextureImageUnits: 32,
28 /* .MaxCombinedTextureImageUnits = */ .maxCombinedTextureImageUnits: 80,
29 /* .MaxTextureImageUnits = */ .maxTextureImageUnits: 32,
30 /* .MaxFragmentUniformComponents = */ .maxFragmentUniformComponents: 4096,
31 /* .MaxDrawBuffers = */ .maxDrawBuffers: 32,
32 /* .MaxVertexUniformVectors = */ .maxVertexUniformVectors: 128,
33 /* .MaxVaryingVectors = */ .maxVaryingVectors: 8,
34 /* .MaxFragmentUniformVectors = */ .maxFragmentUniformVectors: 16,
35 /* .MaxVertexOutputVectors = */ .maxVertexOutputVectors: 16,
36 /* .MaxFragmentInputVectors = */ .maxFragmentInputVectors: 15,
37 /* .MinProgramTexelOffset = */ .minProgramTexelOffset: -8,
38 /* .MaxProgramTexelOffset = */ .maxProgramTexelOffset: 7,
39 /* .MaxClipDistances = */ .maxClipDistances: 8,
40 /* .MaxComputeWorkGroupCountX = */ .maxComputeWorkGroupCountX: 65535,
41 /* .MaxComputeWorkGroupCountY = */ .maxComputeWorkGroupCountY: 65535,
42 /* .MaxComputeWorkGroupCountZ = */ .maxComputeWorkGroupCountZ: 65535,
43 /* .MaxComputeWorkGroupSizeX = */ .maxComputeWorkGroupSizeX: 1024,
44 /* .MaxComputeWorkGroupSizeY = */ .maxComputeWorkGroupSizeY: 1024,
45 /* .MaxComputeWorkGroupSizeZ = */ .maxComputeWorkGroupSizeZ: 64,
46 /* .MaxComputeUniformComponents = */ .maxComputeUniformComponents: 1024,
47 /* .MaxComputeTextureImageUnits = */ .maxComputeTextureImageUnits: 16,
48 /* .MaxComputeImageUniforms = */ .maxComputeImageUniforms: 8,
49 /* .MaxComputeAtomicCounters = */ .maxComputeAtomicCounters: 8,
50 /* .MaxComputeAtomicCounterBuffers = */ .maxComputeAtomicCounterBuffers: 1,
51 /* .MaxVaryingComponents = */ .maxVaryingComponents: 60,
52 /* .MaxVertexOutputComponents = */ .maxVertexOutputComponents: 64,
53 /* .MaxGeometryInputComponents = */ .maxGeometryInputComponents: 64,
54 /* .MaxGeometryOutputComponents = */ .maxGeometryOutputComponents: 128,
55 /* .MaxFragmentInputComponents = */ .maxFragmentInputComponents: 128,
56 /* .MaxImageUnits = */ .maxImageUnits: 8,
57 /* .MaxCombinedImageUnitsAndFragmentOutputs = */ .maxCombinedImageUnitsAndFragmentOutputs: 8,
58 /* .MaxCombinedShaderOutputResources = */ .maxCombinedShaderOutputResources: 8,
59 /* .MaxImageSamples = */ .maxImageSamples: 0,
60 /* .MaxVertexImageUniforms = */ .maxVertexImageUniforms: 0,
61 /* .MaxTessControlImageUniforms = */ .maxTessControlImageUniforms: 0,
62 /* .MaxTessEvaluationImageUniforms = */ .maxTessEvaluationImageUniforms: 0,
63 /* .MaxGeometryImageUniforms = */ .maxGeometryImageUniforms: 0,
64 /* .MaxFragmentImageUniforms = */ .maxFragmentImageUniforms: 8,
65 /* .MaxCombinedImageUniforms = */ .maxCombinedImageUniforms: 8,
66 /* .MaxGeometryTextureImageUnits = */ .maxGeometryTextureImageUnits: 16,
67 /* .MaxGeometryOutputVertices = */ .maxGeometryOutputVertices: 256,
68 /* .MaxGeometryTotalOutputComponents = */ .maxGeometryTotalOutputComponents: 1024,
69 /* .MaxGeometryUniformComponents = */ .maxGeometryUniformComponents: 1024,
70 /* .MaxGeometryVaryingComponents = */ .maxGeometryVaryingComponents: 64,
71 /* .MaxTessControlInputComponents = */ .maxTessControlInputComponents: 128,
72 /* .MaxTessControlOutputComponents = */ .maxTessControlOutputComponents: 128,
73 /* .MaxTessControlTextureImageUnits = */ .maxTessControlTextureImageUnits: 16,
74 /* .MaxTessControlUniformComponents = */ .maxTessControlUniformComponents: 1024,
75 /* .MaxTessControlTotalOutputComponents = */ .maxTessControlTotalOutputComponents: 4096,
76 /* .MaxTessEvaluationInputComponents = */ .maxTessEvaluationInputComponents: 128,
77 /* .MaxTessEvaluationOutputComponents = */ .maxTessEvaluationOutputComponents: 128,
78 /* .MaxTessEvaluationTextureImageUnits = */ .maxTessEvaluationTextureImageUnits: 16,
79 /* .MaxTessEvaluationUniformComponents = */ .maxTessEvaluationUniformComponents: 1024,
80 /* .MaxTessPatchComponents = */ .maxTessPatchComponents: 120,
81 /* .MaxPatchVertices = */ .maxPatchVertices: 32,
82 /* .MaxTessGenLevel = */ .maxTessGenLevel: 64,
83 /* .MaxViewports = */ .maxViewports: 16,
84 /* .MaxVertexAtomicCounters = */ .maxVertexAtomicCounters: 0,
85 /* .MaxTessControlAtomicCounters = */ .maxTessControlAtomicCounters: 0,
86 /* .MaxTessEvaluationAtomicCounters = */ .maxTessEvaluationAtomicCounters: 0,
87 /* .MaxGeometryAtomicCounters = */ .maxGeometryAtomicCounters: 0,
88 /* .MaxFragmentAtomicCounters = */ .maxFragmentAtomicCounters: 8,
89 /* .MaxCombinedAtomicCounters = */ .maxCombinedAtomicCounters: 8,
90 /* .MaxAtomicCounterBindings = */ .maxAtomicCounterBindings: 1,
91 /* .MaxVertexAtomicCounterBuffers = */ .maxVertexAtomicCounterBuffers: 0,
92 /* .MaxTessControlAtomicCounterBuffers = */ .maxTessControlAtomicCounterBuffers: 0,
93 /* .MaxTessEvaluationAtomicCounterBuffers = */ .maxTessEvaluationAtomicCounterBuffers: 0,
94 /* .MaxGeometryAtomicCounterBuffers = */ .maxGeometryAtomicCounterBuffers: 0,
95 /* .MaxFragmentAtomicCounterBuffers = */ .maxFragmentAtomicCounterBuffers: 1,
96 /* .MaxCombinedAtomicCounterBuffers = */ .maxCombinedAtomicCounterBuffers: 1,
97 /* .MaxAtomicCounterBufferSize = */ .maxAtomicCounterBufferSize: 16384,
98 /* .MaxTransformFeedbackBuffers = */ .maxTransformFeedbackBuffers: 4,
99 /* .MaxTransformFeedbackInterleavedComponents = */ .maxTransformFeedbackInterleavedComponents: 64,
100 /* .MaxCullDistances = */ .maxCullDistances: 8,
101 /* .MaxCombinedClipAndCullDistances = */ .maxCombinedClipAndCullDistances: 8,
102 /* .MaxSamples = */ .maxSamples: 4,
103 /* .maxMeshOutputVerticesNV = */ 256,
104 /* .maxMeshOutputPrimitivesNV = */ 512,
105 /* .maxMeshWorkGroupSizeX_NV = */ 32,
106 /* .maxMeshWorkGroupSizeY_NV = */ 1,
107 /* .maxMeshWorkGroupSizeZ_NV = */ 1,
108 /* .maxTaskWorkGroupSizeX_NV = */ 32,
109 /* .maxTaskWorkGroupSizeY_NV = */ 1,
110 /* .maxTaskWorkGroupSizeZ_NV = */ 1,
111 /* .maxMeshViewCountNV = */ 4,
112 /* .maxDualSourceDrawBuffersEXT = */ 1,
113
114 /* .limits = */ {
115 /* .nonInductiveForLoops = */ 1,
116 /* .whileLoops = */ 1,
117 /* .doWhileLoops = */ 1,
118 /* .generalUniformIndexing = */ 1,
119 /* .generalAttributeMatrixVectorIndexing = */ 1,
120 /* .generalVaryingIndexing = */ 1,
121 /* .generalSamplerIndexing = */ 1,
122 /* .generalVariableIndexing = */ 1,
123 /* .generalConstantMatrixVectorIndexing = */ 1,
124 }
125};
126
127struct QSpirvCompilerPrivate
128{
129 bool readFile(const QString &fn);
130 bool compile();
131
132 QString sourceFileName;
133 QByteArray source;
134 QByteArray batchableSource;
135 EShLanguage stage = EShLangVertex;
136 QSpirvCompiler::Flags flags;
137 QByteArray preamble;
138 int batchAttrLoc = 7;
139 QByteArray spirv;
140 QString log;
141};
142
143bool QSpirvCompilerPrivate::readFile(const QString &fn)
144{
145 QFile f(fn);
146 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
147 qWarning(msg: "QSpirvCompiler: Failed to open %s", qPrintable(fn));
148 return false;
149 }
150 source = f.readAll();
151 batchableSource.clear();
152 sourceFileName = fn;
153 f.close();
154 return true;
155}
156
157using namespace QtShaderTools;
158
159class Includer : public glslang::TShader::Includer
160{
161public:
162 IncludeResult *includeLocal(const char *headerName,
163 const char *includerName,
164 size_t inclusionDepth) override
165 {
166 Q_UNUSED(inclusionDepth);
167 return readFile(headerName, includerName);
168 }
169
170 IncludeResult *includeSystem(const char *headerName,
171 const char *includerName,
172 size_t inclusionDepth) override
173 {
174 Q_UNUSED(inclusionDepth);
175 return readFile(headerName, includerName);
176 }
177
178 void releaseInclude(IncludeResult *result) override
179 {
180 if (result) {
181 delete static_cast<QByteArray *>(result->userData);
182 delete result;
183 }
184 }
185
186private:
187 IncludeResult *readFile(const char *headerName, const char *includerName);
188};
189
190glslang::TShader::Includer::IncludeResult *Includer::readFile(const char *headerName, const char *includerName)
191{
192 // Just treat the included name as relative to the includer:
193 // Take the path from the includer, append the included name, remove redundancies.
194 // This should work also for qrc (source filenames with qrc:/ or :/ prefix).
195
196 QString includer = QString::fromUtf8(utf8: includerName);
197 if (includer.isEmpty())
198 includer = QLatin1String(".");
199 QString included = QFileInfo(includer).canonicalPath() + QLatin1Char('/') + QString::fromUtf8(utf8: headerName);
200 included = QFileInfo(included).canonicalFilePath();
201 if (included.isEmpty()) {
202 qWarning(msg: "QSpirvCompiler: Failed to find include file %s", headerName);
203 return nullptr;
204 }
205 QFile f(included);
206 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
207 qWarning(msg: "QSpirvCompiler: Failed to read include file %s", qPrintable(included));
208 return nullptr;
209 }
210
211 QByteArray *data = new QByteArray;
212 *data = f.readAll();
213 return new IncludeResult(included.toStdString(), data->constData(), data->size(), data);
214}
215
216class GlobalInit
217{
218public:
219 GlobalInit() { glslang::InitializeProcess(); }
220 ~GlobalInit() { glslang::FinalizeProcess(); }
221};
222
223bool QSpirvCompilerPrivate::compile()
224{
225 log.clear();
226
227 const bool useBatchable = (stage == EShLangVertex && flags.testFlag(flag: QSpirvCompiler::RewriteToMakeBatchableForSG));
228 const QByteArray *actualSource = useBatchable ? &batchableSource : &source;
229 if (actualSource->isEmpty())
230 return false;
231
232 static GlobalInit globalInit;
233
234 glslang::TShader shader(stage);
235 const QByteArray fn = sourceFileName.toUtf8();
236 const char *fnStr = fn.constData();
237 const char *srcStr = actualSource->constData();
238 const int size = actualSource->size();
239 shader.setStringsWithLengthsAndNames(s: &srcStr, l: &size, names: &fnStr, n: 1);
240 if (!preamble.isEmpty()) {
241 // Line numbers in errors and #version are not affected by having a
242 // preamble, which is just what we need.
243 shader.setPreamble(preamble.constData());
244 }
245
246 shader.setEnvInput(lang: glslang::EShSourceGlsl, envStage: stage, client: glslang::EShClientVulkan, version: 100);
247 shader.setEnvClient(client: glslang::EShClientVulkan, version: glslang::EShTargetVulkan_1_0);
248 shader.setEnvTarget(lang: glslang::EshTargetSpv, version: glslang::EShTargetSpv_1_0);
249
250 int messages = EShMsgDefault;
251 if (flags.testFlag(flag: QSpirvCompiler::FullDebugInfo)) // embed source
252 messages |= EShMsgDebugInfo;
253
254 Includer includer;
255 if (!shader.parse(builtInResources: &resourceLimits, defaultVersion: 100, forwardCompatible: false, messages: EShMessages(messages), includer)) {
256 qWarning(msg: "QSpirvCompiler: Failed to parse shader");
257 log = QString::fromUtf8(utf8: shader.getInfoLog()).trimmed();
258 return false;
259 }
260
261 glslang::TProgram program;
262 program.addShader(shader: &shader);
263 if (!program.link(EShMsgDefault)) {
264 qWarning(msg: "QSpirvCompiler: Link failed");
265 log = QString::fromUtf8(utf8: shader.getInfoLog()).trimmed();
266 return false;
267 }
268
269 // The only interesting option here is the debug info, optimizations and
270 // such do not happen at this level.
271 glslang::SpvOptions options;
272 options.generateDebugInfo = flags.testFlag(flag: QSpirvCompiler::FullDebugInfo);
273
274 std::vector<unsigned int> spv;
275 glslang::GlslangToSpv(intermediate: *program.getIntermediate(stage), spirv&: spv, options: &options);
276 if (!spv.size()) {
277 qWarning(msg: "Failed to generate SPIR-V");
278 return false;
279 }
280
281 spirv.resize(size: int(spv.size() * 4));
282 memcpy(dest: spirv.data(), src: spv.data(), n: spirv.size());
283
284 return true;
285}
286
287QSpirvCompiler::QSpirvCompiler()
288 : d(new QSpirvCompilerPrivate)
289{
290}
291
292QSpirvCompiler::~QSpirvCompiler()
293{
294 delete d;
295}
296
297void QSpirvCompiler::setSourceFileName(const QString &fileName)
298{
299 if (!d->readFile(fn: fileName))
300 return;
301
302 const QString suffix = QFileInfo(fileName).suffix();
303 if (suffix == QStringLiteral("vert")) {
304 d->stage = EShLangVertex;
305 } else if (suffix == QStringLiteral("frag")) {
306 d->stage = EShLangFragment;
307 } else if (suffix == QStringLiteral("tesc")) {
308 d->stage = EShLangTessControl;
309 } else if (suffix == QStringLiteral("tese")) {
310 d->stage = EShLangTessEvaluation;
311 } else if (suffix == QStringLiteral("geom")) {
312 d->stage = EShLangGeometry;
313 } else if (suffix == QStringLiteral("comp")) {
314 d->stage = EShLangCompute;
315 } else {
316 qWarning(msg: "QSpirvCompiler: Unknown shader stage, defaulting to vertex");
317 d->stage = EShLangVertex;
318 }
319}
320
321static inline EShLanguage mapShaderStage(QShader::Stage stage)
322{
323 switch (stage) {
324 case QShader::VertexStage:
325 return EShLangVertex;
326 case QShader::TessellationControlStage:
327 return EShLangTessControl;
328 case QShader::TessellationEvaluationStage:
329 return EShLangTessEvaluation;
330 case QShader::GeometryStage:
331 return EShLangGeometry;
332 case QShader::FragmentStage:
333 return EShLangFragment;
334 case QShader::ComputeStage:
335 return EShLangCompute;
336 default:
337 return EShLangVertex;
338 }
339}
340
341void QSpirvCompiler::setSourceFileName(const QString &fileName, QShader::Stage stage)
342{
343 if (!d->readFile(fn: fileName))
344 return;
345
346 d->stage = mapShaderStage(stage);
347}
348
349void QSpirvCompiler::setSourceDevice(QIODevice *device, QShader::Stage stage, const QString &fileName)
350{
351 setSourceString(sourceString: device->readAll(), stage, fileName);
352}
353
354void QSpirvCompiler::setSourceString(const QByteArray &sourceString, QShader::Stage stage, const QString &fileName)
355{
356 d->sourceFileName = fileName; // for error messages, include handling, etc.
357 d->source = sourceString;
358 d->batchableSource.clear();
359 d->stage = mapShaderStage(stage);
360}
361
362void QSpirvCompiler::setFlags(Flags flags)
363{
364 d->flags = flags;
365}
366
367void QSpirvCompiler::setPreamble(const QByteArray &preamble)
368{
369 d->preamble = preamble;
370}
371
372void QSpirvCompiler::setSGBatchingVertexInputLocation(int location)
373{
374 d->batchAttrLoc = location;
375}
376
377QByteArray QSpirvCompiler::compileToSpirv()
378{
379#ifdef TOKENIZER_DEBUG
380 QShaderRewriter::debugTokenizer(d->source);
381#endif
382
383 if (d->stage == EShLangVertex && d->flags.testFlag(flag: RewriteToMakeBatchableForSG) && d->batchableSource.isEmpty())
384 d->batchableSource = QShaderRewriter::addZAdjustment(input: d->source, vertexInputLocation: d->batchAttrLoc);
385
386 return d->compile() ? d->spirv : QByteArray();
387}
388
389QString QSpirvCompiler::errorMessage() const
390{
391 return d->log;
392}
393
394QT_END_NAMESPACE
395

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