MythTV master
mythshadervulkan.cpp
Go to the documentation of this file.
1// MythTV
2#include "libmythbase/mythconfig.h"
6
7// Std
8#include <algorithm>
9
10#define LOC QString("VulkanShader: ")
11
12// libglslang
13#if CONFIG_LIBGLSLANG
14#include <glslang/Public/ShaderLang.h>
15#include <glslang/SPIRV/GlslangToSpv.h>
16
17static const TBuiltInResource s_TBuiltInResource = {
18 .maxLights = 32,
19 .maxClipPlanes = 6,
20 .maxTextureUnits = 32,
21 .maxTextureCoords = 32,
22 .maxVertexAttribs = 64,
23 .maxVertexUniformComponents = 4096,
24 .maxVaryingFloats = 64,
25 .maxVertexTextureImageUnits = 32,
26 .maxCombinedTextureImageUnits = 80,
27 .maxTextureImageUnits = 32,
28 .maxFragmentUniformComponents = 4096,
29 .maxDrawBuffers = 32,
30 .maxVertexUniformVectors = 128,
31 .maxVaryingVectors = 8,
32 .maxFragmentUniformVectors = 16,
33 .maxVertexOutputVectors = 16,
34 .maxFragmentInputVectors = 15,
35 .minProgramTexelOffset = -8,
36 .maxProgramTexelOffset = 7,
37 .maxClipDistances = 8,
38 .maxComputeWorkGroupCountX = 65535,
39 .maxComputeWorkGroupCountY = 65535,
40 .maxComputeWorkGroupCountZ = 65535,
41 .maxComputeWorkGroupSizeX = 1024,
42 .maxComputeWorkGroupSizeY = 1024,
43 .maxComputeWorkGroupSizeZ = 64,
44 .maxComputeUniformComponents = 1024,
45 .maxComputeTextureImageUnits = 16,
46 .maxComputeImageUniforms = 8,
47 .maxComputeAtomicCounters = 8,
48 .maxComputeAtomicCounterBuffers = 1,
49 .maxVaryingComponents = 60,
50 .maxVertexOutputComponents = 64,
51 .maxGeometryInputComponents = 64,
52 .maxGeometryOutputComponents = 128,
53 .maxFragmentInputComponents = 128,
54 .maxImageUnits = 8,
55 .maxCombinedImageUnitsAndFragmentOutputs = 8,
56 .maxCombinedShaderOutputResources = 8,
57 .maxImageSamples = 0,
58 .maxVertexImageUniforms = 0,
59 .maxTessControlImageUniforms = 0,
60 .maxTessEvaluationImageUniforms = 0,
61 .maxGeometryImageUniforms = 0,
62 .maxFragmentImageUniforms = 8,
63 .maxCombinedImageUniforms = 8,
64 .maxGeometryTextureImageUnits = 16,
65 .maxGeometryOutputVertices = 256,
66 .maxGeometryTotalOutputComponents = 1024,
67 .maxGeometryUniformComponents = 1024,
68 .maxGeometryVaryingComponents = 64,
69 .maxTessControlInputComponents = 128,
70 .maxTessControlOutputComponents = 128,
71 .maxTessControlTextureImageUnits = 16,
72 .maxTessControlUniformComponents = 1024,
73 .maxTessControlTotalOutputComponents = 4096,
74 .maxTessEvaluationInputComponents = 128,
75 .maxTessEvaluationOutputComponents = 128,
76 .maxTessEvaluationTextureImageUnits = 16,
77 .maxTessEvaluationUniformComponents = 1024,
78 .maxTessPatchComponents = 120,
79 .maxPatchVertices = 32,
80 .maxTessGenLevel = 64,
81 .maxViewports = 16,
82 .maxVertexAtomicCounters = 0,
83 .maxTessControlAtomicCounters = 0,
84 .maxTessEvaluationAtomicCounters = 0,
85 .maxGeometryAtomicCounters = 0,
86 .maxFragmentAtomicCounters = 8,
87 .maxCombinedAtomicCounters = 8,
88 .maxAtomicCounterBindings = 1,
89 .maxVertexAtomicCounterBuffers = 0,
90 .maxTessControlAtomicCounterBuffers = 0,
91 .maxTessEvaluationAtomicCounterBuffers = 0,
92 .maxGeometryAtomicCounterBuffers = 0,
93 .maxFragmentAtomicCounterBuffers = 1,
94 .maxCombinedAtomicCounterBuffers = 1,
95 .maxAtomicCounterBufferSize = 16384,
96 .maxTransformFeedbackBuffers = 4,
97 .maxTransformFeedbackInterleavedComponents = 64,
98 .maxCullDistances = 8,
99 .maxCombinedClipAndCullDistances = 8,
100 .maxSamples = 4,
101 .maxMeshOutputVerticesNV = 256,
102 .maxMeshOutputPrimitivesNV = 512,
103 .maxMeshWorkGroupSizeX_NV = 32,
104 .maxMeshWorkGroupSizeY_NV = 1,
105 .maxMeshWorkGroupSizeZ_NV = 1,
106 .maxTaskWorkGroupSizeX_NV = 32,
107 .maxTaskWorkGroupSizeY_NV = 1,
108 .maxTaskWorkGroupSizeZ_NV = 1,
109 .maxMeshViewCountNV = 4,
110#if HAVE_TBUILTINRESOURCE_EXT_FIELDS
111 .maxMeshOutputVerticesEXT = 0,
112 .maxMeshOutputPrimitivesEXT = 0,
113 .maxMeshWorkGroupSizeX_EXT = 0,
114 .maxMeshWorkGroupSizeY_EXT = 0,
115 .maxMeshWorkGroupSizeZ_EXT = 0,
116 .maxTaskWorkGroupSizeX_EXT = 0,
117 .maxTaskWorkGroupSizeY_EXT = 0,
118 .maxTaskWorkGroupSizeZ_EXT = 0,
119 .maxMeshViewCountEXT = 0,
120 .maxDualSourceDrawBuffersEXT = 0,
121#endif
122
123 .limits = {
124 .nonInductiveForLoops = true,
125 .whileLoops = true,
126 .doWhileLoops = true,
127 .generalUniformIndexing = true,
128 .generalAttributeMatrixVectorIndexing = true,
129 .generalVaryingIndexing = true,
130 .generalSamplerIndexing = true,
131 .generalVariableIndexing = true,
132 .generalConstantMatrixVectorIndexing = true,
133 }
134};
135
136static auto GLSLangCompile(EShLanguage Stage, const QString &Code)
137{
138 std::vector<uint32_t> result;
139 auto *shader = new glslang::TShader(Stage);
140 if (!shader)
141 return result;
142
143 QByteArray data = Code.toLocal8Bit();
144 const char *tmp = data.constData();
145 shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
146 shader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
147 shader->setStrings(&tmp, 1);
148 if (!shader->parse(&s_TBuiltInResource, glslang::EShTargetVulkan_1_1, true, EShMsgDefault))
149 {
150 LOG(VB_GENERAL, LOG_ERR, LOC + "Shader parse error:");
151 LOG(VB_GENERAL, LOG_ERR, shader->getInfoLog());
152 delete shader;
153 return result;
154 }
155
156 auto *program = new glslang::TProgram();
157 if (!program)
158 {
159 delete shader;
160 return result;
161 }
162
163 program->addShader(shader);
164 if (program->link(EShMsgDefault))
165 {
166 auto ByteCodeToString = [](std::vector<uint32_t> &OpCodes)
167 {
168 QString string;
169 int count = 0;
170 for (uint32_t opcode : OpCodes)
171 {
172 if (count++ == 0) string += "\n";
173 if (count > 5) count = 0;
174 string += "0x" + QString("%1, ").arg(opcode, 8, 16, QLatin1Char('0')).toUpper();
175 }
176 return string;
177 };
178
179 glslang::SpvOptions options { };
180 options.generateDebugInfo = false;
181 options.disassemble = false;
182 options.validate = false;
183 options.disableOptimizer = false;
184 options.optimizeSize = true;
185 GlslangToSpv(*program->getIntermediate(Stage), result, &options);
186 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Generated SPIR-V: %1bytes").arg(result.size() * sizeof (uint32_t)));
187 LOG(VB_GENERAL, LOG_INFO, "Source:\n" + Code);
188 LOG(VB_GENERAL, LOG_INFO, "ByteCode:\n" + ByteCodeToString(result));
189 }
190 else
191 {
192 LOG(VB_GENERAL, LOG_ERR, LOC + "Shader link error:");
193 LOG(VB_GENERAL, LOG_ERR, program->getInfoLog());
194 }
195
196 delete shader;
197 delete program;
198 return result;
199}
200
201bool MythShaderVulkan::InitGLSLang(bool Release /* = false */)
202{
203 static QMutex s_glslangLock;
204 static int s_glslangRefcount = 0;
205 static bool s_initSuccess = false;
206 QMutexLocker locker(&s_glslangLock);
207
208 if (Release)
209 {
210 s_glslangRefcount--;
211 if (s_glslangRefcount < 0)
212 {
213 LOG(VB_GENERAL, LOG_ERR, LOC + "GLSLang ref count error");
214 return false;
215 }
216
217 if (s_glslangRefcount < 1)
218 {
219 LOG(VB_GENERAL, LOG_INFO, LOC + "GLSLang released");
220 glslang::FinalizeProcess();
221 }
222 return true;
223 }
224
225 if (s_glslangRefcount < 1)
226 {
227 s_initSuccess = glslang::InitializeProcess();
228 if (s_initSuccess)
229 LOG(VB_GENERAL, LOG_INFO, LOC + "GLSLang initialised");
230 }
231 s_glslangRefcount++;
232 return s_initSuccess;
233}
234
235bool MythShaderVulkan::CreateShaderFromGLSL(const std::vector<MythGLSLStage> &Stages)
236{
237 if (Stages.empty())
238 return false;
239
240 if (!MythShaderVulkan::InitGLSLang())
241 return false;
242
243 std::vector<MythSPIRVStage> spirvstages;
244
245 auto glslangtype = [](VkShaderStageFlags Type)
246 {
247 switch (Type)
248 {
249 case VK_SHADER_STAGE_VERTEX_BIT: return EShLangVertex;
250 case VK_SHADER_STAGE_FRAGMENT_BIT: return EShLangFragment;
251 case VK_SHADER_STAGE_COMPUTE_BIT: return EShLangCompute;
252 default: break;
253 }
254 return EShLangCount;
255 };
256
257 for (const auto& stage : Stages)
258 if (glslangtype(stage.first) != EShLangCount)
259 spirvstages.emplace_back(stage.first, GLSLangCompile(glslangtype(stage.first), stage.second));
260 MythShaderVulkan::InitGLSLang(true);
261 return CreateShaderFromSPIRV(spirvstages);
262}
263#endif
264
274 const std::vector<int> &Stages,
275 const MythShaderMap *Sources,
276 const MythBindingMap *Bindings)
277{
278 auto * result = new MythShaderVulkan(Vulkan, Stages, Sources, Bindings);
279 if (result && !result->IsValidVulkan())
280 {
281 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create shader");
282 delete result;
283 result = nullptr;
284 }
285 return result;
286}
287
289 const std::vector<int> &Stages,
290 const MythShaderMap *Sources,
291 const MythBindingMap *Bindings)
292 : MythVulkanObject(Vulkan)
293{
294 if (!m_vulkanValid || Stages.empty())
295 return;
296 m_vulkanValid = false;
297
298 if (!Sources)
299 Sources = &k450DefaultShaders;
300
301 if (!Bindings)
302 Bindings = &k450ShaderBindings;
303
304 // ensure we have sources and bindings
305 for (const auto & stage : Stages)
306 if (!Sources->contains(stage) || !Bindings->contains(stage))
307 return;
308
309 // build the descriptor set layouts from the shader descriptions
310 bool foundvertices = false;
311 bool pushconstants = false;
312 VkPushConstantRange ranges = { };
313
314 std::map<int, std::vector<VkDescriptorSetLayoutBinding>> layoutbindings;
315 std::map<int, std::vector<VkDescriptorPoolSize>> poolsizes;
316 for (const auto & stage : Stages)
317 {
318 bool isvertex = false;
319 MythBindingDesc desc = Bindings->at(stage);
320 m_topology = std::get<0>(desc);
321 MythStageLayout binding = std::get<1>(desc);
322
323 for (auto & stagelayout : binding)
324 {
325 if (stagelayout.second.stageFlags == VK_SHADER_STAGE_VERTEX_BIT)
326 isvertex = true;
327
328 if (layoutbindings.contains(stagelayout.first))
329 layoutbindings.at(stagelayout.first).emplace_back(stagelayout.second);
330 else
331 layoutbindings.insert( { stagelayout.first, { stagelayout.second } } );
332
333 VkDescriptorPoolSize poolsize = {stagelayout.second.descriptorType, stagelayout.second.descriptorCount};
334 if (poolsizes.contains(stagelayout.first))
335 poolsizes.at(stagelayout.first).emplace_back(poolsize);
336 else
337 poolsizes.insert( { stagelayout.first, { poolsize } } );
338 }
339
340 if (isvertex && !foundvertices)
341 {
342 foundvertices = true;
343 m_vertexBindingDesc = std::get<2>(desc);
344 m_vertexAttributes = std::get<3>(desc);
345 }
346
347 if (!pushconstants)
348 {
349 VkPushConstantRange range = std::get<4>(desc);
350 if (range.stageFlags)
351 {
352 pushconstants = true;
353 ranges = range;
354 }
355 }
356 }
357
358 // convert poolsizes to vector
359 for (const auto& poolsize : poolsizes)
360 m_descriptorPoolSizes.emplace_back(poolsize.second);
361
362 // create the desriptor layouts
363 for (auto & layoutbinding : layoutbindings)
364 {
365 VkDescriptorSetLayout layout = MYTH_NULL_DISPATCH;
366 VkDescriptorSetLayoutCreateInfo layoutinfo { };
367 layoutinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
368 layoutinfo.bindingCount = static_cast<uint32_t>(layoutbinding.second.size());
369 layoutinfo.pBindings = layoutbinding.second.data();
370 if (m_vulkanFuncs->vkCreateDescriptorSetLayout(m_vulkanDevice, &layoutinfo, nullptr, &layout) != VK_SUCCESS)
371 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create DescriptorSetLayout");
372 else
373 m_descriptorSetLayouts.push_back(layout);
374 }
375
376 if (m_descriptorSetLayouts.size() != layoutbindings.size())
377 {
378 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create all layouts");
379 return;
380 }
381
382 VkPipelineLayoutCreateInfo pipelinelayout { };
383 pipelinelayout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
384 pipelinelayout.setLayoutCount = static_cast<uint32_t>(m_descriptorSetLayouts.size());
385 pipelinelayout.pSetLayouts = m_descriptorSetLayouts.data();
386
387 if (pushconstants)
388 {
389 pipelinelayout.pushConstantRangeCount = 1;
390 pipelinelayout.pPushConstantRanges = &ranges;
391 }
392
393 if (m_vulkanFuncs->vkCreatePipelineLayout(m_vulkanDevice, &pipelinelayout, nullptr, &m_pipelineLayout) != VK_SUCCESS)
394 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create pipeline layout");
395
396 if (!m_pipelineLayout)
397 return;
398
399#if CONFIG_LIBGLSLANG
400 static bool useglsl = qEnvironmentVariableIsSet("MYTHTV_GLSLANG");
401 if (useglsl)
402 {
403 std::vector<MythGLSLStage> glslstages;
404 std::ranges::transform(Stages, std::back_inserter(glslstages),
405 [&](int Stage) { return MythGLSLStage{Stage & VK_SHADER_STAGE_ALL_GRAPHICS, Sources->at(Stage).first }; });
406 m_vulkanValid = MythShaderVulkan::CreateShaderFromGLSL(glslstages);
407 return;
408 }
409#endif
410
411 std::vector<MythSPIRVStage> stages;
412 std::ranges::transform(Stages, std::back_inserter(stages),
413 [&](int Stage) { return MythSPIRVStage{Stage & VK_SHADER_STAGE_ALL_GRAPHICS, Sources->at(Stage).second }; });
415}
416
418{
419 if (m_vulkanValid)
420 {
421 m_vulkanFuncs->vkDestroyPipelineLayout(m_vulkanDevice, m_pipelineLayout, nullptr);
422 for (auto & layout : m_descriptorSetLayouts)
423 m_vulkanFuncs->vkDestroyDescriptorSetLayout(m_vulkanDevice, layout, nullptr);
424 for (auto & stage : m_stages)
425 m_vulkanFuncs->vkDestroyShaderModule(m_vulkanDevice, stage.module, nullptr);
426 }
427 for (auto * spirv : m_spirv)
428 delete [] spirv;
429}
430
431bool MythShaderVulkan::CreateShaderFromSPIRV(const std::vector<MythSPIRVStage> &Stages)
432{
433 if (Stages.empty())
434 return false;
435
436 if (std::ranges::any_of(Stages,
437 [](const MythSPIRVStage& Stage) { return Stage.second.empty(); }))
438 return false;
439
440 bool success = true;
441 for (const auto & stage : Stages)
442 {
443 auto size = stage.second.size() * sizeof (uint32_t);
444 auto *code = reinterpret_cast<uint32_t*>(new uint8_t [size]);
445 memcpy(code, stage.second.data(), size);
446 VkShaderModule module = MYTH_NULL_DISPATCH;
447 VkShaderModuleCreateInfo create { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, nullptr, 0, size, code };
448 success &= (m_vulkanFuncs->vkCreateShaderModule(m_vulkanDevice, &create, nullptr, &module) == VK_SUCCESS);
449 m_stages.push_back( { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr,
450 0, static_cast<VkShaderStageFlagBits>(stage.first), module, "main", nullptr } );
451 m_spirv.push_back(code);
452 }
453 return success;
454}
455
456const std::vector<VkPipelineShaderStageCreateInfo>& MythShaderVulkan::Stages(void) const
457{
458 return m_stages;
459}
460
461
463{
464 return m_vertexAttributes;
465}
466
467const VkVertexInputBindingDescription& MythShaderVulkan::GetVertexBindingDesc(void) const
468{
469 return m_vertexBindingDesc;
470}
471
472VkPipelineLayout MythShaderVulkan::GetPipelineLayout(void) const
473{
474 return m_pipelineLayout;
475}
476
477const std::vector<VkDescriptorPoolSize>& MythShaderVulkan::GetPoolSizes(size_t Set) const
478{
479 if (Set < m_descriptorPoolSizes.size())
480 return m_descriptorPoolSizes.at(Set);
481 static const std::vector<VkDescriptorPoolSize> broken;
482 return broken;
483}
484
485VkDescriptorSetLayout MythShaderVulkan::GetDescSetLayout(size_t Set) const
486{
487 if (Set < m_descriptorSetLayouts.size())
488 return m_descriptorSetLayouts.at(Set);
489 return MYTH_NULL_DISPATCH;
490}
491
492VkPrimitiveTopology MythShaderVulkan::GetTopology() const
493{
494 return m_topology;
495}
Creates shader objects suitable for use with the Vulkan API.
VkPrimitiveTopology GetTopology() const
std::vector< uint32_t * > m_spirv
const std::vector< VkDescriptorPoolSize > & GetPoolSizes(size_t Set) const
bool CreateShaderFromSPIRV(const std::vector< MythSPIRVStage > &Stages)
const std::vector< VkPipelineShaderStageCreateInfo > & Stages(void) const
VkVertexInputBindingDescription m_vertexBindingDesc
VkPipelineLayout m_pipelineLayout
std::vector< std::vector< VkDescriptorPoolSize > > m_descriptorPoolSizes
MythShaderVulkan(MythVulkanObject *Vulkan, const std::vector< int > &Stages, const MythShaderMap *Sources=nullptr, const MythBindingMap *Bindings=nullptr)
VkDescriptorSetLayout GetDescSetLayout(size_t Set) const
static MythShaderVulkan * Create(MythVulkanObject *Vulkan, const std::vector< int > &Stages, const MythShaderMap *Sources=nullptr, const MythBindingMap *Bindings=nullptr)
MythVertexAttrs m_vertexAttributes
VkPrimitiveTopology m_topology
const VkVertexInputBindingDescription & GetVertexBindingDesc(void) const
const MythVertexAttrs & GetVertexAttributes(void) const
std::vector< VkPipelineShaderStageCreateInfo > m_stages
std::vector< VkDescriptorSetLayout > m_descriptorSetLayouts
VkPipelineLayout GetPipelineLayout(void) const
QVulkanDeviceFunctions * m_vulkanFuncs
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define MYTH_NULL_DISPATCH
static const MythBindingMap k450ShaderBindings
static const MythShaderMap k450DefaultShaders
#define LOC
std::pair< VkShaderStageFlags, const std::vector< uint32_t > > MythSPIRVStage
std::map< int, std::pair< QString, std::vector< uint32_t > > > MythShaderMap
std::vector< VkVertexInputAttributeDescription > MythVertexAttrs
std::tuple< VkPrimitiveTopology, MythStageLayout, VkVertexInputBindingDescription, MythVertexAttrs, VkPushConstantRange > MythBindingDesc
std::pair< VkShaderStageFlags, QString > MythGLSLStage
std::vector< MythSetLayout > MythStageLayout
std::map< int, MythBindingDesc > MythBindingMap