• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/shader_compiler.h"
16 
17 #include <algorithm>
18 #include <cstdlib>
19 #include <iterator>
20 #include <string>
21 #include <utility>
22 
23 #if AMBER_ENABLE_SPIRV_TOOLS
24 #include "spirv-tools/libspirv.hpp"
25 #include "spirv-tools/linker.hpp"
26 #include "spirv-tools/optimizer.hpp"
27 #endif  // AMBER_ENABLE_SPIRV_TOOLS
28 
29 #if AMBER_ENABLE_SHADERC
30 #pragma clang diagnostic push
31 #pragma clang diagnostic ignored "-Wold-style-cast"
32 #pragma clang diagnostic ignored "-Wshadow-uncaptured-local"
33 #pragma clang diagnostic ignored "-Wweak-vtables"
34 #include "shaderc/shaderc.hpp"
35 #pragma clang diagnostic pop
36 #endif  // AMBER_ENABLE_SHADERC
37 
38 #if AMBER_ENABLE_DXC
39 #include "src/dxc_helper.h"
40 #endif  // AMBER_ENABLE_DXC
41 
42 #if AMBER_ENABLE_CLSPV
43 #include "src/clspv_helper.h"
44 #endif  // AMBER_ENABLE_CLSPV
45 
46 namespace amber {
47 
48 ShaderCompiler::ShaderCompiler() = default;
49 
ShaderCompiler(const std::string & env,bool disable_spirv_validation,VirtualFileStore * virtual_files)50 ShaderCompiler::ShaderCompiler(const std::string& env,
51                                bool disable_spirv_validation,
52                                VirtualFileStore* virtual_files)
53     : spv_env_(env),
54       disable_spirv_validation_(disable_spirv_validation),
55       virtual_files_(virtual_files) {
56   // Do not warn about virtual_files_ not being used.
57   // This is conditionally used based on preprocessor defines.
58   (void)virtual_files_;
59 }
60 
61 ShaderCompiler::~ShaderCompiler() = default;
62 
Compile(Pipeline * pipeline,Pipeline::ShaderInfo * shader_info,const ShaderMap & shader_map) const63 std::pair<Result, std::vector<uint32_t>> ShaderCompiler::Compile(
64     Pipeline* pipeline,
65     Pipeline::ShaderInfo* shader_info,
66     const ShaderMap& shader_map) const {
67   const auto shader = shader_info->GetShader();
68   std::string key = shader->GetName();
69   const std::string pipeline_name = pipeline->GetName();
70   if (pipeline_name != "") {
71     key = pipeline_name + "-" + key;
72   }
73   auto it = shader_map.find(key);
74   if (it != shader_map.end()) {
75 #if AMBER_ENABLE_CLSPV
76     if (shader->GetFormat() == kShaderFormatOpenCLC) {
77       return {Result("OPENCL-C shaders do not support pre-compiled shaders"),
78               {}};
79     }
80 #endif  // AMBER_ENABLE_CLSPV
81     return {{}, it->second};
82   }
83 
84 #if AMBER_ENABLE_SPIRV_TOOLS
85   std::string spv_errors;
86 
87   spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
88   if (!spv_env_.empty()) {
89     if (!spvParseTargetEnv(spv_env_.c_str(), &target_env))
90       return {Result("Unable to parse SPIR-V target environment"), {}};
91   }
92 
93   auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
94                                     const spv_position_t& position,
95                                     const char* message) {
96     switch (level) {
97       case SPV_MSG_FATAL:
98       case SPV_MSG_INTERNAL_ERROR:
99       case SPV_MSG_ERROR:
100         spv_errors += "error: line " + std::to_string(position.index) + ": " +
101                       message + "\n";
102         break;
103       case SPV_MSG_WARNING:
104         spv_errors += "warning: line " + std::to_string(position.index) + ": " +
105                       message + "\n";
106         break;
107       case SPV_MSG_INFO:
108         spv_errors += "info: line " + std::to_string(position.index) + ": " +
109                       message + "\n";
110         break;
111       case SPV_MSG_DEBUG:
112         break;
113     }
114   };
115 
116   spvtools::SpirvTools tools(target_env);
117   tools.SetMessageConsumer(msg_consumer);
118 #endif  // AMBER_ENABLE_SPIRV_TOOLS
119 
120   std::vector<uint32_t> results;
121 
122   if (shader->GetFormat() == kShaderFormatSpirvHex) {
123     Result r = ParseHex(shader->GetData(), &results);
124     if (!r.IsSuccess())
125       return {Result("Unable to parse shader hex."), {}};
126 
127 #if AMBER_ENABLE_SHADERC
128   } else if (shader->GetFormat() == kShaderFormatGlsl) {
129     Result r = CompileGlsl(shader, &results);
130     if (!r.IsSuccess())
131       return {r, {}};
132 #endif  // AMBER_ENABLE_SHADERC
133 
134 #if AMBER_ENABLE_DXC
135   } else if (shader->GetFormat() == kShaderFormatHlsl) {
136     Result r = CompileHlsl(shader, shader_info->GetEmitDebugInfo(), &results);
137     if (!r.IsSuccess())
138       return {r, {}};
139 #endif  // AMBER_ENABLE_DXC
140 
141 #if AMBER_ENABLE_SPIRV_TOOLS
142   } else if (shader->GetFormat() == kShaderFormatSpirvAsm) {
143     if (!tools.Assemble(shader->GetData(), &results,
144                         spvtools::SpirvTools::kDefaultAssembleOption)) {
145       return {Result("Shader assembly failed: " + spv_errors), {}};
146     }
147 #endif  // AMBER_ENABLE_SPIRV_TOOLS
148 
149 #if AMBER_ENABLE_CLSPV
150   } else if (shader->GetFormat() == kShaderFormatOpenCLC) {
151     Result r = CompileOpenCLC(shader_info, pipeline, target_env, &results);
152     if (!r.IsSuccess())
153       return {r, {}};
154 #endif  // AMBER_ENABLE_CLSPV
155 
156   } else {
157     return {Result("Invalid shader format"), results};
158   }
159 
160   // Validate the shader, but have an option to disable that.
161   // Always use the data member, to avoid an unused-variable warning
162   // when not using SPIRV-Tools support.
163   if (!disable_spirv_validation_) {
164 #if AMBER_ENABLE_SPIRV_TOOLS
165     spvtools::ValidatorOptions options;
166     if (!tools.Validate(results.data(), results.size(), options))
167       return {Result("Invalid shader: " + spv_errors), {}};
168 #endif  // AMBER_ENABLE_SPIRV_TOOLS
169   }
170 
171 #if AMBER_ENABLE_SPIRV_TOOLS
172   // Optimize the shader if any optimizations were specified.
173   if (!shader_info->GetShaderOptimizations().empty()) {
174     spvtools::Optimizer optimizer(target_env);
175     optimizer.SetMessageConsumer(msg_consumer);
176     if (!optimizer.RegisterPassesFromFlags(
177             shader_info->GetShaderOptimizations())) {
178       return {Result("Invalid optimizations: " + spv_errors), {}};
179     }
180     if (!optimizer.Run(results.data(), results.size(), &results))
181       return {Result("Optimizations failed: " + spv_errors), {}};
182   }
183 #endif  // AMBER_ENABLE_SPIRV_TOOLS
184 
185   return {{}, results};
186 }
187 
ParseHex(const std::string & data,std::vector<uint32_t> * result) const188 Result ShaderCompiler::ParseHex(const std::string& data,
189                                 std::vector<uint32_t>* result) const {
190   size_t used = 0;
191   const char* str = data.c_str();
192   uint8_t converted = 0;
193   uint32_t tmp = 0;
194   while (used < data.length()) {
195     char* new_pos = nullptr;
196     uint64_t v = static_cast<uint64_t>(std::strtol(str, &new_pos, 16));
197 
198     ++converted;
199 
200     // TODO(dsinclair): Is this actually right?
201     tmp = tmp | (static_cast<uint32_t>(v) << (8 * (converted - 1)));
202     if (converted == 4) {
203       result->push_back(tmp);
204       tmp = 0;
205       converted = 0;
206     }
207 
208     used += static_cast<size_t>(new_pos - str);
209     str = new_pos;
210   }
211   return {};
212 }
213 
214 #if AMBER_ENABLE_SHADERC
CompileGlsl(const Shader * shader,std::vector<uint32_t> * result) const215 Result ShaderCompiler::CompileGlsl(const Shader* shader,
216                                    std::vector<uint32_t>* result) const {
217   shaderc::Compiler compiler;
218   shaderc::CompileOptions options;
219 
220   uint32_t env = 0u;
221   uint32_t env_version = 0u;
222   uint32_t spirv_version = 0u;
223   auto r = ParseSpvEnv(spv_env_, &env, &env_version, &spirv_version);
224   if (!r.IsSuccess())
225     return r;
226 
227   options.SetTargetEnvironment(static_cast<shaderc_target_env>(env),
228                                env_version);
229   options.SetTargetSpirv(static_cast<shaderc_spirv_version>(spirv_version));
230 
231   shaderc_shader_kind kind;
232   if (shader->GetType() == kShaderTypeCompute)
233     kind = shaderc_compute_shader;
234   else if (shader->GetType() == kShaderTypeFragment)
235     kind = shaderc_fragment_shader;
236   else if (shader->GetType() == kShaderTypeGeometry)
237     kind = shaderc_geometry_shader;
238   else if (shader->GetType() == kShaderTypeVertex)
239     kind = shaderc_vertex_shader;
240   else if (shader->GetType() == kShaderTypeTessellationControl)
241     kind = shaderc_tess_control_shader;
242   else if (shader->GetType() == kShaderTypeTessellationEvaluation)
243     kind = shaderc_tess_evaluation_shader;
244   else
245     return Result("Unknown shader type");
246 
247   shaderc::SpvCompilationResult module =
248       compiler.CompileGlslToSpv(shader->GetData(), kind, "-", options);
249 
250   if (module.GetCompilationStatus() != shaderc_compilation_status_success)
251     return Result(module.GetErrorMessage());
252 
253   std::copy(module.cbegin(), module.cend(), std::back_inserter(*result));
254   return {};
255 }
256 #else
CompileGlsl(const Shader *,std::vector<uint32_t> *) const257 Result ShaderCompiler::CompileGlsl(const Shader*,
258                                    std::vector<uint32_t>*) const {
259   return {};
260 }
261 #endif  // AMBER_ENABLE_SHADERC
262 
263 #if AMBER_ENABLE_DXC
CompileHlsl(const Shader * shader,bool emit_debug_info,std::vector<uint32_t> * result) const264 Result ShaderCompiler::CompileHlsl(const Shader* shader,
265                                    bool emit_debug_info,
266                                    std::vector<uint32_t>* result) const {
267   std::string target;
268   if (shader->GetType() == kShaderTypeCompute)
269     target = "cs_6_2";
270   else if (shader->GetType() == kShaderTypeFragment)
271     target = "ps_6_2";
272   else if (shader->GetType() == kShaderTypeGeometry)
273     target = "gs_6_2";
274   else if (shader->GetType() == kShaderTypeVertex)
275     target = "vs_6_2";
276   else
277     return Result("Unknown shader type");
278 
279   return dxchelper::Compile(shader->GetData(), "main", target, spv_env_,
280                             shader->GetFilePath(), virtual_files_,
281                             emit_debug_info, result);
282 }
283 #else
CompileHlsl(const Shader *,bool,std::vector<uint32_t> *) const284 Result ShaderCompiler::CompileHlsl(const Shader*,
285                                    bool,
286                                    std::vector<uint32_t>*) const {
287   return {};
288 }
289 #endif  // AMBER_ENABLE_DXC
290 
291 #if AMBER_ENABLE_CLSPV
CompileOpenCLC(Pipeline::ShaderInfo * shader_info,Pipeline * pipeline,spv_target_env env,std::vector<uint32_t> * result) const292 Result ShaderCompiler::CompileOpenCLC(Pipeline::ShaderInfo* shader_info,
293                                       Pipeline* pipeline,
294                                       spv_target_env env,
295                                       std::vector<uint32_t>* result) const {
296   return clspvhelper::Compile(shader_info, pipeline, env, result);
297 }
298 #endif  // AMBER_ENABLE_CLSPV
299 
300 namespace {
301 
302 // Value for the Vulkan API, used in the Shaderc API
303 const uint32_t kVulkan = 0;
304 // Values for versions of the Vulkan API, used in the Shaderc API
305 const uint32_t kVulkan_1_0 = (uint32_t(1) << 22);
306 const uint32_t kVulkan_1_1 = (uint32_t(1) << 22) | (1 << 12);
307 const uint32_t kVulkan_1_2 = (uint32_t(1) << 22) | (2 << 12);
308 // Values for SPIR-V versions, used in the Shaderc API
309 const uint32_t kSpv_1_0 = uint32_t(0x10000);
310 const uint32_t kSpv_1_1 = uint32_t(0x10100);
311 const uint32_t kSpv_1_2 = uint32_t(0x10200);
312 const uint32_t kSpv_1_3 = uint32_t(0x10300);
313 const uint32_t kSpv_1_4 = uint32_t(0x10400);
314 const uint32_t kSpv_1_5 = uint32_t(0x10500);
315 
316 #if AMBER_ENABLE_SHADERC
317 // Check that we have the right values, from the original definitions
318 // in the Shaderc API.
319 static_assert(kVulkan == shaderc_target_env_vulkan,
320               "enum vulkan* value mismatch");
321 static_assert(kVulkan_1_0 == shaderc_env_version_vulkan_1_0,
322               "enum vulkan1.0 value mismatch");
323 static_assert(kVulkan_1_1 == shaderc_env_version_vulkan_1_1,
324               "enum vulkan1.1 value mismatch");
325 static_assert(kVulkan_1_2 == shaderc_env_version_vulkan_1_2,
326               "enum vulkan1.2 value mismatch");
327 static_assert(kSpv_1_0 == shaderc_spirv_version_1_0,
328               "enum spv1.0 value mismatch");
329 static_assert(kSpv_1_1 == shaderc_spirv_version_1_1,
330               "enum spv1.1 value mismatch");
331 static_assert(kSpv_1_2 == shaderc_spirv_version_1_2,
332               "enum spv1.2 value mismatch");
333 static_assert(kSpv_1_3 == shaderc_spirv_version_1_3,
334               "enum spv1.3 value mismatch");
335 static_assert(kSpv_1_4 == shaderc_spirv_version_1_4,
336               "enum spv1.4 value mismatch");
337 static_assert(kSpv_1_5 == shaderc_spirv_version_1_5,
338               "enum spv1.5 value mismatch");
339 #endif
340 
341 }  // namespace
342 
ParseSpvEnv(const std::string & spv_env,uint32_t * target_env,uint32_t * target_env_version,uint32_t * spirv_version)343 Result ParseSpvEnv(const std::string& spv_env,
344                    uint32_t* target_env,
345                    uint32_t* target_env_version,
346                    uint32_t* spirv_version) {
347   if (!target_env || !target_env_version || !spirv_version)
348     return Result("ParseSpvEnv: null pointer parameter");
349 
350   // Use the same values as in Shaderc's shaderc/env.h
351   struct Values {
352     uint32_t env;
353     uint32_t env_version;
354     uint32_t spirv_version;
355   };
356   Values values{kVulkan, kVulkan_1_0, kSpv_1_0};
357 
358   if (spv_env == "" || spv_env == "spv1.0") {
359     values = {kVulkan, kVulkan_1_0, kSpv_1_0};
360   } else if (spv_env == "spv1.1") {
361     values = {kVulkan, kVulkan_1_1, kSpv_1_1};
362   } else if (spv_env == "spv1.2") {
363     values = {kVulkan, kVulkan_1_1, kSpv_1_2};
364   } else if (spv_env == "spv1.3") {
365     values = {kVulkan, kVulkan_1_1, kSpv_1_3};
366   } else if (spv_env == "spv1.4") {
367     // Vulkan 1.2 requires support for SPIR-V 1.4,
368     // but Vulkan 1.1 permits it with an extension.
369     // So Vulkan 1.2 is the right answer here.
370     values = {kVulkan, kVulkan_1_2, kSpv_1_4};
371   } else if (spv_env == "spv1.5") {
372     values = {kVulkan, kVulkan_1_2, kSpv_1_5};
373   } else if (spv_env == "vulkan1.0") {
374     values = {kVulkan, kVulkan_1_0, kSpv_1_0};
375   } else if (spv_env == "vulkan1.1") {
376     // Vulkan 1.1 requires support for SPIR-V 1.3.
377     values = {kVulkan, kVulkan_1_1, kSpv_1_3};
378   } else if (spv_env == "vulkan1.1spv1.4") {
379     values = {kVulkan, kVulkan_1_1, kSpv_1_4};
380   } else if (spv_env == "vulkan1.2") {
381     values = {kVulkan, kVulkan_1_2, kSpv_1_5};
382   } else {
383     return Result(std::string("Unrecognized environment ") + spv_env);
384   }
385 
386   *target_env = values.env;
387   *target_env_version = values.env_version;
388   *spirv_version = values.spirv_version;
389   return {};
390 }
391 
392 }  // namespace amber
393