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