/ src / video_core / renderer_vulkan / vk_shader_util.cpp
vk_shader_util.cpp
  1  // Copyright 2023 Citra Emulator Project
  2  // Licensed under GPLv2 or any later version
  3  // Refer to the license.txt file included.
  4  
  5  #include <SPIRV/GlslangToSpv.h>
  6  #include <glslang/Include/ResourceLimits.h>
  7  #include <glslang/Public/ShaderLang.h>
  8  #include "common/assert.h"
  9  #include "common/literals.h"
 10  #include "common/logging/log.h"
 11  #include "video_core/renderer_vulkan/vk_shader_util.h"
 12  
 13  namespace Vulkan {
 14  
 15  using namespace Common::Literals;
 16  
 17  namespace {
 18  constexpr TBuiltInResource DefaultTBuiltInResource = {
 19      .maxLights = 32,
 20      .maxClipPlanes = 6,
 21      .maxTextureUnits = 32,
 22      .maxTextureCoords = 32,
 23      .maxVertexAttribs = 64,
 24      .maxVertexUniformComponents = 4096,
 25      .maxVaryingFloats = 64,
 26      .maxVertexTextureImageUnits = 32,
 27      .maxCombinedTextureImageUnits = 80,
 28      .maxTextureImageUnits = 32,
 29      .maxFragmentUniformComponents = 4096,
 30      .maxDrawBuffers = 32,
 31      .maxVertexUniformVectors = 128,
 32      .maxVaryingVectors = 8,
 33      .maxFragmentUniformVectors = 16,
 34      .maxVertexOutputVectors = 16,
 35      .maxFragmentInputVectors = 15,
 36      .minProgramTexelOffset = -8,
 37      .maxProgramTexelOffset = 7,
 38      .maxClipDistances = 8,
 39      .maxComputeWorkGroupCountX = 65535,
 40      .maxComputeWorkGroupCountY = 65535,
 41      .maxComputeWorkGroupCountZ = 65535,
 42      .maxComputeWorkGroupSizeX = 1024,
 43      .maxComputeWorkGroupSizeY = 1024,
 44      .maxComputeWorkGroupSizeZ = 64,
 45      .maxComputeUniformComponents = 1024,
 46      .maxComputeTextureImageUnits = 16,
 47      .maxComputeImageUniforms = 8,
 48      .maxComputeAtomicCounters = 8,
 49      .maxComputeAtomicCounterBuffers = 1,
 50      .maxVaryingComponents = 60,
 51      .maxVertexOutputComponents = 64,
 52      .maxGeometryInputComponents = 64,
 53      .maxGeometryOutputComponents = 128,
 54      .maxFragmentInputComponents = 128,
 55      .maxImageUnits = 8,
 56      .maxCombinedImageUnitsAndFragmentOutputs = 8,
 57      .maxCombinedShaderOutputResources = 8,
 58      .maxImageSamples = 0,
 59      .maxVertexImageUniforms = 0,
 60      .maxTessControlImageUniforms = 0,
 61      .maxTessEvaluationImageUniforms = 0,
 62      .maxGeometryImageUniforms = 0,
 63      .maxFragmentImageUniforms = 8,
 64      .maxCombinedImageUniforms = 8,
 65      .maxGeometryTextureImageUnits = 16,
 66      .maxGeometryOutputVertices = 256,
 67      .maxGeometryTotalOutputComponents = 1024,
 68      .maxGeometryUniformComponents = 1024,
 69      .maxGeometryVaryingComponents = 64,
 70      .maxTessControlInputComponents = 128,
 71      .maxTessControlOutputComponents = 128,
 72      .maxTessControlTextureImageUnits = 16,
 73      .maxTessControlUniformComponents = 1024,
 74      .maxTessControlTotalOutputComponents = 4096,
 75      .maxTessEvaluationInputComponents = 128,
 76      .maxTessEvaluationOutputComponents = 128,
 77      .maxTessEvaluationTextureImageUnits = 16,
 78      .maxTessEvaluationUniformComponents = 1024,
 79      .maxTessPatchComponents = 120,
 80      .maxPatchVertices = 32,
 81      .maxTessGenLevel = 64,
 82      .maxViewports = 16,
 83      .maxVertexAtomicCounters = 0,
 84      .maxTessControlAtomicCounters = 0,
 85      .maxTessEvaluationAtomicCounters = 0,
 86      .maxGeometryAtomicCounters = 0,
 87      .maxFragmentAtomicCounters = 8,
 88      .maxCombinedAtomicCounters = 8,
 89      .maxAtomicCounterBindings = 1,
 90      .maxVertexAtomicCounterBuffers = 0,
 91      .maxTessControlAtomicCounterBuffers = 0,
 92      .maxTessEvaluationAtomicCounterBuffers = 0,
 93      .maxGeometryAtomicCounterBuffers = 0,
 94      .maxFragmentAtomicCounterBuffers = 1,
 95      .maxCombinedAtomicCounterBuffers = 1,
 96      .maxAtomicCounterBufferSize = 16384,
 97      .maxTransformFeedbackBuffers = 4,
 98      .maxTransformFeedbackInterleavedComponents = 64,
 99      .maxCullDistances = 8,
100      .maxCombinedClipAndCullDistances = 8,
101      .maxSamples = 4,
102      .maxMeshOutputVerticesNV = 256,
103      .maxMeshOutputPrimitivesNV = 512,
104      .maxMeshWorkGroupSizeX_NV = 32,
105      .maxMeshWorkGroupSizeY_NV = 1,
106      .maxMeshWorkGroupSizeZ_NV = 1,
107      .maxTaskWorkGroupSizeX_NV = 32,
108      .maxTaskWorkGroupSizeY_NV = 1,
109      .maxTaskWorkGroupSizeZ_NV = 1,
110      .maxMeshViewCountNV = 4,
111      .maxDualSourceDrawBuffersEXT = 1,
112      .limits =
113          TLimits{
114              .nonInductiveForLoops = 1,
115              .whileLoops = 1,
116              .doWhileLoops = 1,
117              .generalUniformIndexing = 1,
118              .generalAttributeMatrixVectorIndexing = 1,
119              .generalVaryingIndexing = 1,
120              .generalSamplerIndexing = 1,
121              .generalVariableIndexing = 1,
122              .generalConstantMatrixVectorIndexing = 1,
123          },
124  };
125  
126  EShLanguage ToEshShaderStage(vk::ShaderStageFlagBits stage) {
127      switch (stage) {
128      case vk::ShaderStageFlagBits::eVertex:
129          return EShLanguage::EShLangVertex;
130      case vk::ShaderStageFlagBits::eGeometry:
131          return EShLanguage::EShLangGeometry;
132      case vk::ShaderStageFlagBits::eFragment:
133          return EShLanguage::EShLangFragment;
134      case vk::ShaderStageFlagBits::eCompute:
135          return EShLanguage::EShLangCompute;
136      default:
137          UNREACHABLE_MSG("Unkown shader stage {}", stage);
138      }
139      return EShLanguage::EShLangVertex;
140  }
141  
142  bool InitializeCompiler() {
143      static bool glslang_initialized = false;
144  
145      if (glslang_initialized) {
146          return true;
147      }
148  
149      if (!glslang::InitializeProcess()) {
150          LOG_CRITICAL(Render_Vulkan, "Failed to initialize glslang shader compiler");
151          return false;
152      }
153  
154      std::atexit([]() { glslang::FinalizeProcess(); });
155  
156      glslang_initialized = true;
157      return true;
158  }
159  } // Anonymous namespace
160  
161  vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) {
162      if (!InitializeCompiler()) {
163          return {};
164      }
165  
166      EProfile profile = ECoreProfile;
167      EShMessages messages =
168          static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
169      EShLanguage lang = ToEshShaderStage(stage);
170  
171      const int default_version = 450;
172      const char* pass_source_code = code.data();
173      int pass_source_code_length = static_cast<int>(code.size());
174  
175      auto shader = std::make_unique<glslang::TShader>(lang);
176      shader->setEnvTarget(glslang::EShTargetSpv,
177                           glslang::EShTargetLanguageVersion::EShTargetSpv_1_3);
178      shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1);
179  
180      glslang::TShader::ForbidIncluder includer;
181      if (!shader->parse(&DefaultTBuiltInResource, default_version, profile, false, true, messages,
182                         includer)) [[unlikely]] {
183          LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(),
184                   shader->getInfoDebugLog());
185          return {};
186      }
187  
188      // Even though there's only a single shader, we still need to link it to generate SPV
189      auto program = std::make_unique<glslang::TProgram>();
190      program->addShader(shader.get());
191      if (!program->link(messages)) {
192          LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(),
193                   program->getInfoDebugLog());
194          return {};
195      }
196  
197      glslang::TIntermediate* intermediate = program->getIntermediate(lang);
198      std::vector<u32> out_code;
199      spv::SpvBuildLogger logger;
200      glslang::SpvOptions options;
201  
202      // Enable optimizations on the generated SPIR-V code.
203      options.disableOptimizer = false;
204      options.validate = false;
205      options.optimizeSize = true;
206  
207      out_code.reserve(8_KiB);
208      glslang::GlslangToSpv(*intermediate, out_code, &logger, &options);
209  
210      const std::string spv_messages = logger.getAllMessages();
211      if (!spv_messages.empty()) {
212          LOG_INFO(Render_Vulkan, "SPIR-V conversion messages: {}", spv_messages);
213      }
214  
215      return CompileSPV(out_code, device);
216  }
217  
218  vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) {
219      const vk::ShaderModuleCreateInfo shader_info = {
220          .codeSize = code.size() * sizeof(u32),
221          .pCode = code.data(),
222      };
223  
224      try {
225          return device.createShaderModule(shader_info);
226      } catch (vk::SystemError& err) {
227          UNREACHABLE_MSG("{}", err.what());
228      }
229  
230      return {};
231  }
232  
233  } // namespace Vulkan