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