1 // Copyright 2019 The Shaderc Authors. All rights reserved.
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 <fstream>
16 #include <iomanip>
17 #include <iostream>
18 #include <sstream>
19 #include <vector>
20 
21 #include "libshaderc_util/args.h"
22 #include "libshaderc_util/string_piece.h"
23 #include "shaderc/env.h"
24 #include "spvc/spvc.hpp"
25 #include "shaderc/status.h"
26 #include "spirv-tools/libspirv.h"
27 
28 using shaderc_util::string_piece;
29 
30 namespace {
31 
32 // Prints the help message.
PrintHelp(std::ostream * out)33 void PrintHelp(std::ostream* out) {
34   *out << R"(spvc - Compile SPIR-V into GLSL/HLSL/MSL.
35 
36 Usage: spvc [options] file
37 
38 An input file of - represents standard input.
39 
40 Options:
41   --help                   Display available options.
42   -v                       Display compiler version information.
43   -o <output file>         '-' means standard output.
44   --no-validate            Disable validating input and intermediate source.
45                              Validation is by default enabled.
46   --no-optimize            Disable optimizing input and intermediate source.
47                              Optimization is by default enabled.
48   --source-env=<env>       Execution environment of the input source.
49                              <env> is vulkan1.0 (the default), vulkan1.1,
50                              or webgpu0
51   --entry=<name>           Specify entry point.
52   --language=<lang>        Specify output language.
53                              <lang> is glsl (the default), msl or hlsl.
54   --glsl-version=<ver>     Specify GLSL output language version, e.g. 100
55                              Default is 450 if not detected from input.
56   --msl-version=<ver>      Specify MSL output language version, e.g. 100
57                              Default is 10200.
58   --target-env=<env>       Target intermediate execution environment to
59                            transform the source to before cross-compiling.
60                            Defaults to the value set for source-env.
61                            <env> must be one of the legal values for source-env.
62 
63                            If target-env and source-env are the same, then no
64                            transformation is performed.
65                            If there is no defined transformation between source
66                            and target the operation will fail.
67                            Defined transforms:
68                              webgpu0 -> vulkan1.1
69                              vulkan1.1 -> webgpu0
70    --use-spvc-parser       Use the built in parser for generating spirv-cross IR
71                            instead of spirv-cross.
72 
73 
74   The following flags behave as in spirv-cross:
75 
76   --remove-unused-variables
77   --vulkan-semantics
78   --separate-shader-objects
79   --flatten-ubo
80   --flatten-multidimensional-arrays
81   --es
82   --no-es
83   --glsl-emit-push-constant-as-ubo
84   --msl-swizzle-texture-samples
85   --msl-platform=ios|macos
86   --msl-pad-fragment-output
87   --msl-capture-output
88   --msl-domain-lower-left
89   --msl-argument-buffers
90   --msl-discrete-descriptor-set=<number>
91   --emit-line-directives
92   --hlsl-enable-compat
93   --shader-model=<model>
94 )";
95 }
96 
97 const char kBuildVersion[] = ""
98     // TODO(bug 653): #include "build-version.inc"
99     ;
100 
ReadFile(const std::string & path,std::vector<uint32_t> * out)101 bool ReadFile(const std::string& path, std::vector<uint32_t>* out) {
102   FILE* file =
103       path == "-" ? freopen(nullptr, "rb", stdin) : fopen(path.c_str(), "rb");
104   if (!file) {
105     std::cerr << "Failed to open SPIR-V file: " << path << std::endl;
106     return false;
107   }
108 
109   fseek(file, 0, SEEK_END);
110   out->resize(ftell(file) / sizeof((*out)[0]));
111   rewind(file);
112 
113   if (fread(out->data(), sizeof((*out)[0]), out->size(), file) != out->size()) {
114     std::cerr << "Failed to read SPIR-V file: " << path << std::endl;
115     out->clear();
116     return false;
117   }
118 
119   fclose(file);
120   return true;
121 }
122 
StringPieceToEnvEnum(const string_piece & str,shaderc_spvc_spv_env * env)123 bool StringPieceToEnvEnum(const string_piece& str, shaderc_spvc_spv_env* env) {
124   if (!env) {
125     std::cerr << "spvc: error: internal error calling StringPieceToEnvEnum"
126               << std::endl;
127     return false;
128   }
129 
130   if (str == "vulkan1.0") {
131     *env = shaderc_spvc_spv_env_vulkan_1_0;
132   } else if (str == "vulkan1.1") {
133     *env = shaderc_spvc_spv_env_vulkan_1_1;
134   } else if (str == "webgpu0") {
135     *env = shaderc_spvc_spv_env_webgpu_0;
136   } else {
137     std::cerr << "spvc: error: invalid value '" << str
138               << "' in --source-env= or --target-env=" << std::endl;
139     return false;
140   }
141   return true;
142 }
143 
144 }  // anonymous namespace
145 
main(int argc,char ** argv)146 int main(int argc, char** argv) {
147   shaderc_spvc::Context context;
148   std::vector<uint32_t> input;
149   std::vector<uint32_t> msl_discrete_descriptor;
150   string_piece output_path;
151   string_piece output_language;
152   string_piece source_str = "vulkan1.0";
153   string_piece target_str = "";
154 
155   // Scan and find any source and target env flags, since we need to know those
156   // to create options.
157   for (int i = 1; i < argc; ++i) {
158     const string_piece arg = argv[i];
159     if (arg.starts_with("--source-env=")) {
160       string_piece env;
161       shaderc_util::GetOptionArgument(argc, argv, &i, "--source-env=", &env);
162       source_str = env;
163     } else if (arg.starts_with("--target-env=")) {
164       string_piece env;
165       shaderc_util::GetOptionArgument(argc, argv, &i, "--target-env=", &env);
166       target_str = env;
167     }
168   }
169 
170   if (target_str == "") {
171     target_str = source_str;
172   }
173 
174   shaderc_spvc_spv_env source_env;
175   if (!StringPieceToEnvEnum(source_str, &source_env)) {
176     return 1;
177   }
178 
179   shaderc_spvc_spv_env target_env;
180   if (!StringPieceToEnvEnum(target_str, &target_env)) {
181     return 1;
182   }
183 
184   shaderc_spvc::CompileOptions options(source_env, target_env);
185   for (int i = 1; i < argc; ++i) {
186     const string_piece arg = argv[i];
187     if (arg == "--help") {
188       ::PrintHelp(&std::cout);
189       return 0;
190     } else if (arg == "-v") {
191       std::cout << kBuildVersion << std::endl;
192       std::cout << "Target: " << spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0)
193                 << std::endl;
194       return 0;
195     } else if (arg.starts_with("-o")) {
196       if (!shaderc_util::GetOptionArgument(argc, argv, &i, "-o",
197                                            &output_path)) {
198         std::cerr
199             << "spvc: error: argument to '-o' is missing (expected 1 value)"
200             << std::endl;
201         return 1;
202       }
203     } else if (arg.starts_with("--entry=")) {
204       string_piece entry_point;
205       shaderc_util::GetOptionArgument(argc, argv, &i, "--entry=", &entry_point);
206       options.SetEntryPoint(entry_point.data());
207     } else if (arg.starts_with("--glsl-version=")) {
208       string_piece version_str;
209       shaderc_util::GetOptionArgument(argc, argv, &i,
210                                       "--glsl-version=", &version_str);
211       uint32_t version_num;
212       if (!shaderc_util::ParseUint32(version_str.str(), &version_num)) {
213         std::cerr << "spvc: error: invalid value '" << version_str
214                   << "' in --glsl-version=" << std::endl;
215         return 1;
216       }
217       options.SetGLSLLanguageVersion(version_num);
218     } else if (arg.starts_with("--msl-version=")) {
219       string_piece version_str;
220       shaderc_util::GetOptionArgument(argc, argv, &i,
221                                       "--msl-version=", &version_str);
222       uint32_t version_num;
223       if (!shaderc_util::ParseUint32(version_str.str(), &version_num)) {
224         std::cerr << "spvc: error: invalid value '" << version_str
225                   << "' in --msl-version=" << std::endl;
226         return 1;
227       }
228       options.SetMSLLanguageVersion(version_num);
229     } else if (arg.starts_with("--language=")) {
230       shaderc_util::GetOptionArgument(argc, argv, &i,
231                                       "--language=", &output_language);
232       if (!(output_language == "glsl" || output_language == "msl" ||
233             output_language == "hlsl" || output_language == "vulkan")) {
234         std::cerr << "spvc: error: invalid value '" << output_language
235                   << "' in --language=" << std::endl;
236         return 1;
237       }
238     } else if (arg == "--remove-unused-variables") {
239       options.SetRemoveUnusedVariables(true);
240     } else if (arg == "--no-validate"){
241       options.SetValidate(false);
242     } else if (arg == "--no-optimize"){
243       options.SetOptimize(false);
244     } else if (arg == "--robust-buffer-access-pass"){
245       options.SetRobustBufferAccessPass(true);
246     } else if (arg == "--vulkan-semantics") {
247       options.SetVulkanSemantics(true);
248     } else if (arg == "--separate-shader-objects") {
249       options.SetSeparateShaderObjects(true);
250     } else if (arg == "--flatten-ubo") {
251       options.SetFlattenUbo(true);
252     } else if (arg == "--flatten-multidimensional-arrays") {
253       options.SetFlattenMultidimensionalArrays(true);
254     } else if (arg == "--es") {
255       options.SetES(true);
256     } else if (arg == "--no-es") {
257       options.SetES(false);
258     } else if (arg == "--hlsl-enable-compat") {
259       options.SetHLSLPointSizeCompat(true);
260       options.SetHLSLPointCoordCompat(true);
261     } else if (arg == "--glsl-emit-push-constant-as-ubo") {
262       options.SetGLSLEmitPushConstantAsUBO(true);
263     } else if (arg == "--msl-swizzle-texture-samples") {
264       options.SetMSLSwizzleTextureSamples(true);
265     } else if (arg.starts_with("--msl-platform=")) {
266       string_piece platform;
267       GetOptionArgument(argc, argv, &i, "--msl-platform=", &platform);
268       if (platform == "ios") {
269         options.SetMSLPlatform(shaderc_spvc_msl_platform_ios);
270       } else if (platform == "macos") {
271         options.SetMSLPlatform(shaderc_spvc_msl_platform_macos);
272       } else {
273         std::cerr << "spvc: error: invalid value '" << platform
274                   << "' in --msl-platform=" << std::endl;
275         return 1;
276       }
277     } else if (arg == "--msl-pad-fragment-output") {
278       options.SetMSLPadFragmentOutput(true);
279     } else if (arg == "--msl-capture-output") {
280       options.SetMSLCapture(true);
281     } else if (arg == "--msl-domain-lower-left") {
282       options.SetMSLDomainLowerLeft(true);
283     } else if (arg == "--msl-argument-buffers") {
284       options.SetMSLArgumentBuffers(true);
285     } else if (arg.starts_with("--msl-discrete-descriptor-set=")) {
286       string_piece descriptor_str;
287       GetOptionArgument(argc, argv, &i,
288                         "--msl-discrete-descriptor-set=", &descriptor_str);
289       uint32_t descriptor_num;
290       if (!shaderc_util::ParseUint32(descriptor_str.str(), &descriptor_num)) {
291         std::cerr << "spvc: error: invalid value '" << descriptor_str
292                   << "' in --msl-discrete-descriptor-set=" << std::endl;
293         return 1;
294       }
295       msl_discrete_descriptor.push_back(descriptor_num);
296     } else if (arg == "--emit-line-directives") {
297       options.SetEmitLineDirectives(true);
298     } else if (arg.starts_with("--shader-model=")) {
299       string_piece shader_model_str;
300       shaderc_util::GetOptionArgument(argc, argv, &i,
301                                       "--shader-model=", &shader_model_str);
302       uint32_t shader_model_num;
303       if (!shaderc_util::ParseUint32(shader_model_str.str(),
304                                      &shader_model_num)) {
305         std::cerr << "spvc: error: invalid value '" << shader_model_str
306                   << "' in --shader-model=" << std::endl;
307         return 1;
308       }
309       options.SetHLSLShaderModel(shader_model_num);
310     } else if (arg.starts_with("--source-env=")) {
311       // Parsed in the previous iteration
312     } else if (arg.starts_with("--target-env=")) {
313       // Parsed in the previous iteration
314     } else if (arg == "--use-spvc-parser") {
315       context.SetUseSpvcParser(true);
316     } else {
317       if (!ReadFile(arg.str(), &input)) {
318         std::cerr << "spvc: error: could not read file" << std::endl;
319         return 1;
320       }
321     }
322   }
323 
324   options.SetMSLDiscreteDescriptorSets(msl_discrete_descriptor);
325 
326   shaderc_spvc::CompilationResult result;
327   shaderc_spvc_status status = shaderc_spvc_status_configuration_error;
328 
329   if (output_language == "glsl") {
330     status = context.InitializeForGlsl((const uint32_t*)input.data(),
331                                        input.size(), options);
332   } else if (output_language == "msl") {
333     status = context.InitializeForMsl((const uint32_t*)input.data(),
334                                       input.size(), options);
335   } else if (output_language == "hlsl") {
336     status = context.InitializeForHlsl((const uint32_t*)input.data(),
337                                        input.size(), options);
338   } else if (output_language == "vulkan") {
339     status = context.InitializeForVulkan((const uint32_t*)input.data(),
340                                          input.size(), options);
341   } else {
342     std::cerr << "Attempted to output to unknown language: " << output_language
343               << std::endl;
344     return 1;
345   }
346 
347   if (status == shaderc_spvc_status_success)
348     status = context.CompileShader(&result);
349 
350   switch (status) {
351     case shaderc_spvc_status_validation_error: {
352       std::cerr << "validation failed:\n" << context.GetMessages() << std::endl;
353       return 1;
354     }
355 
356     case shaderc_spvc_status_success: {
357       const char* path = output_path.data();
358       if (output_language != "vulkan") {
359         std::string string_output;
360         result.GetStringOutput(&string_output);
361         if (path && strcmp(path, "-")) {
362           std::basic_ofstream<char>(path) << string_output;
363         } else {
364           std::cout << string_output;
365         }
366       } else {
367         if (path && strcmp(path, "-")) {
368           std::vector<uint32_t> binary_output;
369           result.GetBinaryOutput(&binary_output);
370           std::ofstream out(path, std::ios::binary);
371           out.write((char*)binary_output.data(),
372                     (sizeof(uint32_t) / sizeof(char)) *
373                         binary_output.size());
374         } else {
375           std::cerr << "Cowardly refusing to output binary result to screen"
376                     << std::endl;
377           return 1;
378         }
379       }
380       return 0;
381     }
382 
383     case shaderc_spvc_status_compilation_error: {
384       std::cerr << "compilation failed:\n"
385                 << context.GetMessages() << std::endl;
386       return 1;
387     }
388 
389     default:
390       std::cerr << "unexpected error " << status << std::endl;
391       return 1;
392   }
393 }
394