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, &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,std::vector<uint32_t> * result) const264 Result ShaderCompiler::CompileHlsl(const Shader* shader,
265                                    std::vector<uint32_t>* result) const {
266   std::string target;
267   if (shader->GetType() == kShaderTypeCompute)
268     target = "cs_6_2";
269   else if (shader->GetType() == kShaderTypeFragment)
270     target = "ps_6_2";
271   else if (shader->GetType() == kShaderTypeGeometry)
272     target = "gs_6_2";
273   else if (shader->GetType() == kShaderTypeVertex)
274     target = "vs_6_2";
275   else
276     return Result("Unknown shader type");
277 
278   return dxchelper::Compile(shader->GetData(), "main", target, spv_env_,
279                             shader->GetFilePath(), virtual_files_, result);
280 }
281 #else
CompileHlsl(const Shader *,std::vector<uint32_t> *) const282 Result ShaderCompiler::CompileHlsl(const Shader*,
283                                    std::vector<uint32_t>*) const {
284   return {};
285 }
286 #endif  // AMBER_ENABLE_DXC
287 
288 #if AMBER_ENABLE_CLSPV
CompileOpenCLC(Pipeline::ShaderInfo * shader_info,Pipeline * pipeline,spv_target_env env,std::vector<uint32_t> * result) const289 Result ShaderCompiler::CompileOpenCLC(Pipeline::ShaderInfo* shader_info,
290                                       Pipeline* pipeline,
291                                       spv_target_env env,
292                                       std::vector<uint32_t>* result) const {
293   return clspvhelper::Compile(shader_info, pipeline, env, result);
294 }
295 #endif  // AMBER_ENABLE_CLSPV
296 
297 namespace {
298 
299 // Value for the Vulkan API, used in the Shaderc API
300 const uint32_t kVulkan = 0;
301 // Values for versions of the Vulkan API, used in the Shaderc API
302 const uint32_t kVulkan_1_0 = (uint32_t(1) << 22);
303 const uint32_t kVulkan_1_1 = (uint32_t(1) << 22) | (1 << 12);
304 const uint32_t kVulkan_1_2 = (uint32_t(1) << 22) | (2 << 12);
305 // Values for SPIR-V versions, used in the Shaderc API
306 const uint32_t kSpv_1_0 = uint32_t(0x10000);
307 const uint32_t kSpv_1_1 = uint32_t(0x10100);
308 const uint32_t kSpv_1_2 = uint32_t(0x10200);
309 const uint32_t kSpv_1_3 = uint32_t(0x10300);
310 const uint32_t kSpv_1_4 = uint32_t(0x10400);
311 const uint32_t kSpv_1_5 = uint32_t(0x10500);
312 
313 #if AMBER_ENABLE_SHADERC
314 // Check that we have the right values, from the original definitions
315 // in the Shaderc API.
316 static_assert(kVulkan == shaderc_target_env_vulkan,
317               "enum vulkan* value mismatch");
318 static_assert(kVulkan_1_0 == shaderc_env_version_vulkan_1_0,
319               "enum vulkan1.0 value mismatch");
320 static_assert(kVulkan_1_1 == shaderc_env_version_vulkan_1_1,
321               "enum vulkan1.1 value mismatch");
322 static_assert(kVulkan_1_2 == shaderc_env_version_vulkan_1_2,
323               "enum vulkan1.2 value mismatch");
324 static_assert(kSpv_1_0 == shaderc_spirv_version_1_0,
325               "enum spv1.0 value mismatch");
326 static_assert(kSpv_1_1 == shaderc_spirv_version_1_1,
327               "enum spv1.1 value mismatch");
328 static_assert(kSpv_1_2 == shaderc_spirv_version_1_2,
329               "enum spv1.2 value mismatch");
330 static_assert(kSpv_1_3 == shaderc_spirv_version_1_3,
331               "enum spv1.3 value mismatch");
332 static_assert(kSpv_1_4 == shaderc_spirv_version_1_4,
333               "enum spv1.4 value mismatch");
334 static_assert(kSpv_1_5 == shaderc_spirv_version_1_5,
335               "enum spv1.5 value mismatch");
336 #endif
337 
338 }  // namespace
339 
ParseSpvEnv(const std::string & spv_env,uint32_t * target_env,uint32_t * target_env_version,uint32_t * spirv_version)340 Result ParseSpvEnv(const std::string& spv_env,
341                    uint32_t* target_env,
342                    uint32_t* target_env_version,
343                    uint32_t* spirv_version) {
344   if (!target_env || !target_env_version || !spirv_version)
345     return Result("ParseSpvEnv: null pointer parameter");
346 
347   // Use the same values as in Shaderc's shaderc/env.h
348   struct Values {
349     uint32_t env;
350     uint32_t env_version;
351     uint32_t spirv_version;
352   };
353   Values values{kVulkan, kVulkan_1_0, kSpv_1_0};
354 
355   if (spv_env == "" || spv_env == "spv1.0") {
356     values = {kVulkan, kVulkan_1_0, kSpv_1_0};
357   } else if (spv_env == "spv1.1") {
358     values = {kVulkan, kVulkan_1_1, kSpv_1_1};
359   } else if (spv_env == "spv1.2") {
360     values = {kVulkan, kVulkan_1_1, kSpv_1_2};
361   } else if (spv_env == "spv1.3") {
362     values = {kVulkan, kVulkan_1_1, kSpv_1_3};
363   } else if (spv_env == "spv1.4") {
364     // Vulkan 1.2 requires support for SPIR-V 1.4,
365     // but Vulkan 1.1 permits it with an extension.
366     // So Vulkan 1.2 is the right answer here.
367     values = {kVulkan, kVulkan_1_2, kSpv_1_4};
368   } else if (spv_env == "spv1.5") {
369     values = {kVulkan, kVulkan_1_2, kSpv_1_5};
370   } else if (spv_env == "vulkan1.0") {
371     values = {kVulkan, kVulkan_1_0, kSpv_1_0};
372   } else if (spv_env == "vulkan1.1") {
373     // Vulkan 1.1 requires support for SPIR-V 1.3.
374     values = {kVulkan, kVulkan_1_1, kSpv_1_3};
375   } else if (spv_env == "vulkan1.1spv1.4") {
376     values = {kVulkan, kVulkan_1_1, kSpv_1_4};
377   } else if (spv_env == "vulkan1.2") {
378     values = {kVulkan, kVulkan_1_2, kSpv_1_5};
379   } else {
380     return Result(std::string("Unrecognized environment ") + spv_env);
381   }
382 
383   *target_env = values.env;
384   *target_env_version = values.env_version;
385   *spirv_version = values.spirv_version;
386   return {};
387 }
388 
389 }  // namespace amber
390