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 "qspirvshader_p.h" |
5 | #include "qspirvshaderremap_p.h" |
6 | #include <private/qshaderdescription_p.h> |
7 | #include <private/qshader_p.h> |
8 | |
9 | #include <spirv_cross_c.h> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | struct QSpirvShaderPrivate |
14 | { |
15 | ~QSpirvShaderPrivate(); |
16 | |
17 | void createCompiler(spvc_backend backend); |
18 | void reflect(); |
19 | |
20 | QShaderDescription::InOutVariable inOutVar(const spvc_reflected_resource &r); |
21 | QShaderDescription::BlockVariable blockVar(spvc_type_id typeId, uint32_t memberIdx); |
22 | |
23 | QShader::Stage stage; |
24 | QByteArray ir; |
25 | QShaderDescription shaderDescription; |
26 | |
27 | spvc_context ctx = nullptr; |
28 | spvc_compiler glslGen = nullptr; |
29 | spvc_compiler hlslGen = nullptr; |
30 | spvc_compiler mslGen = nullptr; |
31 | |
32 | QString spirvCrossErrorMsg; |
33 | }; |
34 | |
35 | QSpirvShaderPrivate::~QSpirvShaderPrivate() |
36 | { |
37 | spvc_context_destroy(context: ctx); |
38 | } |
39 | |
40 | void QSpirvShaderPrivate::createCompiler(spvc_backend backend) |
41 | { |
42 | if (!ctx) { |
43 | if (spvc_context_create(context: &ctx) != SPVC_SUCCESS) { |
44 | qWarning(msg: "Failed to create SPIRV-Cross context" ); |
45 | return; |
46 | } |
47 | } |
48 | |
49 | const SpvId *spirv = reinterpret_cast<const SpvId *>(ir.constData()); |
50 | size_t wordCount = size_t(ir.size()) / sizeof(SpvId); |
51 | spvc_parsed_ir parsedIr; |
52 | if (spvc_context_parse_spirv(context: ctx, spirv, word_count: wordCount, parsed_ir: &parsedIr) != SPVC_SUCCESS) { |
53 | qWarning(msg: "Failed to parse SPIR-V: %s" , spvc_context_get_last_error_string(context: ctx)); |
54 | return; |
55 | } |
56 | |
57 | spvc_compiler *outCompiler = nullptr; |
58 | switch (backend) { |
59 | case SPVC_BACKEND_GLSL: |
60 | outCompiler = &glslGen; |
61 | break; |
62 | case SPVC_BACKEND_HLSL: |
63 | outCompiler = &hlslGen; |
64 | break; |
65 | case SPVC_BACKEND_MSL: |
66 | outCompiler = &mslGen; |
67 | break; |
68 | default: |
69 | return; |
70 | } |
71 | |
72 | if (spvc_context_create_compiler(context: ctx, backend, parsed_ir: parsedIr, |
73 | mode: SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, compiler: outCompiler) != SPVC_SUCCESS) |
74 | { |
75 | qWarning(msg: "Failed to create SPIRV-Cross compiler: %s" , spvc_context_get_last_error_string(context: ctx)); |
76 | return; |
77 | } |
78 | } |
79 | |
80 | static QShaderDescription::VariableType matVarType(const spvc_type &t, QShaderDescription::VariableType compType) |
81 | { |
82 | const unsigned vecsize = spvc_type_get_vector_size(type: t); |
83 | switch (spvc_type_get_columns(type: t)) { |
84 | case 2: |
85 | return QShaderDescription::VariableType(compType + 4 + (vecsize == 3 ? 1 : vecsize == 4 ? 2 : 0)); |
86 | case 3: |
87 | return QShaderDescription::VariableType(compType + 7 + (vecsize == 2 ? 1 : vecsize == 4 ? 2 : 0)); |
88 | case 4: |
89 | return QShaderDescription::VariableType(compType + 10 + (vecsize == 2 ? 1 : vecsize == 3 ? 2 : 0)); |
90 | default: |
91 | return QShaderDescription::Unknown; |
92 | } |
93 | } |
94 | |
95 | static QShaderDescription::VariableType vecVarType(const spvc_type &t, QShaderDescription::VariableType compType) |
96 | { |
97 | switch (spvc_type_get_vector_size(type: t)) { |
98 | case 1: |
99 | return compType; |
100 | case 2: |
101 | return QShaderDescription::VariableType(compType + 1); |
102 | case 3: |
103 | return QShaderDescription::VariableType(compType + 2); |
104 | case 4: |
105 | return QShaderDescription::VariableType(compType + 3); |
106 | default: |
107 | return QShaderDescription::Unknown; |
108 | } |
109 | } |
110 | |
111 | static QShaderDescription::VariableType sampledImageVarType(const spvc_type &t) |
112 | { |
113 | switch (spvc_type_get_image_dimension(type: t)) { |
114 | case SpvDim1D: |
115 | return spvc_type_get_image_arrayed(type: t) ? QShaderDescription::Sampler1DArray |
116 | : QShaderDescription::Sampler1D; |
117 | case SpvDim2D: |
118 | return spvc_type_get_image_arrayed(type: t) |
119 | ? (spvc_type_get_image_multisampled(type: t) ? QShaderDescription::Sampler2DMSArray |
120 | : QShaderDescription::Sampler2DArray) |
121 | : (spvc_type_get_image_multisampled(type: t) ? QShaderDescription::Sampler2DMS |
122 | : QShaderDescription::Sampler2D); |
123 | case SpvDim3D: |
124 | return spvc_type_get_image_arrayed(type: t) ? QShaderDescription::Sampler3DArray |
125 | : QShaderDescription::Sampler3D; |
126 | case SpvDimCube: |
127 | return spvc_type_get_image_arrayed(type: t) ? QShaderDescription::SamplerCubeArray |
128 | : QShaderDescription::SamplerCube; |
129 | case SpvDimRect: |
130 | return QShaderDescription::SamplerRect; |
131 | case SpvDimBuffer: |
132 | return QShaderDescription::SamplerBuffer; |
133 | default: |
134 | return QShaderDescription::Unknown; |
135 | } |
136 | } |
137 | |
138 | static QShaderDescription::VariableType imageVarType(const spvc_type &t) |
139 | { |
140 | switch (spvc_type_get_image_dimension(type: t)) { |
141 | case SpvDim1D: |
142 | return spvc_type_get_image_arrayed(type: t) ? QShaderDescription::Image1DArray |
143 | : QShaderDescription::Image1D; |
144 | case SpvDim2D: |
145 | return spvc_type_get_image_arrayed(type: t) |
146 | ? (spvc_type_get_image_multisampled(type: t) ? QShaderDescription::Image2DMSArray |
147 | : QShaderDescription::Image2DArray) |
148 | : (spvc_type_get_image_multisampled(type: t) ? QShaderDescription::Image2DMS |
149 | : QShaderDescription::Image2D); |
150 | case SpvDim3D: |
151 | return spvc_type_get_image_arrayed(type: t) ? QShaderDescription::Image3DArray |
152 | : QShaderDescription::Image3D; |
153 | case SpvDimCube: |
154 | return spvc_type_get_image_arrayed(type: t) ? QShaderDescription::ImageCubeArray |
155 | : QShaderDescription::ImageCube; |
156 | case SpvDimRect: |
157 | return QShaderDescription::ImageRect; |
158 | case SpvDimBuffer: |
159 | return QShaderDescription::ImageBuffer; |
160 | default: |
161 | return QShaderDescription::Unknown; |
162 | } |
163 | } |
164 | |
165 | static QShaderDescription::VariableType varType(const spvc_type &t) |
166 | { |
167 | QShaderDescription::VariableType vt = QShaderDescription::Unknown; |
168 | const spvc_basetype basetype = spvc_type_get_basetype(type: t); |
169 | switch (basetype) { |
170 | case SPVC_BASETYPE_FP32: |
171 | vt = spvc_type_get_columns(type: t) > 1 ? matVarType(t, compType: QShaderDescription::Float) |
172 | : vecVarType(t, compType: QShaderDescription::Float); |
173 | break; |
174 | case SPVC_BASETYPE_FP64: |
175 | vt = spvc_type_get_columns(type: t) > 1 ? matVarType(t, compType: QShaderDescription::Double) |
176 | : vecVarType(t, compType: QShaderDescription::Double); |
177 | break; |
178 | case SPVC_BASETYPE_UINT32: |
179 | vt = vecVarType(t, compType: QShaderDescription::Uint); |
180 | break; |
181 | case SPVC_BASETYPE_INT32: |
182 | vt = vecVarType(t, compType: QShaderDescription::Int); |
183 | break; |
184 | case SPVC_BASETYPE_BOOLEAN: |
185 | vt = vecVarType(t, compType: QShaderDescription::Uint); |
186 | break; |
187 | case SPVC_BASETYPE_SAMPLED_IMAGE: |
188 | vt = sampledImageVarType(t); |
189 | break; |
190 | case SPVC_BASETYPE_IMAGE: |
191 | vt = imageVarType(t); |
192 | break; |
193 | case SPVC_BASETYPE_SAMPLER: |
194 | vt = QShaderDescription::Sampler; |
195 | break; |
196 | case SPVC_BASETYPE_STRUCT: |
197 | vt = QShaderDescription::Struct; |
198 | break; |
199 | default: |
200 | // can encounter types we do not (yet) handle, return Unknown for those |
201 | qWarning(msg: "Unsupported base type %d" , basetype); |
202 | break; |
203 | } |
204 | return vt; |
205 | } |
206 | |
207 | QShaderDescription::InOutVariable QSpirvShaderPrivate::inOutVar(const spvc_reflected_resource &r) |
208 | { |
209 | QShaderDescription::InOutVariable v; |
210 | v.name = r.name; |
211 | |
212 | spvc_type baseTypeHandle = spvc_compiler_get_type_handle(compiler: glslGen, id: r.base_type_id); |
213 | v.type = varType(t: baseTypeHandle); |
214 | |
215 | spvc_type typeHandle = spvc_compiler_get_type_handle(compiler: glslGen, id: r.type_id); |
216 | for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(type: typeHandle); i < dimCount; ++i) |
217 | v.arrayDims.append(t: int(spvc_type_get_array_dimension(type: typeHandle, dimension: i))); |
218 | |
219 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationLocation)) |
220 | v.location = spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationLocation); |
221 | |
222 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationBinding)) |
223 | v.binding = spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationBinding); |
224 | |
225 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationDescriptorSet)) |
226 | v.descriptorSet = spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationDescriptorSet); |
227 | |
228 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationPatch)) |
229 | v.perPatch = spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationPatch); |
230 | |
231 | if (spvc_type_get_basetype(type: baseTypeHandle) == SPVC_BASETYPE_IMAGE) { |
232 | v.imageFormat = QShaderDescription::ImageFormat(spvc_type_get_image_storage_format(type: baseTypeHandle)); |
233 | |
234 | v.imageFlags.setFlag(flag: QShaderDescription::ImageFlag::WriteOnlyImage, |
235 | on: spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationNonReadable)); |
236 | |
237 | v.imageFlags.setFlag(flag: QShaderDescription::ImageFlag::ReadOnlyImage, |
238 | on: spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationNonWritable)); |
239 | } |
240 | |
241 | if (v.type == QShaderDescription::Struct) { |
242 | const unsigned count = spvc_type_get_num_member_types(type: baseTypeHandle); |
243 | const spvc_type_id id = spvc_type_get_base_type_id(type: baseTypeHandle); |
244 | |
245 | for (unsigned idx = 0; idx < count; ++idx) { |
246 | v.structMembers.append(t: blockVar(typeId: id, memberIdx: idx)); |
247 | v.perPatch |= bool(spvc_compiler_has_member_decoration(compiler: glslGen, id, member_index: idx, decoration: SpvDecorationPatch)); |
248 | } |
249 | } |
250 | |
251 | return v; |
252 | } |
253 | |
254 | QShaderDescription::BlockVariable QSpirvShaderPrivate::blockVar(spvc_type_id typeId, uint32_t memberIdx) |
255 | { |
256 | QShaderDescription::BlockVariable v; |
257 | v.name = spvc_compiler_get_member_name(compiler: glslGen, id: typeId, member_index: memberIdx); |
258 | |
259 | spvc_type t = spvc_compiler_get_type_handle(compiler: glslGen, id: typeId); |
260 | spvc_type_id memberTypeId = spvc_type_get_member_type(type: t, index: memberIdx); |
261 | spvc_type memberType = spvc_compiler_get_type_handle(compiler: glslGen, id: memberTypeId); |
262 | v.type = varType(t: memberType); |
263 | v.offset = -1; |
264 | |
265 | unsigned offset = 0; |
266 | if (spvc_compiler_type_struct_member_offset(compiler: glslGen, type: t, index: memberIdx, offset: &offset) == SPVC_SUCCESS) |
267 | v.offset = int(offset); |
268 | |
269 | size_t size = 0; |
270 | if (spvc_compiler_get_declared_struct_member_size(compiler: glslGen, type: t, index: memberIdx, size: &size) == SPVC_SUCCESS) |
271 | v.size = int(size); |
272 | |
273 | for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(type: memberType); i < dimCount; ++i) |
274 | v.arrayDims.append(t: int(spvc_type_get_array_dimension(type: memberType, dimension: i))); |
275 | |
276 | if (spvc_compiler_has_member_decoration(compiler: glslGen, id: typeId, member_index: memberIdx, decoration: SpvDecorationArrayStride)) { |
277 | unsigned stride = 0; |
278 | if (spvc_compiler_type_struct_member_array_stride(compiler: glslGen, type: t, index: memberIdx, stride: &stride) == SPVC_SUCCESS) |
279 | v.arrayStride = int(stride); |
280 | } |
281 | |
282 | if (spvc_compiler_has_member_decoration(compiler: glslGen, id: typeId, member_index: memberIdx, decoration: SpvDecorationMatrixStride)) { |
283 | unsigned stride = 0; |
284 | if (spvc_compiler_type_struct_member_matrix_stride(compiler: glslGen, type: t, index: memberIdx, stride: &stride) == SPVC_SUCCESS) |
285 | v.matrixStride = int(stride); |
286 | } |
287 | |
288 | if (spvc_compiler_has_member_decoration(compiler: glslGen, id: typeId, member_index: memberIdx, decoration: SpvDecorationRowMajor)) |
289 | v.matrixIsRowMajor = true; |
290 | |
291 | if (v.type == QShaderDescription::Struct) { |
292 | unsigned count = spvc_type_get_num_member_types(type: memberType); |
293 | for (unsigned idx = 0; idx < count; ++idx) |
294 | v.structMembers.append(t: blockVar(typeId: spvc_type_get_base_type_id(type: memberType), memberIdx: idx)); |
295 | } |
296 | |
297 | return v; |
298 | } |
299 | |
300 | void QSpirvShaderPrivate::reflect() |
301 | { |
302 | if (!glslGen) |
303 | return; |
304 | |
305 | shaderDescription = QShaderDescription(); |
306 | QShaderDescriptionPrivate *dd = QShaderDescriptionPrivate::get(desc: &shaderDescription); |
307 | |
308 | dd->localSize[0] = spvc_compiler_get_execution_mode_argument_by_index(compiler: glslGen, mode: SpvExecutionModeLocalSize, index: 0); |
309 | dd->localSize[1] = spvc_compiler_get_execution_mode_argument_by_index(compiler: glslGen, mode: SpvExecutionModeLocalSize, index: 1); |
310 | dd->localSize[2] = spvc_compiler_get_execution_mode_argument_by_index(compiler: glslGen, mode: SpvExecutionModeLocalSize, index: 2); |
311 | |
312 | dd->tessOutVertCount = spvc_compiler_get_execution_mode_argument(compiler: glslGen, mode: SpvExecutionModeOutputVertices); |
313 | dd->tessMode = QShaderDescription::UnknownTessellationMode; |
314 | dd->tessWind = QShaderDescription::UnknownTessellationWindingOrder; |
315 | dd->tessPart = QShaderDescription::UnknownTessellationPartitioning; |
316 | |
317 | const SpvExecutionMode *execModes = nullptr; |
318 | size_t execModeCount = 0; |
319 | if (spvc_compiler_get_execution_modes(compiler: glslGen, modes: &execModes, num_modes: &execModeCount) != SPVC_SUCCESS) { |
320 | qWarning(msg: "Failed to get shader execution modes: %s" , spvc_context_get_last_error_string(context: ctx)); |
321 | return; |
322 | } |
323 | for (size_t i = 0; i < execModeCount; ++i) { |
324 | switch (execModes[i]) { |
325 | case SpvExecutionModeTriangles: |
326 | dd->tessMode = QShaderDescription::TrianglesTessellationMode; |
327 | break; |
328 | case SpvExecutionModeQuads: |
329 | dd->tessMode = QShaderDescription::QuadTessellationMode; |
330 | break; |
331 | case SpvExecutionModeIsolines: |
332 | qWarning(msg: "Isoline execution mode used for tessellation," |
333 | " this is not portable and may not work as expected." ); |
334 | dd->tessMode = QShaderDescription::IsolineTessellationMode; |
335 | break; |
336 | case SpvExecutionModeSpacingEqual: |
337 | dd->tessPart = QShaderDescription::EqualTessellationPartitioning; |
338 | break; |
339 | case SpvExecutionModeSpacingFractionalEven: |
340 | dd->tessPart = QShaderDescription::FractionalEvenTessellationPartitioning; |
341 | break; |
342 | case SpvExecutionModeSpacingFractionalOdd: |
343 | dd->tessPart = QShaderDescription::FractionalOddTessellationPartitioning; |
344 | break; |
345 | case SpvExecutionModeVertexOrderCw: |
346 | dd->tessWind = QShaderDescription::CwTessellationWindingOrder; |
347 | break; |
348 | case SpvExecutionModeVertexOrderCcw : |
349 | dd->tessWind = QShaderDescription::CcwTessellationWindingOrder; |
350 | break; |
351 | default: |
352 | break; |
353 | } |
354 | } |
355 | |
356 | // For builtin inputs/outputs (think of things like gl_Position (or |
357 | // gl_out[].gl_Position), gl_InvocationID, gl_TessLevelOuter, etc.) we only |
358 | // want the ones that are active (really read or written). |
359 | spvc_compiler_update_active_builtins(compiler: glslGen); |
360 | |
361 | spvc_resources resources; |
362 | if (spvc_compiler_create_shader_resources(compiler: glslGen, resources: &resources) != SPVC_SUCCESS) { |
363 | qWarning(msg: "Failed to get shader resources: %s" , spvc_context_get_last_error_string(context: ctx)); |
364 | return; |
365 | } |
366 | |
367 | const spvc_reflected_resource *resourceList = nullptr; |
368 | const spvc_reflected_builtin_resource *builtinResourceList = nullptr; |
369 | size_t resourceListCount = 0; |
370 | |
371 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_STAGE_INPUT, |
372 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
373 | { |
374 | for (size_t i = 0; i < resourceListCount; ++i) { |
375 | const QShaderDescription::InOutVariable v = inOutVar(r: resourceList[i]); |
376 | if (v.type != QShaderDescription::Unknown) |
377 | dd->inVars.append(t: v); |
378 | } |
379 | } |
380 | |
381 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_STAGE_OUTPUT, |
382 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
383 | { |
384 | for (size_t i = 0; i < resourceListCount; ++i) { |
385 | const QShaderDescription::InOutVariable v = inOutVar(r: resourceList[i]); |
386 | if (v.type != QShaderDescription::Unknown) |
387 | dd->outVars.append(t: v); |
388 | } |
389 | } |
390 | |
391 | if (spvc_resources_get_builtin_resource_list_for_type(resources, type: SPVC_BUILTIN_RESOURCE_TYPE_STAGE_INPUT, |
392 | resource_list: &builtinResourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
393 | { |
394 | for (size_t i = 0; i < resourceListCount; ++i) { |
395 | if (spvc_compiler_has_active_builtin(compiler: glslGen, builtin: builtinResourceList[i].builtin, storage: SpvStorageClassInput)) { |
396 | QShaderDescription::BuiltinVariable v; |
397 | v.type = QShaderDescription::BuiltinType(builtinResourceList[i].builtin); |
398 | |
399 | spvc_type type = spvc_compiler_get_type_handle( |
400 | compiler: glslGen, id: builtinResourceList[i].value_type_id); |
401 | v.varType = varType(t: type); |
402 | |
403 | for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(type); |
404 | i < dimCount; ++i) |
405 | v.arrayDims.append(t: int(spvc_type_get_array_dimension(type, dimension: i))); |
406 | |
407 | dd->inBuiltins.append(t: v); |
408 | } |
409 | } |
410 | std::sort(first: dd->inBuiltins.begin(), last: dd->inBuiltins.end(), |
411 | comp: [](const QShaderDescription::BuiltinVariable &a, const QShaderDescription::BuiltinVariable &b) |
412 | { |
413 | return a.type < b.type; |
414 | }); |
415 | } |
416 | |
417 | if (spvc_resources_get_builtin_resource_list_for_type(resources, type: SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, |
418 | resource_list: &builtinResourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
419 | { |
420 | for (size_t i = 0; i < resourceListCount; ++i) { |
421 | if (spvc_compiler_has_active_builtin(compiler: glslGen, builtin: builtinResourceList[i].builtin, storage: SpvStorageClassOutput)) { |
422 | QShaderDescription::BuiltinVariable v; |
423 | v.type = QShaderDescription::BuiltinType(builtinResourceList[i].builtin); |
424 | |
425 | spvc_type type = spvc_compiler_get_type_handle( |
426 | compiler: glslGen, id: builtinResourceList[i].value_type_id); |
427 | v.varType = varType(t: type); |
428 | |
429 | for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(type); |
430 | i < dimCount; ++i) |
431 | v.arrayDims.append(t: int(spvc_type_get_array_dimension(type, dimension: i))); |
432 | |
433 | dd->outBuiltins.append(t: v); |
434 | } |
435 | } |
436 | std::sort(first: dd->outBuiltins.begin(), last: dd->outBuiltins.end(), |
437 | comp: [](const QShaderDescription::BuiltinVariable &a, const QShaderDescription::BuiltinVariable &b) |
438 | { |
439 | return a.type < b.type; |
440 | }); |
441 | } |
442 | |
443 | // uniform blocks map to either a uniform buffer or a plain struct |
444 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, |
445 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
446 | { |
447 | for (size_t i = 0; i < resourceListCount; ++i) { |
448 | const spvc_reflected_resource &r(resourceList[i]); |
449 | spvc_type t = spvc_compiler_get_type_handle(compiler: glslGen, id: r.base_type_id); |
450 | QShaderDescription::UniformBlock block; |
451 | block.blockName = r.name; |
452 | |
453 | // the simple case: |
454 | // layout(...) uniform blk { T v; } inst; |
455 | // gives us blockName "blk" and structName "inst" because |
456 | // in GLSL without uniform blocks this ends up being |
457 | // struct blk { T v; }; uniform blk inst; |
458 | // or, where real UBs are used, for instance Metal: |
459 | // struct blk { T v; }; constant blk& inst [[buffer(N)]] |
460 | block.structName = spvc_compiler_get_name(compiler: glslGen, id: r.id); |
461 | |
462 | // the annoying case: |
463 | // layout(...) uniform blk { T v; }; |
464 | // here structName is empty and the generated code (when no uniform |
465 | // blocks) uses _ID as a fallback name: |
466 | // struct blk { T v; }; uniform blk _ID; |
467 | // or, with real uniform buffers, f.ex. Metal: |
468 | // struct blk { T v; }; constant blk& _ID [[buffer(N)]] |
469 | // Let's make sure the fallback name is filled in correctly. |
470 | if (block.structName.isEmpty()) { |
471 | // The catch (matters only when uniform blocks are not used in |
472 | // the output) is that ID is per-shader and so may differ |
473 | // between shaders, meaning that having the same uniform block |
474 | // in a vertex and fragment shader will lead to each stage |
475 | // having its own set of uniforms corresponding to the ub |
476 | // members (ofc, inactive uniforms may get optimized out by the |
477 | // compiler, so the duplication then is only there for members |
478 | // used in both stages). Not much we can do about that here, |
479 | // though. The GL backend of QRhi can deal with this. |
480 | block.structName = QByteArrayLiteral("_" ) + QByteArray::number(r.id); |
481 | } |
482 | |
483 | size_t size = 0; |
484 | spvc_compiler_get_declared_struct_size(compiler: glslGen, struct_type: t, size: &size); |
485 | block.size = int(size); |
486 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationBinding)) |
487 | block.binding = int(spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationBinding)); |
488 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationDescriptorSet)) |
489 | block.descriptorSet = int(spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationDescriptorSet)); |
490 | |
491 | unsigned count = spvc_type_get_num_member_types(type: t); |
492 | for (unsigned idx = 0; idx < count; ++idx) { |
493 | const QShaderDescription::BlockVariable v = blockVar(typeId: r.base_type_id, memberIdx: idx); |
494 | if (v.type != QShaderDescription::Unknown) |
495 | block.members.append(t: v); |
496 | } |
497 | |
498 | dd->uniformBlocks.append(t: block); |
499 | } |
500 | } |
501 | |
502 | // push constant blocks map to a plain GLSL struct regardless of version |
503 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_PUSH_CONSTANT, |
504 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
505 | { |
506 | for (size_t i = 0; i < resourceListCount; ++i) { |
507 | const spvc_reflected_resource &r(resourceList[i]); |
508 | spvc_type t = spvc_compiler_get_type_handle(compiler: glslGen, id: r.base_type_id); |
509 | QShaderDescription::PushConstantBlock block; |
510 | block.name = spvc_compiler_get_name(compiler: glslGen, id: r.id); |
511 | size_t size = 0; |
512 | spvc_compiler_get_declared_struct_size(compiler: glslGen, struct_type: t, size: &size); |
513 | block.size = int(size); |
514 | unsigned count = spvc_type_get_num_member_types(type: t); |
515 | for (unsigned idx = 0; idx < count; ++idx) { |
516 | const QShaderDescription::BlockVariable v = blockVar(typeId: r.base_type_id, memberIdx: idx); |
517 | if (v.type != QShaderDescription::Unknown) |
518 | block.members.append(t: v); |
519 | } |
520 | dd->pushConstantBlocks.append(t: block); |
521 | } |
522 | } |
523 | |
524 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_STORAGE_BUFFER, |
525 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
526 | { |
527 | for (size_t i = 0; i < resourceListCount; ++i) { |
528 | const spvc_reflected_resource &r(resourceList[i]); |
529 | spvc_type t = spvc_compiler_get_type_handle(compiler: glslGen, id: r.base_type_id); |
530 | QShaderDescription::StorageBlock block; |
531 | block.blockName = r.name; |
532 | block.instanceName = spvc_compiler_get_name(compiler: glslGen, id: r.id); |
533 | size_t size = 0; |
534 | spvc_compiler_get_declared_struct_size(compiler: glslGen, struct_type: t, size: &size); |
535 | block.knownSize = int(size); |
536 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationBinding)) |
537 | block.binding = int(spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationBinding)); |
538 | if (spvc_compiler_has_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationDescriptorSet)) |
539 | block.descriptorSet = int(spvc_compiler_get_decoration(compiler: glslGen, id: r.id, decoration: SpvDecorationDescriptorSet)); |
540 | size_t offset0 = 0; |
541 | spvc_compiler_get_declared_struct_size_runtime_array(compiler: glslGen, struct_type: t, array_size: 0, size: &offset0); |
542 | size_t offset1 = 0; |
543 | spvc_compiler_get_declared_struct_size_runtime_array(compiler: glslGen, struct_type: t, array_size: 1, size: &offset1); |
544 | block.runtimeArrayStride = int(offset1 - offset0); |
545 | const SpvDecoration *decorations; |
546 | spvc_compiler_get_buffer_block_decorations(compiler: glslGen, id: r.id, decorations: &decorations, num_decorations: &size); |
547 | for (uint i = 0; i < size; ++i) { |
548 | switch (decorations[i]) { |
549 | case SpvDecorationCoherent: |
550 | block.qualifierFlags.setFlag(flag: QShaderDescription::QualifierCoherent); |
551 | break; |
552 | case SpvDecorationVolatile: |
553 | block.qualifierFlags.setFlag(flag: QShaderDescription::QualifierVolatile); |
554 | break; |
555 | case SpvDecorationRestrict: |
556 | block.qualifierFlags.setFlag(flag: QShaderDescription::QualifierRestrict); |
557 | break; |
558 | case SpvDecorationNonWritable: |
559 | block.qualifierFlags.setFlag(flag: QShaderDescription::QualifierReadOnly); |
560 | break; |
561 | case SpvDecorationNonReadable: |
562 | block.qualifierFlags.setFlag(flag: QShaderDescription::QualifierWriteOnly); |
563 | break; |
564 | default: |
565 | break; |
566 | } |
567 | } |
568 | unsigned count = spvc_type_get_num_member_types(type: t); |
569 | for (unsigned idx = 0; idx < count; ++idx) { |
570 | const QShaderDescription::BlockVariable v = blockVar(typeId: r.base_type_id, memberIdx: idx); |
571 | if (v.type != QShaderDescription::Unknown) |
572 | block.members.append(t: v); |
573 | } |
574 | dd->storageBlocks.append(t: block); |
575 | } |
576 | } |
577 | |
578 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, |
579 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
580 | { |
581 | for (size_t i = 0; i < resourceListCount; ++i) { |
582 | const spvc_reflected_resource &r(resourceList[i]); |
583 | const QShaderDescription::InOutVariable v = inOutVar(r); |
584 | if (v.type != QShaderDescription::Unknown) |
585 | dd->combinedImageSamplers.append(t: v); |
586 | } |
587 | } |
588 | |
589 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SEPARATE_IMAGE, |
590 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
591 | { |
592 | for (size_t i = 0; i < resourceListCount; ++i) { |
593 | const spvc_reflected_resource &r(resourceList[i]); |
594 | const QShaderDescription::InOutVariable v = inOutVar(r); |
595 | if (v.type != QShaderDescription::Unknown) |
596 | dd->separateImages.append(t: v); |
597 | } |
598 | } |
599 | |
600 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS, |
601 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
602 | { |
603 | for (size_t i = 0; i < resourceListCount; ++i) { |
604 | const spvc_reflected_resource &r(resourceList[i]); |
605 | const QShaderDescription::InOutVariable v = inOutVar(r); |
606 | if (v.type != QShaderDescription::Unknown) |
607 | dd->separateSamplers.append(t: v); |
608 | } |
609 | } |
610 | |
611 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_STORAGE_IMAGE, |
612 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
613 | { |
614 | for (size_t i = 0; i < resourceListCount; ++i) { |
615 | const spvc_reflected_resource &r(resourceList[i]); |
616 | const QShaderDescription::InOutVariable v = inOutVar(r); |
617 | if (v.type != QShaderDescription::Unknown) |
618 | dd->storageImages.append(t: v); |
619 | } |
620 | } |
621 | } |
622 | |
623 | QSpirvShader::QSpirvShader() |
624 | : d(new QSpirvShaderPrivate) |
625 | { |
626 | } |
627 | |
628 | QSpirvShader::~QSpirvShader() |
629 | { |
630 | delete d; |
631 | } |
632 | |
633 | void QSpirvShader::setSpirvBinary(const QByteArray &spirv, QShader::Stage stage) |
634 | { |
635 | d->stage = stage; |
636 | d->ir = spirv; |
637 | d->createCompiler(backend: SPVC_BACKEND_GLSL); |
638 | d->reflect(); |
639 | } |
640 | |
641 | QShaderDescription QSpirvShader::shaderDescription() const |
642 | { |
643 | return d->shaderDescription; |
644 | } |
645 | |
646 | QByteArray QSpirvShader::spirvBinary() const |
647 | { |
648 | return d->ir; |
649 | } |
650 | |
651 | QByteArray QSpirvShader::remappedSpirvBinary(RemapFlags flags, QString *errorMessage) const |
652 | { |
653 | QSpirvShaderRemapper remapper; |
654 | QByteArray result = remapper.remap(ir: d->ir, flags); |
655 | if (errorMessage) |
656 | *errorMessage = remapper.errorMessage(); |
657 | return result; |
658 | } |
659 | |
660 | QByteArray QSpirvShader::translateToGLSL(int version, |
661 | GlslFlags flags, |
662 | QVector<SeparateToCombinedImageSamplerMapping> *separateToCombinedImageSamplerMappings) const |
663 | { |
664 | d->spirvCrossErrorMsg.clear(); |
665 | |
666 | d->createCompiler(backend: SPVC_BACKEND_GLSL); |
667 | if (!d->glslGen) |
668 | return QByteArray(); |
669 | |
670 | spvc_compiler_options options = nullptr; |
671 | if (spvc_compiler_create_compiler_options(compiler: d->glslGen, options: &options) != SPVC_SUCCESS) |
672 | return QByteArray(); |
673 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_GLSL_VERSION, |
674 | value: version); |
675 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_GLSL_ES, |
676 | value: flags.testFlag(flag: GlslFlag::GlslEs)); |
677 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_FIXUP_DEPTH_CONVENTION, |
678 | value: flags.testFlag(flag: GlslFlag::FixClipSpace)); |
679 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_GLSL_ES_DEFAULT_FLOAT_PRECISION_HIGHP, |
680 | value: !flags.testFlag(flag: GlslFlag::FragDefaultMediump)); |
681 | // The gl backend of QRhi is not prepared for UBOs atm. Have a uniform (heh) |
682 | // behavior regardless of the GLSL version. |
683 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, |
684 | value: true); |
685 | // Do not emit binding qualifiers for samplers (and for uniform blocks, but |
686 | // those we just disabled above). |
687 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_GLSL_ENABLE_420PACK_EXTENSION, |
688 | value: false); |
689 | spvc_compiler_install_compiler_options(compiler: d->glslGen, options); |
690 | |
691 | // Let's say the shader has these separate imagers and samplers: |
692 | // layout(binding = 2) uniform texture2D sepTex; |
693 | // layout(binding = 3) uniform sampler sepSampler; |
694 | // layout(binding = 4) uniform sampler sepSampler2; |
695 | // where sepTex is used with both samplers in the shader. |
696 | // For OpenGL this is mapped to "combined image samplers", i.e. the |
697 | // traditional sampler2Ds with arbitrary names: |
698 | // uniform sampler2D _39; |
699 | // uniform sampler2D _41; |
700 | // This mapping (2+3 -> "_39", 2+4 -> "_41") needs to be exposed to the caller. |
701 | if (spvc_compiler_build_combined_image_samplers(compiler: d->glslGen) != SPVC_SUCCESS) { |
702 | d->spirvCrossErrorMsg = QString::fromUtf8(utf8: spvc_context_get_last_error_string(context: d->ctx)); |
703 | return QByteArray(); |
704 | } |
705 | if (separateToCombinedImageSamplerMappings) { |
706 | const spvc_combined_image_sampler *combinedImageSamplerMappings = nullptr; |
707 | size_t numCombinedImageSamplerMappings = 0; |
708 | if (spvc_compiler_get_combined_image_samplers(compiler: d->glslGen, |
709 | samplers: &combinedImageSamplerMappings, |
710 | num_samplers: &numCombinedImageSamplerMappings) == SPVC_SUCCESS) |
711 | { |
712 | for (size_t i = 0; i < numCombinedImageSamplerMappings; ++i) { |
713 | const spvc_combined_image_sampler *mapping = &combinedImageSamplerMappings[i]; |
714 | QByteArray combinedName = spvc_compiler_get_name(compiler: d->glslGen, id: mapping->combined_id); |
715 | if (combinedName.isEmpty()) |
716 | combinedName = QByteArrayLiteral("_" ) + QByteArray::number(mapping->combined_id); |
717 | QByteArray textureName = spvc_compiler_get_name(compiler: d->glslGen, id: mapping->image_id); |
718 | QByteArray samplerName = spvc_compiler_get_name(compiler: d->glslGen, id: mapping->sampler_id); |
719 | separateToCombinedImageSamplerMappings->append(t: { .textureName: textureName, .samplerName: samplerName, .combinedSamplerName: combinedName }); |
720 | } |
721 | } |
722 | } |
723 | |
724 | const char *result = nullptr; |
725 | if (spvc_compiler_compile(compiler: d->glslGen, source: &result) != SPVC_SUCCESS) { |
726 | d->spirvCrossErrorMsg = QString::fromUtf8(utf8: spvc_context_get_last_error_string(context: d->ctx)); |
727 | return QByteArray(); |
728 | } |
729 | |
730 | // We used to fix up the result to complement GL_ARB_shading_language_420pack |
731 | // with GL_ARB_separate_shader_objects to make Mesa happy, but the 420pack |
732 | // is never relied on now so no need to do anything here. |
733 | return result; |
734 | } |
735 | |
736 | QByteArray QSpirvShader::translateToHLSL(int version, QShader::NativeResourceBindingMap *nativeBindings) const |
737 | { |
738 | d->spirvCrossErrorMsg.clear(); |
739 | |
740 | d->createCompiler(backend: SPVC_BACKEND_HLSL); |
741 | if (!d->hlslGen) |
742 | return QByteArray(); |
743 | |
744 | spvc_compiler_options options = nullptr; |
745 | if (spvc_compiler_create_compiler_options(compiler: d->hlslGen, options: &options) != SPVC_SUCCESS) |
746 | return QByteArray(); |
747 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL, |
748 | value: version); |
749 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_HLSL_POINT_SIZE_COMPAT, |
750 | value: true); |
751 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_HLSL_POINT_COORD_COMPAT, |
752 | value: true); |
753 | spvc_compiler_install_compiler_options(compiler: d->hlslGen, options); |
754 | |
755 | // D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT is 16, so we cannot have combined |
756 | // image/sampler bindings above 15. Problem is, the (SPIR-V) binding |
757 | // namespace is common with uniform buffers and other resources, and so |
758 | // having many uniform buffers and samplers can push the sampler binding to |
759 | // above 15 even when there are less than 16 samplers in total. So cannot |
760 | // just rely on SPIRV-Cross setting registers t<binding> and s<binding> for |
761 | // the SRV and sampler for each combined image/sampler. Instead, we want |
762 | // t<slot> and s<slot> where slot is 0-based, with its own namespace, and |
763 | // is then exposed in the native resource binding map so QRhi can map |
764 | // between (SPIR-V) bindings and D3D sampler slots. |
765 | const SpvExecutionModel stage = spvc_compiler_get_execution_model(compiler: d->hlslGen); |
766 | int regBinding = 0; // SRVs and samplers |
767 | for (const QShaderDescription::InOutVariable &var : d->shaderDescription.combinedImageSamplers()) { |
768 | spvc_hlsl_resource_binding bindingMapping; |
769 | bindingMapping.stage = stage; // will be per-stage but we have a per-shader NativeResourceBindingMap so it's ok |
770 | bindingMapping.desc_set = 0; |
771 | bindingMapping.binding = var.binding; |
772 | bindingMapping.srv.register_space = 0; |
773 | bindingMapping.srv.register_binding = regBinding; // t0, t1, ... |
774 | bindingMapping.sampler.register_space = 0; |
775 | bindingMapping.sampler.register_binding = regBinding; // s0, s1, ... |
776 | spvc_compiler_hlsl_add_resource_binding(compiler: d->hlslGen, binding: &bindingMapping); |
777 | nativeBindings->insert(key: var.binding, value: { regBinding, regBinding }); |
778 | regBinding += var.arrayDims.isEmpty() ? 1 : var.arrayDims.first(); |
779 | } |
780 | // In fact, with separate images and samplers added to the mix, the manual |
781 | // mapping specification is essential so that the HLSL nicely uses t0, t1, |
782 | // t2, ... and s0, s1, s2, ... for all the images and samplers incl. both |
783 | // combined and separate. |
784 | int firstSeparateImageReg = regBinding; |
785 | for (const QShaderDescription::InOutVariable &var : d->shaderDescription.separateImages()) { |
786 | spvc_hlsl_resource_binding bindingMapping; |
787 | bindingMapping.stage = stage; |
788 | bindingMapping.desc_set = 0; |
789 | bindingMapping.binding = var.binding; |
790 | bindingMapping.srv.register_space = 0; |
791 | bindingMapping.srv.register_binding = regBinding; // tN, tN+1, ..., where N is the next reg after the combined image sampler ones |
792 | spvc_compiler_hlsl_add_resource_binding(compiler: d->hlslGen, binding: &bindingMapping); |
793 | nativeBindings->insert(key: var.binding, value: { regBinding, -1 }); |
794 | regBinding += var.arrayDims.isEmpty() ? 1 : var.arrayDims.first(); |
795 | } |
796 | regBinding = firstSeparateImageReg; |
797 | for (const QShaderDescription::InOutVariable &var : d->shaderDescription.separateSamplers()) { |
798 | spvc_hlsl_resource_binding bindingMapping; |
799 | bindingMapping.stage = stage; |
800 | bindingMapping.desc_set = 0; |
801 | bindingMapping.binding = var.binding; |
802 | bindingMapping.sampler.register_space = 0; |
803 | bindingMapping.sampler.register_binding = regBinding; // sN, sN+1, ..., where N is the next reg after the combined image sampler ones |
804 | spvc_compiler_hlsl_add_resource_binding(compiler: d->hlslGen, binding: &bindingMapping); |
805 | nativeBindings->insert(key: var.binding, value: { regBinding, -1 }); |
806 | regBinding += var.arrayDims.isEmpty() ? 1 : var.arrayDims.first(); |
807 | } |
808 | |
809 | regBinding = 0; // CBVs |
810 | for (const QShaderDescription::UniformBlock &blk : d->shaderDescription.uniformBlocks()) { |
811 | spvc_hlsl_resource_binding bindingMapping; |
812 | bindingMapping.stage = stage; |
813 | bindingMapping.desc_set = 0; |
814 | bindingMapping.binding = blk.binding; |
815 | bindingMapping.cbv.register_space = 0; |
816 | bindingMapping.cbv.register_binding = regBinding; // b0, b1, ... |
817 | spvc_compiler_hlsl_add_resource_binding(compiler: d->hlslGen, binding: &bindingMapping); |
818 | nativeBindings->insert(key: blk.binding, value: { regBinding, -1 }); |
819 | regBinding += 1; |
820 | } |
821 | |
822 | regBinding = 0; // UAVs |
823 | for (const QShaderDescription::StorageBlock &blk : d->shaderDescription.storageBlocks()) { |
824 | spvc_hlsl_resource_binding bindingMapping; |
825 | bindingMapping.stage = stage; |
826 | bindingMapping.desc_set = 0; |
827 | bindingMapping.binding = blk.binding; |
828 | bindingMapping.uav.register_space = 0; |
829 | bindingMapping.uav.register_binding = regBinding; // u0, u1, ... |
830 | spvc_compiler_hlsl_add_resource_binding(compiler: d->hlslGen, binding: &bindingMapping); |
831 | nativeBindings->insert(key: blk.binding, value: { regBinding, -1 }); |
832 | regBinding += 1; |
833 | } |
834 | for (const QShaderDescription::InOutVariable &var : d->shaderDescription.storageImages()) { |
835 | spvc_hlsl_resource_binding bindingMapping; |
836 | bindingMapping.stage = stage; |
837 | bindingMapping.desc_set = 0; |
838 | bindingMapping.binding = var.binding; |
839 | bindingMapping.uav.register_space = 0; |
840 | bindingMapping.uav.register_binding = regBinding; // u0, u1, ... |
841 | spvc_compiler_hlsl_add_resource_binding(compiler: d->hlslGen, binding: &bindingMapping); |
842 | nativeBindings->insert(key: var.binding, value: { regBinding, -1 }); |
843 | regBinding += 1; |
844 | } |
845 | |
846 | const char *result = nullptr; |
847 | if (spvc_compiler_compile(compiler: d->hlslGen, source: &result) != SPVC_SUCCESS) { |
848 | d->spirvCrossErrorMsg = QString::fromUtf8(utf8: spvc_context_get_last_error_string(context: d->ctx)); |
849 | return QByteArray(); |
850 | } |
851 | |
852 | return QByteArray(result); |
853 | } |
854 | |
855 | QByteArray QSpirvShader::translateToMSL(int version, |
856 | MslFlags flags, |
857 | QShader::Stage stage, |
858 | QShader::NativeResourceBindingMap *nativeBindings, |
859 | QShader::NativeShaderInfo *shaderInfo, |
860 | const TessellationInfo &tessInfo) const |
861 | { |
862 | d->spirvCrossErrorMsg.clear(); |
863 | |
864 | d->createCompiler(backend: SPVC_BACKEND_MSL); |
865 | if (!d->mslGen) |
866 | return QByteArray(); |
867 | |
868 | spvc_compiler_options options = nullptr; |
869 | if (spvc_compiler_create_compiler_options(compiler: d->mslGen, options: &options) != SPVC_SUCCESS) |
870 | return QByteArray(); |
871 | |
872 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_VERSION, |
873 | SPVC_MAKE_MSL_VERSION(version / 10, version % 10, 0)); |
874 | |
875 | if (flags.testFlag(flag: MslFlag::VertexAsCompute)) { |
876 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_MSL_VERTEX_FOR_TESSELLATION, value: 1); |
877 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_MSL_CAPTURE_OUTPUT_TO_BUFFER, value: 1); |
878 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_MSL_DISABLE_RASTERIZATION, value: 1); |
879 | |
880 | spvc_msl_index_type indexType = SPVC_MSL_INDEX_TYPE_NONE; |
881 | if (flags.testFlag(flag: MslFlag::WithUInt16Index)) |
882 | indexType = SPVC_MSL_INDEX_TYPE_UINT16; |
883 | else if (flags.testFlag(flag: MslFlag::WithUInt32Index)) |
884 | indexType = SPVC_MSL_INDEX_TYPE_UINT32; |
885 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_VERTEX_INDEX_TYPE, value: indexType); |
886 | } |
887 | |
888 | // for tessellation; matches the defaults; not sure how to query so just set them |
889 | uint spvIndicesBufferIndex = 21; |
890 | uint spvInBufferIndex = 22; |
891 | uint spvTessLevelBufferIndex = 26; |
892 | uint spvPatchOutBufferIndex = 27; |
893 | uint spvOutBufferIndex = 28; |
894 | uint spvIndirectParamsBufferIndex = 29; |
895 | |
896 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_SHADER_INDEX_BUFFER_INDEX, value: spvIndicesBufferIndex); |
897 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_SHADER_INPUT_BUFFER_INDEX, value: spvInBufferIndex); |
898 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_SHADER_TESS_FACTOR_OUTPUT_BUFFER_INDEX, value: spvTessLevelBufferIndex); |
899 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_SHADER_PATCH_OUTPUT_BUFFER_INDEX, value: spvPatchOutBufferIndex); |
900 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_SHADER_OUTPUT_BUFFER_INDEX, value: spvOutBufferIndex); |
901 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_INDIRECT_PARAMS_BUFFER_INDEX, value: spvIndirectParamsBufferIndex); |
902 | |
903 | // for buffer size buffer; matches defaults |
904 | uint spvBufferSizeBufferIndex = 25; |
905 | spvc_compiler_options_set_uint(options, option: SPVC_COMPILER_OPTION_MSL_BUFFER_SIZE_BUFFER_INDEX, value: spvBufferSizeBufferIndex); |
906 | |
907 | if (stage == QShader::TessellationControlStage) { |
908 | // required to get the kind of tess.control inputs we need |
909 | spvc_compiler_options_set_bool(options, option: SPVC_COMPILER_OPTION_MSL_MULTI_PATCH_WORKGROUP, value: 1); |
910 | |
911 | // the execution mode needs to be known (e.g. to switch between |
912 | // MTLTriangleTessellationFactorsHalf and MTLQuadTessellationFactorsHalf) |
913 | SpvExecutionMode execMode = SpvExecutionModeTriangles; |
914 | switch (tessInfo.infoForTesc.mode) { |
915 | case QShaderDescription::QuadTessellationMode: |
916 | execMode = SpvExecutionModeQuads; |
917 | break; |
918 | case QShaderDescription::IsolineTessellationMode: |
919 | d->spirvCrossErrorMsg = QLatin1String("Isoline tessellation mode is not supported with Metal" ); |
920 | return QByteArray(); |
921 | default: |
922 | break; |
923 | } |
924 | spvc_compiler_set_execution_mode(compiler: d->mslGen, mode: execMode); |
925 | } |
926 | |
927 | if (stage == QShader::TessellationEvaluationStage) |
928 | spvc_compiler_set_execution_mode_with_arguments(compiler: d->mslGen, mode: SpvExecutionModeOutputVertices, arg0: uint(tessInfo.infoForTese.vertexCount), arg1: 0, arg2: 0); |
929 | |
930 | // leave platform set to macOS, it won't matter in practice (hopefully) |
931 | spvc_compiler_install_compiler_options(compiler: d->mslGen, options); |
932 | |
933 | const char *result = nullptr; |
934 | if (spvc_compiler_compile(compiler: d->mslGen, source: &result) != SPVC_SUCCESS) { |
935 | d->spirvCrossErrorMsg = QString::fromUtf8(utf8: spvc_context_get_last_error_string(context: d->ctx)); |
936 | return QByteArray(); |
937 | } |
938 | |
939 | if (nativeBindings) { |
940 | spvc_resources resources; |
941 | if (spvc_compiler_create_shader_resources(compiler: d->mslGen, resources: &resources) == SPVC_SUCCESS) { |
942 | const spvc_reflected_resource *resourceList = nullptr; |
943 | size_t resourceListCount = 0; |
944 | // A nativeBinding of -1 means unused. This also fits |
945 | // *_get_automatic_resource_binding() which returns uint32_t(-1) |
946 | // when there is no binding, which can happen when the uniform |
947 | // block, sampler, etc. is not actively used in the shader. The map |
948 | // must always be complete, including a (binding -> -1) mapping for |
949 | // inactive resources as well. The second value of the pair is only |
950 | // relevant for combined image samplers, and is -1 otherwise. |
951 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, |
952 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
953 | { |
954 | for (size_t i = 0; i < resourceListCount; ++i) { |
955 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
956 | unsigned nativeBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
957 | nativeBindings->insert(key: int(binding), value: { int(nativeBinding), -1 }); |
958 | } |
959 | } |
960 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_STORAGE_BUFFER, |
961 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
962 | { |
963 | for (size_t i = 0; i < resourceListCount; ++i) { |
964 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
965 | unsigned nativeBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
966 | nativeBindings->insert(key: int(binding), value: { int(nativeBinding), -1 }); |
967 | } |
968 | } |
969 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, |
970 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
971 | { |
972 | for (size_t i = 0; i < resourceListCount; ++i) { |
973 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
974 | unsigned nativeTextureBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
975 | unsigned nativeSamplerBinding = spvc_compiler_msl_get_automatic_resource_binding_secondary(compiler: d->mslGen, id: resourceList[i].id); |
976 | nativeBindings->insert(key: int(binding), value: { int(nativeTextureBinding), int(nativeSamplerBinding) }); |
977 | } |
978 | } |
979 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SEPARATE_IMAGE, |
980 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
981 | { |
982 | for (size_t i = 0; i < resourceListCount; ++i) { |
983 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
984 | unsigned nativeTextureBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
985 | nativeBindings->insert(key: int(binding), value: { int(nativeTextureBinding), -1 }); |
986 | } |
987 | } |
988 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS, |
989 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
990 | { |
991 | for (size_t i = 0; i < resourceListCount; ++i) { |
992 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
993 | unsigned nativeSamplerBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
994 | nativeBindings->insert(key: int(binding), value: { int(nativeSamplerBinding), -1 }); |
995 | } |
996 | } |
997 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, |
998 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
999 | { |
1000 | for (size_t i = 0; i < resourceListCount; ++i) { |
1001 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
1002 | unsigned nativeTextureBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
1003 | unsigned nativeSamplerBinding = spvc_compiler_msl_get_automatic_resource_binding_secondary(compiler: d->mslGen, id: resourceList[i].id); |
1004 | nativeBindings->insert(key: int(binding), value: { int(nativeTextureBinding), int(nativeSamplerBinding) }); |
1005 | } |
1006 | } |
1007 | if (spvc_resources_get_resource_list_for_type(resources, type: SPVC_RESOURCE_TYPE_STORAGE_IMAGE, |
1008 | resource_list: &resourceList, resource_size: &resourceListCount) == SPVC_SUCCESS) |
1009 | { |
1010 | for (size_t i = 0; i < resourceListCount; ++i) { |
1011 | unsigned binding = spvc_compiler_get_decoration(compiler: d->mslGen, id: resourceList[i].id, decoration: SpvDecorationBinding); |
1012 | unsigned nativeBinding = spvc_compiler_msl_get_automatic_resource_binding(compiler: d->mslGen, id: resourceList[i].id); |
1013 | nativeBindings->insert(key: int(binding), value: { int(nativeBinding), -1 }); |
1014 | } |
1015 | } |
1016 | } |
1017 | } |
1018 | |
1019 | if (spvc_compiler_msl_needs_swizzle_buffer(compiler: d->mslGen)) |
1020 | qWarning(msg: "Translated Metal shader needs swizzle buffer, this is unexpected" ); |
1021 | |
1022 | if (spvc_compiler_msl_needs_buffer_size_buffer(compiler: d->mslGen)) |
1023 | shaderInfo->extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding] = spvBufferSizeBufferIndex; |
1024 | |
1025 | // (Aim to) only store extraBufferBindings entries for things that really |
1026 | // are present, because the presence of a key can already trigger certain |
1027 | // behavior during rendering. (e.g. generating and exposing the |
1028 | // corresponding implicit buffer) |
1029 | switch (spvc_compiler_get_execution_model(compiler: d->mslGen)) { |
1030 | case SpvExecutionModelVertex: |
1031 | if (flags.testFlag(flag: MslFlag::VertexAsCompute)) { |
1032 | if (spvc_compiler_msl_needs_output_buffer(compiler: d->mslGen)) |
1033 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessVertTescOutputBufferBinding] = spvOutBufferIndex; |
1034 | if (flags.testFlag(flag: MslFlag::WithUInt16Index) || flags.testFlag(flag: MslFlag::WithUInt32Index)) { |
1035 | // not given that this buffer is really used in the code, |
1036 | // depends on e.g. the usage of gl_VertexIndex, but not sure how |
1037 | // to tell that here |
1038 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessVertIndicesBufferBinding] = spvIndicesBufferIndex; |
1039 | } |
1040 | } |
1041 | break; |
1042 | case SpvExecutionModelTessellationControl: |
1043 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescInputBufferBinding] = spvInBufferIndex; |
1044 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescTessLevelBufferBinding] = spvTessLevelBufferIndex; |
1045 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescParamsBufferBinding] = spvIndirectParamsBufferIndex; |
1046 | if (spvc_compiler_msl_needs_output_buffer(compiler: d->mslGen)) |
1047 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessVertTescOutputBufferBinding] = spvOutBufferIndex; |
1048 | if (spvc_compiler_msl_needs_patch_output_buffer(compiler: d->mslGen)) |
1049 | shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescPatchOutputBufferBinding] = spvPatchOutBufferIndex; |
1050 | break; |
1051 | default: |
1052 | break; |
1053 | } |
1054 | |
1055 | return QByteArray(result); |
1056 | } |
1057 | |
1058 | QString QSpirvShader::translationErrorMessage() const |
1059 | { |
1060 | return d->spirvCrossErrorMsg; |
1061 | } |
1062 | |
1063 | QT_END_NAMESPACE |
1064 | |